Compare commits

..

43 Commits

Author SHA1 Message Date
Florian Roger 7b071d7935 simplify child contracts 6 months ago
Florian Roger 5bd07b645b manage career from contract 6 months ago
Florian Roger f9b8002b8e domain selection for task_detail in views 6 months ago
Florian Roger 645729f291 view for task detail 6 months ago
Florian Roger 82e06ac97a call views for tasks 6 months ago
Florian Roger a85e03703e call specific mission detail form view in career form 6 months ago
Florian Roger d9184c627a add task.detail 6 months ago
Florian Roger cb05a977e3 Merge branch '16-contract-dev' into 16-career-dev 6 months ago
Florian Roger f6dc026a39 add fixed-term contract renewal 6 months ago
Florian Roger 9dcc3a7b9c create career from contract form 7 months ago
Florian Roger aba81ca7f9 Merge branch '16-contract-dev' into 16-career-dev 7 months ago
Florian Roger 607b979702 link past and future contracts, closes #9 7 months ago
Florian Roger 6554bcfe4e merge from gn_contract 7 months ago
Florian Roger 3dd73e43a6 Merge branch '16-contract-dev' into 16-career-dev 7 months ago
Florian Roger 58e6bdec97 add contract form, closes #10, relates to #9 7 months ago
Florian Roger 660cbc7fac statusbar widget in career form 7 months ago
Florian Roger d93c76d937 add filters on tasks 7 months ago
Florian Roger 45c1e9613e detail for each mission in career 7 months ago
Florian Roger 1e8f55fe09 basic views 7 months ago
Florian Roger dcc055981b basic gn_career relational structure with missions and tasks 7 months ago
Florian Roger ee789ff122 create Tasks and Missions objects 7 months ago
Florian Roger bc4049e1e4 modifs gone to gn_contract 7 months ago
Florian Roger 89534a647c add gn_contracts for contract types and amendments 7 months ago
Florian Roger 854bbeb0e4 Create holidays module, v16.0.0.0.1 7 months ago
Florian Roger 085819695f gn_cc v0.2 7 months ago
Florian Roger ec627b776f refactor gn_cc as aseparate module 7 months ago
Florian Roger d7ffa017f4 start workflow refactor 7 months ago
Florian Roger 03aa1c0b2c basic implementation of entretien - event relation, Closes #6 7 months ago
Florian Roger b2fbc5fdc4 try to extend event calendar view 7 months ago
Florian Roger 2801953953 Reordering of entretiens and link to event 7 months ago
Florian Roger 8e8d04ad27 Debug entretiens creation, Closes #5, #6 7 months ago
Florian Roger 230521005d structure des entretiens 7 months ago
Florian Roger 48ce980121 entretien type children 7 months ago
Florian Roger e4eb441bd0 ajout de types d'entretien 7 months ago
Florian Roger a86dd2deda compute anciennete 7 months ago
Florian Roger 6efa742956 parent_contract_id field in hr.contract 7 months ago
Florian Roger 05f554ff53 conventional remuneration elements 7 months ago
Florian Roger 54359e0441 add idcc3442 7 months ago
Florian Roger ea0421ee93 use convention collective 7 months ago
Florian Roger 5a41c45941 use of worked_days in hours compute 7 months ago
Florian Roger 5c925ca24f add cuml CP et jours fériés v0.0.3 8 months ago
Florian Roger 6b0beaef61 add days-off types 8 months ago
Florian Roger d1a89cc008 import payslip 8 months ago

@ -2,4 +2,36 @@
Addons for Odoo 16.
Starting with association loi 1901 chart of accounts.
## List of modules
| Name | Version | Description |
|-------------------------|--------------|----------------------------------------------------------|
| gn_l10n_fr_pcg_asso | 16.0.0.0.3 | French chart of account and fiscal position for NGO's |
| gn_cc | 16.0.0.0.2 | Configuration for French convention collective, with data for IDCC3442 |
| gn_holidays | 16.0.0.0.1 | French configuration for Publics Holidays and Leave Management |
| gn_career | 16.0.0.0.11 | Fiche de poste et évolution de carrière |
| gn_contract | 16.0.0.0.4 | Amendements to Hr Contracts |
## ToDo
## gn_payroll
### Major
- [x] Créer une structure d'avenants au contrat (commit 6efa7429)
- [x] Déterminer l'ancienneté du salarié (commit a86dd2de )
- [x] Création d'un modèle pour les entretiens
- [x] Création automatique des entretiens légaux
- [x] Lien entre entretiens et events
- [] gestion de la valorisation professionnelle
- [x] création d'une action de création d'avenants
- [] Faire la fiche de poste
- [] Create report view for each entretien_type
- [] refactor in several modules
### Minor
- [] In gn_payroll_contract.py, class Entretien, need compute rule for "absence" start_point in entretien.type model (cf issue #3)
- [] compute contract.cc_id from contract.company_id.cc (cf issue #4)
- [x] debug entretien start date on creation (cf issue #5)
- [x] debug numérotation des entretiens par salarié on creation (cf issue #6)
- [] better handling of event creation from entretien (cf issue #7)

@ -1,23 +0,0 @@
{
"name": "France - Contrats",
"version": "16.0.0.0.1",
"category": "Payroll",
"summary": "Templates for French contracts and agreements",
"author": "Le Garage Numérique",
"maintainers": ["makayabou"],
"website": "https://odoo.legaragenumerique.fr",
"depends": [
"agreement",
"agreement_legal",
"partner_company_type"
],
"data": [
"views/gn_agreement_bipartisan_agreement.xml",
"views/gn_agreement_external_layout_striped_footer.xml",
"data/gn_agreement_document_layout.xml",
"data/gn_agreement_types.xml",
"data/gn_agreement_convention_cadre_template.xml",
"data/gn_agreement_convention_avenant_template.xml",
],
"license": "LGPL-3",
}

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_agreement.layout_convention_partenariat_avenant" model="agreement">
<field name="is_template">true</field>
<field name="name">Modèle d'avenant lié à une convention-cadre de partenariat</field>
<field name="agreement_type_id" ref="gn_agreement.agreement_type_convention_partenariat"/>
<field name="agreement_subtype_id" ref="gn_agreement.agreement_subtype_convention_partenariat_avenant"/>
<field name="description">Avenant à la convention-cadre {{object.parent_agreement_id.code or ''}}</field>
</record>
<record id="gn_agreement.recital_ref_cadre" model="agreement.recital">
<field name="name">référence à la convention-cadre</field>
<field name="sequence">10</field>
<field name="content"><![CDATA[
Les entités {{ object.agreement_id.company_id.name }} et {{ object.agreement_id.partner_id.name }} sont liées par des engagements mutuels,<br/>
établis dans la convention-cadre {{ object.agreement_id.parent_agreement_id.code }} portant sur le dispositif {{ object.agreement_id.parent_agreement_id.name }}]]>
</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_avenant"/>
</record>
<record id="gn_agreement.convention_partenariat_avenant_section_art1" model="agreement.section">
<field name="name">Article 1 - Objet du présent avenant</field>
<field name="sequence">1</field>
<field name="title">Article 1 - Objet du présent avenant</field>
<field name="content"><![CDATA[Le présent avenant a pour objet de définir la participation de {{ object.agreement_id.company_id.name }} au projet {{ object.agreement_id.parent_agreement_id.description or ''}}.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_avenant"/>
</record>
<record id="gn_agreement.convention_partenariat_avenant_section_art2" model="agreement.section">
<field name="name">Article 2 - Actions à mettre en oeuvre</field>
<field name="sequence">2</field>
<field name="title">Article 2 - Actions à mettre en oeuvre</field>
<field name="content"><![CDATA[{{ object.agreement_id.company_id.name}} s'engage à mettre en oeuvre les actions suivantes, dans le cadre défini par le dispositif {{ object.agreement_id.parent_agreement_id.name }}, entre le {{ object.agreement_id.start_date }} et le {{object.agreement_id.to_review_date}}:]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_avenant"/>
</record>
<record id="gn_agreement.convention_partenariat_avenant_section_art3" model="agreement.section">
<field name="name">Article 3 - Dispositions financières</field>
<field name="sequence">3</field>
<field name="title">Article 3 - Dispositions financières</field>
<field name="content"><![CDATA[
{{ object.agreement_id.partner_id.name }} établit ses factures qu'elle adresse à {{ object.agreement_id.company_id.name }}.<br/><br/>
{{ object.agreement_id.company_id.name }} s'engage à régler la facture émise, par chèque ou virement bancaire, dès réception et au plus tard avant la fin de la convention-cadre, soit le {{ object.agreement_id.end_date }}.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_avenant"/>
</record>
<record id="gn_agreement.convention_partenariat_avenant_section_art4" model="agreement.section">
<field name="name">Article 4 - Date d'effet</field>
<field name="sequence">4</field>
<field name="title">Article 4 - Date d'effet</field>
<field name="content"><![CDATA[
Le présent avenant prend effet au {{ object.agreement_id.start_date }}.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_avenant"/>
</record>
<record id="gn_agreement.convention_partenariat_avenant_section_art5" model="agreement.section">
<field name="name">Article 5 - Dispositions de la convention-cadre</field>
<field name="sequence">5</field>
<field name="title">Article 5 - Dispositions de la convention-cadre</field>
<field name="content"><![CDATA[
L'ensemble des dispositions de la convention-cadre {{ object.agreement_id.parent_agreement_id.code }}, non-contraire aux présentes, demeurent inchangées.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_avenant"/>
</record>
</data>
</odoo>

@ -1,135 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_agreement.layout_convention_partenariat_cadre" model="agreement">
<field name="is_template">true</field>
<field name="name">Modèle de convention-cadre de partenariat</field>
<field name="agreement_type_id" ref="gn_agreement.agreement_type_convention_partenariat"/>
<field name="agreement_subtype_id" ref="gn_agreement.agreement_subtype_convention_partenariat_cadre"/>
<field name="description">Convention-Cadre de partenariat pour le dispositif</field>
</record>
<record id="gn_agreement.recital_company" model="agreement.recital">
<field name="name">Notes sur le Porteur de Projet</field>
<field name="sequence">10</field>
<field name="content">{{object.agreement_id.company_id.partner_id.comment or ''}}</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_recital_partner" model="agreement.recital">
<field name="name">Notes sur le Partenaire</field>
<field name="sequence">11</field>
<field name="content">{{object.agreement_id.partner_id.comment or ''}}</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art1" model="agreement.section">
<field name="name">Article 1 - Objet de la Convention</field>
<field name="sequence">1</field>
<field name="title">Article 1 - Objet de la convention</field>
<field name="content"><![CDATA[ {{ object.agreement_id.description }} <br/><br/>
La présente convention a donc pour objet de définir les modalités de collaboration et de fonctionnement, entre les signataires pour la mise en oeuvre des formations pré-citées.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art2" model="agreement.section">
<field name="name">Article 2 - Principes généraux d'organisation et de mise en oeuvre</field>
<field name="sequence">2</field>
<field name="title">Article 2 - Principes généraux d'organisation et de mise en oeuvre</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art2_org_gen" model="agreement.clause">
<field name="name">Organisation générale du projet</field>
<field name="sequence">1</field>
<field name="content">L'organisation générale du projet est réalisée par {{object.section_id.agreement_id.company_id.name or ''}}</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art2"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art2_perimeter" model="agreement.clause">
<field name="name">Périmètre du partenariat</field>
<field name="sequence">2</field>
<field name="content">La mise en oeuvre du projet revient à {{object.section_id.agreement_id.company_id.name or ''}}</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art2"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art2_org_adm" model="agreement.clause">
<field name="name">Organisation administrative</field>
<field name="sequence">3</field>
<field name="content">La responsabilité administrative du projet revient à {{object.section_id.agreement_id.company_id.name or ''}}</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art2"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art2_com" model="agreement.clause">
<field name="name">Communication</field>
<field name="sequence">4</field>
<field name="content">La communication autour du projet revient à {{object.section_id.agreement_id.company_id.name or ''}}</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art2"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art3" model="agreement.section">
<field name="name">Article 3 Dispositions financières</field>
<field name="sequence">3</field>
<field name="title">Article 3 Dispositions financières</field>
<field name="content">
<![CDATA[
Afin de permettre la prise en charge des dépenses liées à la mise en œuvre des formations sus-mentionnées, les parties sengagent à répondre en partenariat à des appels à projet portés par des financeurs publics ou privés. <br/>
Quand les parties remportent un appel à projet, elles conviennent de se répartir les financements obtenus.<br/>
La répartition des financement et leur échéancier fait l'objet d'avenants à la présente convention.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art4" model="agreement.section">
<field name="name">Article 4 Justificatifs et conditions dévaluation</field>
<field name="sequence">4</field>
<field name="title">Article 4 Justificatifs et conditions dévaluation</field>
<field name="content">Les parties s'engagent à:</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art4_com_2mois" model="agreement.clause">
<field name="name">Communiquer tous les 2 mois</field>
<field name="sequence">1</field>
<field name="content">Communiquer tous les deux mois sur létat, les évolutions, les problématiques liées à la mise en place du projet commun. </field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art4"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art4_com_chgt" model="agreement.clause">
<field name="name">Communiquer tout changement</field>
<field name="sequence">2</field>
<field name="content">Communiquer tout changement dans loffre de formation ou la mise en place de celle-ci.</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art4"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art4_com_bilan" model="agreement.clause">
<field name="name">Établir un rapport annuel</field>
<field name="sequence">3</field>
<field name="content">
<![CDATA[
Établir un rapport annuel synthétisant le bilan des travaux menés sur la durée du partenariat et les perspectives que ceux-ci auront ouvertes. <br/>
Ce rapport fera notamment apparaître le bilan des actions de communication menées dans le cadre du partenariat.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
<field name="section_id" ref="gn_agreement.convention_partenariat_cadre_section_art4"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art5" model="agreement.section">
<field name="name">Article 5 - Responsabilité civile</field>
<field name="sequence">5</field>
<field name="title">Article 5 - Responsabilité civile</field>
<field name="content"><![CDATA[ Chaque partie déclare être assurée pour son activité propre.
<br/>
L' {{object.agreement_id.company_id.name }} déclare être assuré contre tous les risques professionnels et pour tous les objets lui appartenant ou appartenant à ses membres dans le cadre de ses activités et plus généralement contre tous les risques dont elle doit répondre en ce qui concerne leurs activités exercées dans les locaux propres ou mis à disposition, et en matière de responsabilité civile.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art6" model="agreement.section">
<field name="name">Article 6 Résiliation de la convention</field>
<field name="sequence">6</field>
<field name="title">Article 6 Résiliation de la convention</field>
<field name="content">En cas de non-respect par lune des parties de lune de ses obligations résultant de la présente convention, celle-ci pourra être résiliée de plein droit par lautre partie à lexpiration dun délai de prévenance raisonnable.</field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
<record id="gn_agreement.convention_partenariat_cadre_section_art7" model="agreement.section">
<field name="name">Article 7 - Durée de la convention</field>
<field name="sequence">7</field>
<field name="title">Article 7 - Durée de la convention</field>
<field name="content"><![CDATA[La présente convention est conclue par {{ object.agreement_id.company_id.name }} et {{ object.agreement_id.partner_id.name }} pour une durée de trois ans à compter de sa signature. Cette convention pourra être renouvelée par tacite reconduction.
<br/>
Chaque année, avant le démarrage des formations, les parties conviennent de déterminer si elles sont en mesure de couvrir les dépenses liées à la mise en œuvre des formations. <br/>
Dans laffirmative, les parties sengagent à signer une convention dapplication annuelle par formation. <br/>
Dans la négative, les parties conviennent de suspendre pour un an la signature de convention dapplication sans remettre en cause pour autant les principes généraux exprimés dans la présente convention.]]></field>
<field name="agreement_id" ref="gn_agreement.layout_convention_partenariat_cadre"/>
</record>
</data>
</odoo>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_agreement.report_layout" model="report.layout">
<field name="name">GN</field>
<field name="sequence">6</field>
<field name="view_id" ref="gn_agreement.external_layout_striped_footer"/>
</record>
</data>
</odoo>

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="gn_agreement.agreement_type_convention_partenariat" model="agreement.type">
<field name="name">Convention de partenariat</field>
<field name="domain">sale</field>
</record>
<record id="gn_agreement.agreement_subtype_convention_partenariat_cadre" model="agreement.subtype">
<field name="name">Convention-Cadre</field>
<field name="agreement_type_id" ref="gn_agreement.agreement_type_convention_partenariat"/>
</record>
<record id="gn_agreement.agreement_subtype_convention_partenariat_avenant" model="agreement.subtype">
<field name="name">Avenant</field>
<field name="agreement_type_id" ref="gn_agreement.agreement_type_convention_partenariat"/>
</record>
</data>
</odoo>

@ -1,156 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="gn_agreement.report_agreement_document" inherit_id="agreement_legal.report_agreement_document">
<xpath expr="//div[contains(@class, 'page') and .//table]" position="replace">
<div class="page">
<h1 style="font-size: 1.5rem; text-align: center; font-weight: bold"><span t-field="doc.code"/> : <t t-if="doc.template_id"><span t-field="doc.template_id.description"/></t><t t-else=""><span t-field="doc.name"/></t>
<t t-if="doc.parent_agreement_id"><span t-field="doc.parent_agreement_id.code"/></t></h1>
<t t-if="doc.parent_agreement_id">
<h1 style="font-size: 1.5rem; text-align: center; font-weight: bold"><span t-field="doc.parent_agreement_id.name"/></h1>
</t>
<div style="font-size: 1.5rem; text-align: center;" name="description">
<span t-field="doc.name"/>
</div>
<!--<h2>Parties</h2>-->
<div name="parties">
<t t-if="doc.use_parties_content">
<p t-field="doc.dynamic_parties"/>
</t>
<t t-else="">
<div class="my-3">
<h6 style="font-weight: bold">Entre</h6>
<div name="company_address">
<t t-set="a" t-value="doc.company_id.partner_id"/>
<div>
<p class="mb-0">
<span style="font-weight: bold;" t-field="a.name"/>,
<span t-field="a.partner_company_type_id"/>,
dont les activités sont situées au
<span t-field="a.street"/>,
<span t-field="a.zip"/>
<span t-field="a.city"/>
</p>
</div>
</div>
<div name="company_contact">
Representée par <span style="font-weight: bold" t-field="doc.company_contact_id.name"/>, en qualité de <span t-field="doc.company_contact_id.function"/>.
</div>
</div>
<div class="my-3">
<h6 style="font-weight: bold">Et</h6>
<div name="partner_address">
<t t-set="a" t-value="doc.partner_id"/>
<div>
<p class="mb-0">
<span style="font-weight: bold;" t-field="a.name"/>,
<span t-field="a.partner_company_type_id"/>, dont les activités sont situées au
<span t-field="a.street"/>,
<span t-field="a.zip"/>
</p>
</div>
<div name="company_contact">
Representée par <span style="font-weight: bold" t-field="doc.partner_contact_id.name"/>, en qualité de <span t-field="doc.partner_contact_id.function"/>.
</div>
</div>
</div>
<div style="text-align:right;" name="company_contact_denomination">
Ci-après dénommés <span style="font-weight: bold">"Les partenaires"</span> ou <span style="font-weight: bold">"Les parties"</span>
</div>
</t>
<!--
<t t-else="">
<h3>Company Information</h3>
<div name="company_address">
<address t-field="doc.company_id.partner_id" t-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: True}"/>
</div>
<div name="company_contact">
Represented by <span t-field="doc.company_contact_id.name"/>.
</div>
<h3>Partner Information</h3>
<div name="partner_address">
<address t-field="doc.partner_id" t-options="{&quot;widget&quot;: &quot;contact&quot;, &quot;fields&quot;: [&quot;address&quot;, &quot;name&quot;], &quot;no_marker&quot;: True}"/>
</div>
<div name="partner_contact">
Represented by <span t-field="doc.partner_contact_id.name"/>.
</div>
</t>
-->
</div>
<!--<h2>Agreement</h2>-->
<h3 style="font-size: 1.2rem;">Préambule</h3>
<table class="table table-condensed">
<tbody>
<tr>
<td>
<t t-foreach="doc.recital_ids" t-as="r">
<t t-if="r.title">
<h3 style="font-size: 1rem;" t-field="r.title"/>
</t>
<p t-field="r.dynamic_content"/>
</t>
</td>
</tr>
</tbody>
</table>
<p>C'est dans ce contexte que les parties se sont rapprochées afin de convenir de ce qui suit: </p>
<t t-foreach="doc.sections_ids" t-as="s">
<table class="table table-condensed">
<tbody class="section_tbody">
<tr>
<td>
<t t-if="s.title">
<h3 style="font-size: 1.2rem;" t-field="s.title"/>
</t>
<p t-field="s.dynamic_content"/>
<ol>
<li t-foreach="s.clauses_ids" t-as="c">
<t t-if="c.title">
<h4 style="font-size: 1rem;" t-field="c.title"/>
</t>
<p t-field="c.dynamic_content"/>
</li>
</ol>
</td>
</tr>
</tbody>
</table>
</t>
<t t-if="special_term">
<h2>Special Terms</h2>
<div name="special_term">
<p t-field="doc.dynamic_special_terms"/>
</div>
</t>
<!--<h2>Signatures</h2>-->
<table class="table table-condensed">
<theader>
<tr>
<th>Signatures</th>
<th></th>
</tr>
</theader>
<tbody class="section_tbody">
<tr>
<td>
<p style="font-weight: bold;">Pour <span t-field="doc.company_id.partner_id"/></p>
<p><span t-field="doc.company_contact_id.name"/>, <span t-field="doc.company_contact_id.function"/></p>
<p>le <span t-field="doc.company_signed_date"/><br/><br/><br/><br/><br/><br/><br/></p>
</td>
<td>
<p style="font-weight: bold;">Pour <span t-field="doc.partner_id"/>,</p>
<p><span t-field="doc.partner_contact_id.name"/>, <span t-field="doc.partner_contact_id.function"/></p>
<p>le <span t-field="doc.partner_signed_date"/><br/><br/><br/><br/><br/><br/><br/> </p>
</td>
</tr>
</tbody>
</table>
</div>
</xpath>
</template>
</odoo>

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="gn_agreement.external_layout_striped_footer" inherit_id="web.external_layout_striped">
<xpath expr="//div[contains(@class, 'text-muted') and span[contains(@class, 'page')] and span[contains(@class, 'topage')]]" position="replace">
<div t-if="report_type == 'pdf'" class="text-muted">
Page <span class="page"/> sur <span class="topage"/> -
<t t-if="doc and doc._name == 'agreement'"><span t-field="doc.code"/><span> : </span><t t-if="doc.template_id"><span t-field="doc.template_id.description"/></t> <t t-if="doc.parent_agreement_id"><span t-field="doc.parent_agreement_id.code"/></t><t t-else=""><span t-field="doc.name"/> </t></t><span t-if="display_name_in_footer" t-field="o.name"/>
</div>
</xpath>
</template>
</odoo>

@ -0,0 +1,44 @@
# GN Career
Module de gestion des fiches de postes et des évolutions de carrière
## How to install
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
2. Refresh list of Applications in UI
3. Search for 'gn_career' and click on **Install**
## Changelog
- v16.0.0.0.11 (2024/03/19):
- clean gn_contract.py code and create action_manage_career_button
- v16.0.0.0.10 (2024/03/18):
- constrains task selection in mission detail
- v16.0.0.0.9 (2024/03/17):
- call specific view for task detail
- v16.0.0.0.8 (2024/03/16):
- call specific view for tasks
- v16.0.0.0.7 (2024/03/16):
- call special form view for mission detail in career form
- v16.0.0.0.6 (2024/03/14):
- add task.detail model
- v16.0.0.0.5 (2024/03/10):
- create career from contract form
- v16.0.0.0.4 (2024/03/09):
- statusbar widget for career form
- v16.0.0.0.3 (2024/03/09):
- Filters on tasks
- v16.0.0.0.2 (2024/03/08):
- Add detail of Missions for each Career
- v16.0.0.0.1 (2024/03/02):
- Création du module
## Issues
- [] Add menuentries and views in Analyse section for missions and tasks (cf issue #12)
- [x] Workflow Career > Mission > Tasks needs debug
- [] Review total_percentage constraints
- [] Review domains and unicity for tasks and missions in models (constraints)
- [] clean and prune methods
- [] ensure cascade deletion
- [] create gn_career.career.tree view
- [] find some mechanisms to ensure career's state is updated. It is neccesary for contract's method action_manage_career.

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
#from . import models
from . import models

@ -0,0 +1,25 @@
{
"name": "France - Fiche de poste",
"version": "16.0.0.0.11",
"category": "HR",
"summary": "Configuration de la fiche de poste et de son évolution conventionnelle",
"author": "Le Garage Numérique",
"maintainers": ["makayabou"],
"website": "https://odoo.legaragenumerique.fr",
"depends": [
"hr",
"hr_contract",
"l10n_fr_oca",
"gn_cc",
"gn_contract",
],
"data": [
"views/gn_career_mission_detail.xml",
"views/gn_career_task_detail.xml",
"views/gn_career.xml",
"views/gn_contract.xml",
"data/gn_career_menus.xml",
"security/ir.model.access.csv",
],
"license": "LGPL-3",
}

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<menuitem
id="gn_career.careers_menu"
name="Fiches de poste"
parent="hr.menu_hr_employee_payroll"
sequence="300"
action="gn_career.careers_configuration"
groups="hr.group_hr_manager"/>
<menuitem
id="gn_career.career_configuration_menu_group"
name="Analyse du poste"
parent="hr.menu_human_resources_configuration"
sequence="300"
groups="hr.group_hr_manager"/>
<menuitem
id="gn_career.missions_configuration_menu"
name="Missions"
parent="gn_career.career_configuration_menu_group"
sequence="100"
action="gn_career.missions_configuration"
groups="hr.group_hr_manager"/>
<menuitem
id="gn_career.tasks_configuration_menu"
name="Tâches"
parent="gn_career.career_configuration_menu_group"
sequence="200"
action="gn_career.tasks_configuration"
groups="hr.group_hr_manager"/>
</odoo>

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import gn_contract
from . import gn_career
from . import gn_mission

@ -0,0 +1,254 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
class GnCareer(models.Model):
_name = "gn_career.career"
_description = "Analyse du poste"
_order = 'start_date'
name = fields.Char("Nom", compute='_compute_name')
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
contract_id = fields.Many2one('hr.contract', string="Contrat ou Avenant associé", ondelete='cascade')
employee_id = fields.Many2one(string="Employee", related='contract_id.employee_id', readonly=True, store=True)
associated_careers_count = fields.Integer(compute='_compute_associated_careers_count', store=True)
state = fields.Selection([
('draft', 'Brouillon'),
('wait_manager_approval', "En attente de validation du manager"),
('wait_director_approval', "En attente de validation par la direction"),
('wait_employee_approval', "En attente de signature par le salarié"),
('ready', 'Prête'),
('active', 'Active'),
], string="État", default='draft', required=True)
mission_ids = fields.Many2many('gn_career.mission', 'career_ids', compute='_compute_mission_ids', string="Missions effectuées", store=True)
mission_detail_ids = fields.One2many('gn_career.mission.detail', 'career_id', string="Détail de la mission")
#total_percentage = fields.Float(compute='_compute_total_percentage', string="Total Percentage", store=True)
#@api.depends('mission_detail_ids.percentage')
def _compute_total_percentage(self):
_logger.warning("Enter in _compute_total_percentage")
for record in self:
record.total_percentage = sum(mission.percentage for mission in record.mission_detail_ids)
#@api.constrains('total_percentage')
def _check_total_percentage(self):
for record in self:
_logger.warning("in career._check_total_percentage, 'bypass_total_percentage_check' in context: %s", self._context.get('bypass_total_percentage_check'))
if 'bypass_total_percentage_check' in self._context:
return
if record.total_percentage != 100:
raise ValidationError("Le pourcentage total des missions au sein de la fiche de poste doit atteindre 100%. Veuillez ajuster la répartition de la mission.")
@api.depends('mission_detail_ids.mission_id')
def _compute_mission_ids(self):
for record in self:
mission_ids_set = set()
for mission_detail in record.mission_detail_ids:
mission_ids_set.add(mission_detail.mission_id.id)
record.mission_ids = [(6, 0, list(mission_ids_set))]
@api.depends('start_date')
def _compute_name(self):
for record in self:
if record.start_date:
# Use an f-string for formatting
record.name = f"Fiche de poste du {record.start_date}"
else:
# Provide a default or handle the case where start_date isn't set
record.name = "Fiche de poste sans date"
def action_open_associated_careers(self):
self.ensure_one()
return self.contract_id.open_associated_careers()
@api.depends('contract_id.related_contract_ids')
def _compute_associated_careers_count(self):
for record in self:
record.associated_careers_count = record.contract_id.len_related_contracts
def action_define_career(self):
_logger.warning("Enter in action_define_career in career's form ( id %s)", self.id)
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Modification de la fiche de poste',
'view_mode': 'form',
'res_model': 'hr.contract',
'res_id': new_contract.id,
'target': 'current',
'context': {
'default_contract_id': self.contract_id.id,
'default_employee_id': self.employee_id.id,
'default_start_date': self.start_date,
},
}
def action_define_mission_detail(self):
_logger.warning("Enter in action _define_mission_detail, which sends 'bypass_total_percentage_check' in context")
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': 'Ajouter une mission',
'view_mode': 'form',
'res_model': 'gn_career.mission.detail',
'target': 'new',
'context': {
'default_career_id': self.id,
'default_percentage': 100,
},
}
class GnCareerMissionDetail(models.Model):
_name = 'gn_career.mission.detail'
_description = "Détails d'une mission pour une fiche de poste"
name = fields.Char(related='mission_id.name')
career_id = fields.Many2one('gn_career.career', string="Fiche de poste")
mission_id = fields.Many2one('gn_career.mission', string="Mission")
exclude_mission_ids = fields.Many2many('gn_career.mission', compute='_compute_exclude_mission_ids')
percentage = fields.Integer("Temps occupé par la mission en % de la fiche de poste", default="100")
tasks_percentage = fields.Float(compute='_compute_tasks_percentage', string="Répartition des tâches", store=True)
task_ids = fields.One2many('gn_career.task.detail', 'mission_detail_id', string="Tâches incluses dans la mission")
#related_mission_task_ids = fields.Many2many('gn_career.task', compute='_compute_related_mission_task_ids')
lasting_task_ids = fields.Many2many('gn_career.task', compute='_compute_lasting_mission_task_ids')
employee_id = fields.Many2one(string="Employee", related='career_id.employee_id', readonly=True)
# A special field used to update task_ids domain in mission.detail form
# With context we can exclude some of the tasks:
# - see "other tasks" of the mission in task form view
# - select only not yet selected task_id for task_ids tree view in mission form view
# _compute_lasting_mission_task_ids:
# To select tasks available in a missionDetail, ie:
# - part of mission_id.possible_task_ids
# - AND not already select in current MissionDetail
# It is used in Mission Detail Form View:
# we have a tree view for the task_detail_ids field,
# when adding a new task_detail_ids line, we need to be
# restricted in choice among the results of this function.
@api.depends('mission_id.possible_task_ids', 'task_ids')
def _compute_lasting_mission_task_ids(self):
for record in self:
if record.mission_id:
possible_tasks_ids = record.mission_id.possible_task_ids.ids
included_tasks_ids = record.task_ids.mapped('task_id').ids
lasting_tasks_ids = [task_id for task_id in possible_tasks_ids if task_id not in included_tasks_ids]
record.lasting_task_ids = [(6, 0, lasting_tasks_ids)]
#A special field used for remove already used tasks (for task.detail tree view in mission.detail form view)
# and also in many2many tag in task form display, using context to exclude
# the actual task in the mission, so we have a view on "other tasks"
# @api.depends('task_ids', 'task_ids.task_id')
# def _compute_related_mission_task_ids(self):
# _logger.warning("Enter in _compute_task_ids_display")
# current_task_id = self._context.get('exclude_task_id')
# for record in self:
# if current_task_id:
# record.task_ids_display = record.task_ids.filtered(lambda t: t.id != current_task_id)
# else:
# record.task_ids_display = record.task_ids
#@api.depends('task_ids', 'task_ids.percentage')
def _compute_tasks_percentage(self):
for record in self:
record.tasks_percentage = sum(task.percentage for task in record.task_ids)
#@api.constrains('tasks_percentage')
def _check_total_percentage(self):
for record in self:
if record.tasks_percentage != 100:
raise ValidationError("Le pourcentage total des tâches au sein de la mission doit atteindre 100%. Veuillez ajuster la répartition de la mission.")
@api.depends('career_id.mission_detail_ids.mission_id')
def _compute_exclude_mission_ids(self):
for rec in self:
existing_mission_ids = rec.career_id.mission_detail_ids.mapped('mission_id.id')
rec.exclude_mission_ids = [(6, 0, existing_mission_ids)]
# Action to open task form in mission detail view
# it opens task_detail form if there is still tasks available in the mission
# otherwise it opens the task creation form, associated with mission as possible_task_ids
def check_and_open_task_detail_form(self):
self.ensure_one()
show_warning = False
all_possible_tasks = self.mission_id.possible_task_ids
included_tasks = self.task_ids.mapped('task_id')
available_tasks_ids = all_possible_tasks.filtered(lambda t: t not in included_tasks).ids
_logger.warning("Available tasks: %s", available_tasks_ids)
if not available_tasks_ids:
show_warning = True
if show_warning == True:
action = {
'type': 'ir.actions.act_window',
'name': "Nouvelle tâche pour la mission",
'res_model': 'gn_career.task',
'view_mode': 'form',
'target': 'new',
'context': dict(self.env.context, default_possible_mission_ids=[(4, self.mission_id.id)]),
'view_id': self.env.ref('gn_career.view_gn_career_task_form_in_mission_form').id,
}
else:
action = {
'type': 'ir.actions.act_window',
'name': "Détail de la tâche",
'res_model': 'gn_career.task.detail',
'view_mode': 'form',
'target': 'new',
'context': dict(self.env.context, default_mission_detail_id=self.id, available_tasks_ids=available_tasks_ids),
'view_id': self.env.ref('gn_career.gn_career_task_detail_form_view_in_mission_detail_view').id,
}
return action
class GnCareerTaskDetail(models.Model):
_name = 'gn_career.task.detail'
_description = "Détails d'une tâche"
name = fields.Char(string="Nom", compute='_compute_task_id_name')
mission_detail_id = fields.Many2one('gn_career.mission.detail', string="Mission de la fiche de poste", ondelete='cascade')
career_id = fields.Many2one('gn_career.career', related='mission_detail_id.career_id', string="Fiche de poste", readonly=True)
employee_id = fields.Many2one(string="Employee", related='career_id.employee_id', readonly=True)
#mission_id = fields.Many2one('gn_career.mission', related='mission_detail_id.mission_id', string="")
task_id = fields.Many2one('gn_career.task', string="Tâche associée")
percentage = fields.Integer("Temps occupé par la tâche en % de la mission", default="100")
other_tasks_in_mission = fields.Many2many('gn_career.task.detail', compute='_compute_other_tasks_in_mission')
@api.depends('task_id', 'percentage')
def _compute_task_id_name(self):
for record in self:
task_name = record.task_id.name or "Tâche indéfinie"
if record.percentage:
record.name = "{} {:.2f}%".format(task_name, record.percentage)
else:
record.name = task_name
@api.depends('mission_detail_id.task_ids')
def _compute_other_tasks_in_mission(self):
for record in self:
record.other_tasks_in_mission = [(6, 0, record.mission_detail_id.task_ids.filtered(lambda t: t.id != record.id ).ids)]
#all_possible_tasks = record.mission_detail_id.mission_id.possible_task_ids
#included_tasks = record.mission_detail_id.task_ids.mapped('task_id')
#record.other_tasks_in_mission = [(6, 0, all_possible_tasks.filtered(lambda t: t not in included_tasks).ids)]
#A special field used to display "other tasks of the mission" in task.detail form view
# @api.depends('mission_detail_id.task_ids', 'mission_detail_id.task_ids.task_id')
# def _compute_other_tasks_in_mission(self):
# current_task_id = self._context.get('exclude_task_id') or False
# for record in self:
# record.other_tasks_in_mission = record.mission_detail_id.task_ids.filtered(lambda t: t.task_id != current_task_id)

@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from datetime import datetime
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
class GnCareerContract(models.Model):
_inherit = "hr.contract"
career_ids = fields.One2many("gn_career.career", 'contract_id',
string="Analyses du poste")
def open_associated_careers(self):
_logger.warning("Open Active Career called on records: %s", self)
self.ensure_one() # Ensure that the method is called on a single record
associated_contract_ids = [contract.id for contract in self.related_contract_ids]
action = {
'type': 'ir.actions.act_window',
"views": [[False, "tree"], [False, "form"]],
'res_model': 'gn_career.career',
'target': 'current',
'domain': [('contract_id', 'in', associated_contract_ids)],
}
return action
def action_manage_career(self):
self.ensure_one()
if self.career_ids:
active_careers = self.career_ids.filtered(lambda c: c.start_date <= fields.Date.today() and c.state != 'draft')
latest_career = active_careers.sorted(key=lambda c: c.start_date, reverse=True)[:1]
if latest_career:
action = {
'type': 'ir.actions.act_window',
"views": [[False, "form"]],
'res_model': 'gn_career.career',
'res_id': latest_career.id,
'target': 'current',
}
else:
latest_draft_career = self.career_ids.filtered(lambda c: c.state == 'draft').sorted(key=lambda c: c.start_date, reverse=True)[:1]
if latest_draft_career:
action = {
'type': 'ir.actions.act_window',
"views": [[False, "form"]],
'res_model': 'gn_career.career',
'res_id': latest_draft_career.id,
'target': 'current',
}
else:
raise UserError("Il semble y avoir un problème avec les fiches de poste associées au contrat. Accédez au menu 'Employés > Fiches de Poste' pour accéder manuellement à la fiche de poste")
else:
action = {
'type': 'ir.actions.act_window',
'name': 'Définir la fiche de poste',
'view_mode': 'form',
'res_model': 'gn_career.career',
'context': {
'default_contract_id': self.id,
'default_employee_id': self.employee_id.id,
'default_start_date': self.date_validity_start or self.date_start,
},
'target': 'current',
}
return action
# def action_open_career(self):
# self.ensure_one()
# if self.career_ids:
# valid_careers = self.career_ids.filtered(lambda c: c.start_date and c.start_date <= fields.Date.today())
# latest_career = valid_careers.sorted(key=lambda c: c.start_date, reverse=True)[:1]
# if latest_career:
# action = {
# 'type': 'ir.actions.act_window',
# "views": [[False, "form"]],
# 'res_model': 'gn_career.career',
# 'res_id': latest_career.id,
# 'target': 'current',
# }
# return action
# def action_define_career(self):
# _logger.warning("Enter in action_define_career for contracts'id %s", self.id)
# self.ensure_one()
# action = {
# 'type': 'ir.actions.act_window',
# 'name': 'Définir la fiche de poste',
# 'view_mode': 'form',
# 'res_model': 'gn_career.career',
# 'context': {
# 'default_contract_id': self.id,
# 'default_employee_id': self.employee_id.id,
# 'default_start_date': self.date_validity_start or self.date_start,
# },
# 'target': 'current',
# }
# return action

@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
# Part of Odoo 16 CE. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
import logging
_logger = logging.getLogger(__name__)
class GnCareerTask(models.Model):
_name = 'gn_career.task'
_description = "Tâches réalisées au cours de la mission"
name = fields.Char("Nom de la tâche")
description = fields.Text("Description de la tâche")
possible_mission_ids = fields.Many2many('gn_career.mission', string="Missions pouvant inclure cette tâche")
effective_mission_ids = fields.Many2many('gn_career.mission.detail', compute='_compute_effective_mission_ids', relation="effective_missions", string="Missions définies impliquant cette tâche")
active_mission_ids = fields.Many2many('gn_career.mission.detail', relation="active_missions", string="Missions actives incluant cette tâche")
task_detail_ids = fields.One2many('gn_career.task.detail', 'task_id', string="Répartition des tâches au sein de la mission")
@api.onchange('task_detail_ids')
def _compute_effective_mission_ids(self):
_logger.warning("Enter in _compute_effective_mission_ids")
for task in self:
effective_mission_ids_set = set()
for task_detail in task.task_detail_ids:
if task_detail.mission_detail_id:
effective_mission_ids_set.add(task_detail.mission_detail_id.id)
task.effective_mission_ids = [(6, 0, list(effective_mission_ids_set))]
class GnCareerMission(models.Model):
_name = 'gn_career.mission'
_description = "Missions prévues dans le contrat"
name = fields.Char("Nom de la mission")
description = fields.Text("Description de la mission")
possible_task_ids = fields.Many2many('gn_career.task', string="Tâches pouvant être incluses dans la mission")
career_ids = fields.Many2many('gn_career.career', 'mission_ids', compute='_compute_career_ids', string="Fiches de poste incluant cette mission", store=True)
mission_detail_ids = fields.One2many('gn_career.mission.detail', 'mission_id', string="Répartition des missions au sein du poste")
@api.depends('mission_detail_ids.career_id')
def _compute_career_ids(self):
_logger.warning("Enter in _compute_career_ids")
for mission in self:
career_ids = mission.mapped('mission_detail_ids.career_id').ids
mission.career_ids = [(6, 0, career_ids)]

@ -0,0 +1,11 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_gn_career_career_user,gn_career_career_user,model_gn_career_career,base.group_user,1,0,0,0
access_gn_career_career_admin,gn_career_career_admin,model_gn_career_career,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_career_task_user,gn_career_task_user,model_gn_career_task,base.group_user,1,0,0,0
access_gn_career_task_admin,gn_career_task_admin,model_gn_career_task,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_career_mission_user,gn_career_mission_user,model_gn_career_mission,base.group_user,1,0,0,0
access_gn_career_mission_admin,gn_career_mission_admin,model_gn_career_mission,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_career_mission_detail_user,gn_career_mission_detail_user,model_gn_career_mission_detail,base.group_user,1,0,0,0
access_gn_career_mission_detail_admin,gn_career_mission_detail_admin,model_gn_career_mission_detail,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_career_task_detail_user,gn_career_task_detail_user,model_gn_career_task_detail,base.group_user,1,0,0,0
access_gn_career_task_detail_admin,gn_career_task_detail_admin,model_gn_career_task_detail,hr_contract.group_hr_contract_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_gn_career_career_user gn_career_career_user model_gn_career_career base.group_user 1 0 0 0
3 access_gn_career_career_admin gn_career_career_admin model_gn_career_career hr_contract.group_hr_contract_manager 1 1 1 1
4 access_gn_career_task_user gn_career_task_user model_gn_career_task base.group_user 1 0 0 0
5 access_gn_career_task_admin gn_career_task_admin model_gn_career_task hr_contract.group_hr_contract_manager 1 1 1 1
6 access_gn_career_mission_user gn_career_mission_user model_gn_career_mission base.group_user 1 0 0 0
7 access_gn_career_mission_admin gn_career_mission_admin model_gn_career_mission hr_contract.group_hr_contract_manager 1 1 1 1
8 access_gn_career_mission_detail_user gn_career_mission_detail_user model_gn_career_mission_detail base.group_user 1 0 0 0
9 access_gn_career_mission_detail_admin gn_career_mission_detail_admin model_gn_career_mission_detail hr_contract.group_hr_contract_manager 1 1 1 1
10 access_gn_career_task_detail_user gn_career_task_detail_user model_gn_career_task_detail base.group_user 1 0 0 0
11 access_gn_career_task_detail_admin gn_career_task_detail_admin model_gn_career_task_detail hr_contract.group_hr_contract_manager 1 1 1 1

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_gn_career_mission_form" model="ir.ui.view">
<field name="name">gn_career.mission.form</field>
<field name="model">gn_career.mission</field>
<field name="arch" type="xml">
<form string="Mission">
<field name="name" string="Nom"/>
<field name="description" string="Description"/>
<field name="possible_task_ids" string="Tâches possibles"
context="{'tree_view_ref':'gn_career.view_gn_career_task_tree',
'form_view_ref': 'gn_career.view_gn_career_task_form_in_mission_form'}"/>
<field name="career_ids" string="Fiches de poste associées"/>
<field name="mission_detail_ids"
context="{'tree_view_ref':'gn_career.gn_career_mission_detail_tree_view'}"
string="Organisation de la mission pour chaque employé"/>
</form>
</field>
</record>
<record id="action_define_mission_detail_xml" model="ir.actions.act_window">
<field name="name">Ajouter une mission</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">gn_career.mission.detail</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{'default_career_id': active_id}</field>
<field name="view_id" ref='gn_career.gn_career_mission_detail_form_in_career_view'/>
</record>
<record id="gn_career.missions_configuration" model="ir.actions.act_window">
<field name="name">Missions</field>
<field name="res_model">gn_career.mission</field>
<field name="view_mode">tree,form</field>
</record>
<record id="view_gn_career_task_tree" model="ir.ui.view">
<field name="name">gn_career.task.tree</field>
<field name="model">gn_career.task</field>
<field name="arch" type="xml">
<tree string="Tâche">
<!-- Other fields -->
<field name="name"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="view_gn_career_task_form" model="ir.ui.view">
<field name="name">gn_career.task.form</field>
<field name="model">gn_career.task</field>
<field name="arch" type="xml">
<form string="Tâche">
<!-- Other fields -->
<field name="name"/>
<field name="description"/>
<field name="possible_mission_ids"/>
<field name="effective_mission_ids"
context="{'exclude_task_id': active_id, 'tree_view_ref':'gn_career.gn_career_mission_detail_tree_view'}"/>
</form>
</field>
</record>
<record id="view_gn_career_task_form_in_mission_form" model="ir.ui.view">
<field name="name">gn_career.task.form.mission.form</field>
<field name="model">gn_career.task</field>
<field name="arch" type="xml">
<form string="Tâche">
<!-- Other fields -->
<field name="name" string="Nom"/>
<field name="description" string="Description"/>
</form>
</field>
</record>
<record id="view_gn_career_career_form" model="ir.ui.view">
<field name="name">gn_career.career.form</field>
<field name="model">gn_career.career</field>
<field name="arch" type="xml">
<form string="Tâche">
<header>
<button name="action_define_career" type="object"
groups="hr_contract.group_hr_contract_manager"
string="Modifier la fiche de poste" class="oe_highlight"
attrs="{'invisible': [('state', '=', 'active')]}"/>
<field name="state" groups="!hr_contract.group_hr_contract_manager" widget="statusbar"/>
<field name="state" groups="hr_contract.group_hr_contract_manager" widget="statusbar" options="{'clickable': '1'}"/>
</header>
<sheet>
<div name='button_box' class="oe_button_box">
<button name="action_open_associated_careers" type='object' class="oe_stat_button" icon="fa-money" groups="hr_contract.group_hr_contract_manager">
<field name="associated_careers_count" widget="statinfo" string="Fiche de poste"/>
</button>
</div>
<group name="top_info">
<field name="name"/>
<field name="start_date"/>
<field name="employee_id" readonly="1"/>
<field name="contract_id" readonly="1"/>
<field name="mission_ids" invisible="1"/>
<field name="mission_detail_ids" readonly="0"
context="{'tree_view_ref':'gn_career.gn_career_mission_detail_tree_view_in_career_view',
'form_view_ref': 'gn_career.gn_career_mission_detail_form_in_career_view'}"/>
<!--options="{'no_create_edit': True, 'no_create': True,
'views': {
'tree': [('ref', 'gn_career.gn_career_mission_detail_tree_view_in_career_view')],
'form': [('ref', 'gn_career.gn_career_mission_detail_form_in_career_view')]
}}"/>-->
<!--<field name="total_percentage"/> -->
<button name="%(action_define_mission_detail_xml)d" type='action' class="oe_highlight"
groups="hr_contract.group_hr_contract_manager"
string="Ajouter une mission (xml)"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="gn_career.tasks_configuration" model="ir.actions.act_window">
<field name="name">Tâches</field>
<field name="res_model">gn_career.task</field>
<field name="view_mode">tree,form</field>
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('view_gn_career_task_tree')})]"/>
</record>
<record id="gn_career.careers_configuration" model="ir.actions.act_window">
<field name="name">Analyses de poste</field>
<field name="res_model">gn_career.career</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_career_mission_detail_form_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.form</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<form string="Détail des missions">
<sheet string="Détail de la mission">
<group>
<group>
<field name="employee_id"/>
<field name="career_id"/>
<field name="mission_id"/>
<field name="related_mission_task_ids" invisible="1"/>
<field name="task_ids" domain="[('id', 'in', related_mission_task_ids)]"/>
<field name="percentage"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="gn_career_mission_detail_tree_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.tree</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<tree string="Mission Details">
<field name="mission_id"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_mission_detail_tree_view_in_mission_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.tree.in_mission_view</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<tree string="Mission Details">
<field name="employee_id"/>
<field name="mission_id"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_mission_detail_tree_view_in_task_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.tree.in_task_view</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<tree string="Mission Details">
<field name="employee_id"/>
<field name="mission_id"/>
<field name="task_ids_display" widget="many2many_tags" string="Autres tâches dans la mission"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_mission_detail_tree_view_in_career_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.tree.in_career_view</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<tree string="Mission Details">
<field name="mission_id"/>
<field name="task_ids" widget="many2many_tags" string="Tâches"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_mission_detail_action" model="ir.actions.act_window">
<field name="name">Détails des missions</field>
<field name="res_model">gn_career.mission.detail</field>
<field name="view_mode">tree,form</field>
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('gn_career_mission_detail_tree_view')}), (0, 0, {'view_mode': 'form', 'view_id': ref('gn_career_mission_detail_form_view')})]"/>
</record>
</odoo>

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_career_mission_detail_form_in_career_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.form.career</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<form string="Détail des missions">
<sheet string="Détail de la mission">
<group>
<group>
<field name="career_id" invisible="1"/>
<field name="exclude_mission_ids" invisible="1"/>
<field name="mission_id" domain="[('id', 'not in', exclude_mission_ids)]"/>
<field name="percentage"/>
<field name="task_ids"
options="{'no_create_edit': True, 'no_create': True,
'views': {
'tree': [('ref', 'gn_career.gn_career_task_detail_tree_view_in_mission_detail_view')],
'form': [('ref', 'gn_career.gn_career_task_detail_form_view')]
}}"
context="{'default_mission_detail_id': active_id}"
widget="many2many"/>
<button name="check_and_open_task_detail_form" type="object" string="Add Task Detail" class="oe_highlight"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="gn_career_mission_detail_tree_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.tree</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<tree string="Mission Details">
<field name="employee_id"/>
<field name="mission_id"/>
<field name="task_ids" widget="many2many_tags" string="Tâches dans la mission"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_mission_detail_tree_view_in_career_view" model="ir.ui.view">
<field name="name">gn_career.mission.detail.tree.in_career_view</field>
<field name="model">gn_career.mission.detail</field>
<field name="arch" type="xml">
<tree string="Mission Details">
<field name="mission_id" readonly="1"/>
<field name="task_ids" widget="many2many_tags" string="Liste des Tâches" readonly="1"/>
<field name="percentage" readonly="1"/>
</tree>
</field>
</record>
<record id="gn_career_mission_detail_action" model="ir.actions.act_window">
<field name="name">Détails des missions</field>
<field name="res_model">gn_career.mission.detail</field>
<field name="view_mode">tree,form</field>
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'tree', 'view_id': ref('gn_career.gn_career_mission_detail_tree_view')}), (0, 0, {'view_mode': 'form', 'view_id': ref('gn_career.gn_career_mission_detail_form_in_career_view')})]"/>
</record>
</odoo>

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_career_task_detail_form_view" model="ir.ui.view">
<field name="name">gn_career.task.detail.form</field>
<field name="model">gn_career.task.detail</field>
<field name="arch" type="xml">
<form string="Détail des tâches">
<sheet string="Détail des tâches">
<group>
<group>
<field name="employee_id"/>
<field name="career_id"/>
<field name="mission_detail_id"/>
<field name="percentage"/>
<field name="other_tasks_in_mission" widget="many2many_tags"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="gn_career_task_detail_form_view_in_mission_detail_view" model="ir.ui.view">
<field name="name">gn_career.task.detail.form.in_mission_detail_view</field>
<field name="model">gn_career.task.detail</field>
<field name="arch" type="xml">
<form string="Répartition des tâches">
<field name="other_tasks_in_mission" invisible="1"/>
<field name="task_id" domain="[('id', 'in', context.get('available_tasks_ids', []))]"/>
<field name="percentage"/>
</form>
</field>
</record>
<record id="gn_career_task_detail_tree_view" model="ir.ui.view">
<field name="name">gn_career.task.detail.tree</field>
<field name="model">gn_career.task.detail</field>
<field name="arch" type="xml">
<tree string="Détails de la tâche">
<field name="mission_detail_id"/>
<field name="task_id"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_task_detail_tree_view_in_mission_detail_view" model="ir.ui.view">
<field name="name">gn_career.task.detail.tree.in_mission_detail_view</field>
<field name="model">gn_career.task.detail</field>
<field name="arch" type="xml">
<tree string="Répartition des tâches">
<field name="other_tasks_in_mission" invisible="1"/>
<field name="task_id" domain="[('id', 'in', other_tasks_in_mission)]"/>
<field name="percentage"/>
</tree>
</field>
</record>
<record id="gn_career_task_detail_action" model="ir.actions.act_window">
<field name="name">Détails des tâches</field>
<field name="res_model">gn_career.task.detail</field>
<field name="view_mode">form,tree</field>
<field name="view_ids" eval="[(5, 0, 0), (0, 0, {'view_mode': 'form', 'view_id': ref('gn_career_task_detail_form_view')}), (0, 0, {'view_mode': 'tree', 'view_id': ref('gn_career_task_detail_tree_view')})]"/>
</record>
</odoo>

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_career.hr_contract" model="ir.ui.view">
<field name="name">hr.contract.form.gncareer</field>
<field name="model">hr.contract</field>
<field name="priority" eval="30"/>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<!--<field name="career_ids" invisible="1"/>-->
<button name="action_manage_career" type="object" class="oe_stat_button"
icon="fa-pencil-square-o" groups="hr_contract.group_hr_contract_manager"
string="Gérer la fiche de poste"/>
<!--<button name="action_open_career" type='object' class="oe_stat_button"
icon="fa-money" groups="hr_contract.group_hr_contract_manager"
attrs="{'invisible': [('career_ids', '=', [])]}"
string="Fiche de poste active">
</button>
<button name="action_define_career" type="object" class="oe_stat_button"
icon="fa-pencil" groups="hr_contract.group_hr_contract_manager"
attrs="{'invisible': [('career_ids', '!=', [])]}"
string="Définir la fiche de poste">
</button> -->
</xpath>
</field>
</record>
</odoo>

@ -0,0 +1,24 @@
# GN CC
Module de gestion des conventions collectives par Le Garage Numérique.
## How to install
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
2. Refresh list of Applications in UI
3. Search for 'gn_cc' and click on **Install**
## Changelog
- v16.0.0.0.2 (2024/03/01):
- Add 'l10n_fr_oca' as dependency
- Add Readme
- v16.0.0.0.1 (2024/02/29):
- Creation of module
# Feature requests
- [] Add Convention Collective for "Organisme de Formation"
- [] Add Convention Collective for "Centres Sociaux"
- [] Add Convention Collective for "Prévention Spécialisée"

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

@ -0,0 +1,21 @@
{
"name": "France - Conventions Collective",
"version": "16.0.0.0.2",
"category": "France",
"summary": "Configuration des accords de branche pour le France: Conventions Collectives",
"author": "Le Garage Numérique",
"maintainers": ["makayabou"],
"website": "https://odoo.legaragenumerique.fr",
"depends": [
"hr",
"hr_contract",
"l10n_fr_oca",
],
"data": [
"views/gn_cc_company.xml",
"views/gn_cc_cc.xml",
"data/gn_cc_cc.xml",
"security/ir.model.access.csv"
],
"license": "LGPL-3",
}

@ -0,0 +1,359 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="gn_cc.cc_3442" model="gn_cc.cc">
<field name="name">Convention Eclat</field>
<field name="idcc">3442</field>
<field name="groups">A;B;C;D;E;F;G;H;I;J;K</field>
<field name="criterias">Autonomie;Responsabilité;Technicité</field>
<field name="point_cat">V1;V2</field>
</record>
<record id="gn_cc.cc_3442_A_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">A</field>
<field name="coeff_min" eval="247"/>
</record>
<record id="gn_cc.cc_3442_A_coeff_20220501" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-05-01</field>
<field name="group">A</field>
<field name="coeff_min" eval="250"/>
</record>
<record id="gn_cc.cc_3442_A_coeff_20231101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2023-11-01</field>
<field name="group">A</field>
<field name="coeff_min" eval="257"/>
</record>
<record id="gn_cc.cc_3442_A_autonomie_2021" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2021-01-01</field>
<field name="group">A</field>
<field name="criteria">Autonomie</field>
<field name="description">Ancienne définition du groupe A autonomie.</field>
</record>
<record id="gn_cc.cc_3442_A_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">A</field>
<field name="criteria">Autonomie</field>
<field name="description">Les consignes et processus sont mis en œuvre. Le contrôle est permanent.</field>
</record>
<record id="gn_cc.cc_3442_A_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">A</field>
<field name="criteria">Responsabilité</field>
<field name="description">Responsabilité des biens (matériel, outils, salle éventuellement) confiés au salarié et/ou des personnes extérieures dont le salarié a la charge (public accueilli). Peut avoir la gestion dun fond de caisse.</field>
</record>
<record id="gn_cc.cc_3442_A_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">A</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences élémentaires impliquant la mise en œuvre de procédures simples</field>
</record>
<record id="gn_cc.cc_3442_B_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">B</field>
<field name="coeff_min" eval="257"/>
</record>
<record id="gn_cc.cc_3442_B_coeff_20220501" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-05-01</field>
<field name="group">B</field>
<field name="coeff_min" eval="260"/>
</record>
<record id="gn_cc.cc_3442_B_coeff_20240101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="group">B</field>
<field name="coeff_min" eval="265"/>
</record>
<record id="gn_cc.cc_3442_B_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="group">B</field>
<field name="criteria">Autonomie</field>
<field name="description">Les consignes et processus sont mis en œuvre. Le contrôle est permanent.</field>
</record>
<record id="gn_cc.cc_3442_B_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="group">B</field>
<field name="criteria">Responsabilité</field>
<field name="description">Responsabilité des biens (matériel, outils, salle éventuellement) confiés au salarié et/ou des personnes extérieures dont le salarié a la charge (public accueilli). Peut avoir la gestion dun fond de caisse.</field>
</record>
<record id="gn_cc.cc_3442_B_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="group">B</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences professionnelles pratiques dans le cadre dune activité généralement simple.</field>
</record>
<record id="gn_cc.cc_3442_C_coeff_20240101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="group">C</field>
<field name="coeff_min" eval="285"/>
</record>
<record id="gn_cc.cc_3442_C_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">C</field>
<field name="criteria">Autonomie</field>
<field name="description">Le salarié peut interpréter et adapter les processus. Le contrôle est périodique.</field>
</record>
<record id="gn_cc.cc_3442_C_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">C</field>
<field name="criteria">Responsabilité</field>
<field name="description">Responsabilité dun budget prescrit. Peut assurer la coordination/le conseil dautres salariés</field>
</record>
<record id="gn_cc.cc_3442_C_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">C</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences techniques et relationnelles nécessaires à la maîtrise dun domaine dactivité</field>
</record>
<record id="gn_cc.cc_3442_D_coeff_20240101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="group">D</field>
<field name="coeff_min" eval="305"/>
</record>
<record id="gn_cc.cc_3442_D_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">D</field>
<field name="criteria">Autonomie</field>
<field name="description">Le salarié peut interpréter et adapter les processus. Le contrôle est périodique.</field>
</record>
<record id="gn_cc.cc_3442_D_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">D</field>
<field name="criteria">Responsabilité</field>
<field name="description">Responsabilité dun budget prescrit. Participe à lélaboration des procédures de léquipe/du service. Implique des fonctions de coordination et de « contrôle » dautres salariés.</field>
</record>
<record id="gn_cc.cc_3442_D_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">D</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences techniques et relationnelles nécessaires à la maîtrise dun domaine dactivité</field>
</record>
<record id="gn_cc.cc_3442_E_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">E</field>
<field name="coeff_min" eval="325"/>
</record>
<record id="gn_cc.cc_3442_E_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">E</field>
<field name="criteria">Autonomie</field>
<field name="description">Le salarié interprète et adapte les processus et leur mise en œuvre sous le contrôle ponctuel de son responsable hiérarchique </field>
</record>
<record id="gn_cc.cc_3442_E_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">E</field>
<field name="criteria">Responsabilité</field>
<field name="description">Responsabilité dun budget prescrit. Participe à lélaboration des procédures de léquipe/du service. Implique des fonctions de coordination et de « contrôle » dautres salariés.</field>
</record>
<record id="gn_cc.cc_3442_E_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">E</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences techniques et relationnelles nécessaires à la maîtrise dun domaine dactivité</field>
</record>
<record id="gn_cc.cc_3442_F_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">F</field>
<field name="coeff_min" eval="350"/>
</record>
<record id="gn_cc.cc_3442_F_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">F</field>
<field name="criteria">Autonomie</field>
<field name="description">Le salarié peut créer lui-même ses processus. Le salarié rend compte au terme de sa mission et est évalué sur les écarts entre les attendus et le réalisé. Le contrôle seffectue a posteriori.</field>
</record>
<record id="gn_cc.cc_3442_F_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">F</field>
<field name="criteria">Responsabilité</field>
<field name="description">Participe à lélaboration des directives et/ou dun budget limité à son périmètre daction et est responsable de son exécution. Il peut assurer la responsabilité hiérarchique dautres salariés dans le cadre dune délégation de responsabilité.</field>
</record>
<record id="gn_cc.cc_3442_F_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">F</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences élargies impliquant une très bonne maîtrise dun ou plusieurs domaines dintervention</field>
</record>
<record id="gn_cc.cc_3442_G_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">G</field>
<field name="coeff_min" eval="375"/>
</record>
<record id="gn_cc.cc_3442_G_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">G</field>
<field name="criteria">Autonomie</field>
<field name="description">Le salarié peut créer lui-même ses processus. Le salarié rend compte au terme de sa mission et est évalué sur les écarts entre les attendus et le réalisé. Le contrôle seffectue a posteriori.</field>
</record>
<record id="gn_cc.cc_3442_G_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">G</field>
<field name="criteria">Responsabilité</field>
<field name="description">Participe à lélaboration des directives et/ou dun budget limité à son périmètre daction et est responsable de son exécution. Il peut assurer la responsabilité hiérarchique dautres salariés dans le cadre dune délégation de responsabilité.</field>
</record>
<record id="gn_cc.cc_3442_G_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">G</field>
<field name="criteria">Technicité</field>
<field name="description">Compétences élargies impliquant une très bonne maîtrise dun ou plusieurs domaines dintervention</field>
</record>
<record id="gn_cc.cc_3442_H_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">H</field>
<field name="coeff_min" eval="400"/>
</record>
<record id="gn_cc.cc_3442_H_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">H</field>
<field name="criteria">Autonomie</field>
<field name="description">Lautonomie est inhérente au statut de cadre. Elle est définie en fonction du poste occupé dans la structure. Le salarié rend compte au terme de sa mission et est évalué sur les écarts entre les attendus et le réalisé. Le contrôle seffectue obligatoirement a posteriori.</field>
</record>
<record id="gn_cc.cc_3442_H_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">H</field>
<field name="criteria">Responsabilité</field>
<field name="description">Définit et assume la politique économique de la structure et/ou assume la responsabilité hiérarchique et disciplinaire et/ou assume la responsabilité juridique de lactivité mise en œuvre. Assure la représentation de la structure dans tout ou partie de ces compétences.</field>
</record>
<record id="gn_cc.cc_3442_H_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">H</field>
<field name="criteria">Technicité</field>
<field name="description">Expertise dans un ou plusieurs domaines de compétences. Au groupe H, peut assurer des missions de développement sur un secteur dactivité ou ponctuellement sur plusieurs secteurs dactivités.</field>
</record>
<record id="gn_cc.cc_3442_I_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">I</field>
<field name="coeff_min" eval="450"/>
</record>
<record id="gn_cc.cc_3442_I_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">I</field>
<field name="criteria">Autonomie</field>
<field name="description">Lautonomie est inhérente au statut de cadre. Elle est définie en fonction du poste occupé dans la structure. Le salarié rend compte au terme de sa mission et est évalué sur les écarts entre les attendus et le réalisé. Le contrôle seffectue obligatoirement a posteriori.</field>
</record>
<record id="gn_cc.cc_3442_I_responsabilite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">I</field>
<field name="criteria">Responsabilité</field>
<field name="description">Définit et assume la politique économique de la structure et/ou assume la responsabilité hiérarchique et disciplinaire et/ou assume la responsabilité juridique de lactivité mise en œuvre. Assure la représentation de la structure dans tout ou partie de ces compétences.</field>
</record>
<record id="gn_cc.cc_3442_I_technicite" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">I</field>
<field name="criteria">Technicité</field>
<field name="description">Expertise dans un ou plusieurs domaines de compétences. Au groupe I, il détermine et pilote la stratégie de développement.</field>
</record>
<record id="gn_cc.cc_3442_J_coeff_20220101" model="gn_cc.cc.group.coeff">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">J</field>
<field name="coeff_min" eval="500"/>
</record>
<record id="gn_cc.cc_3442_J_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">J</field>
<field name="criteria">Autonomie</field>
<field name="description">Salariés qui détiennent la responsabilité de la définition des objectifs de la structure et de lorganisation du travail. Il doit détenir une délégation permanente de pouvoirs émanant dun cadre dun niveau supérieur ou des instances statutaires</field>
</record>
<record id="gn_cc.cc_3442_K_autonomie" model="gn_cc.cc.group.description">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="group">K</field>
<field name="criteria">Autonomie</field>
<field name="description">Salarié répondant à la définition légale et jurisprudentielle de cadre dirigeant </field>
</record>
<record id="gn_cc.cc_3442_V1_20220101" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="name">V1</field>
<field name="value">6.45</field>
</record>
<record id="gn_cc.cc_3442_V1_20220501" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-05-01</field>
<field name="name">V1</field>
<field name="value">6.61</field>
</record>
<record id="gn_cc.cc_3442_V1_20230101" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2023-01-01</field>
<field name="name">V1</field>
<field name="value">6.85</field>
</record>
<record id="gn_cc.cc_3442_V1_20240101" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="name">V1</field>
<field name="value">7.01</field>
</record>
<record id="gn_cc.cc_3442_V2_20220101" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2022-01-01</field>
<field name="name">V2</field>
<field name="value">6.37</field>
</record>
<record id="gn_cc.cc_3442_V2_20230101" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2023-01-01</field>
<field name="name">V2</field>
<field name="value">6.50</field>
</record>
<record id="gn_cc.cc_3442_V2_20240101" model="gn_cc.cc.point_value">
<field name="cc" ref="gn_cc.cc_3442"/>
<field name="start_date">2024-01-01</field>
<field name="name">V2</field>
<field name="value">6.60</field>
</record>
<menuitem
id="gn_cc.cc_configuration_menu"
name="Conventions Collectives"
parent="hr.menu_human_resources_configuration"
sequence="200"
groups="hr.group_hr_manager"/>
<menuitem
id="gn_cc.cc_configuration_menu_cc"
name="Conventions"
parent="gn_cc.cc_configuration_menu"
sequence="10"
action="gn_cc.cc_configuration"
groups="hr.group_hr_manager"/>
</odoo>

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import gn_company
from . import gn_cc

@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
import logging
_logger = logging.getLogger(__name__)
class ConventionCollective(models.Model):
_name = "gn_cc.cc"
_description = "Convention Collective"
_order = 'idcc, name'
name = fields.Char(string='Nom complet de la Convention Collective', required=True)
associated_companies = fields.One2many('res.company', string="Sociétés utilisant cette convention", inverse_name="cc")
active = fields.Boolean(default=True)
idcc = fields.Char(string="IDCC", default="3442")
groups = fields.Text(string="Groupes d'emploi")
criterias = fields.Text(string="Critères de description des groupes d'emploi")
point_cat = fields.Text(string="Catégories de points")
point_values = fields.One2many('gn_cc.cc.point_value', string="Valeurs du point", inverse_name="cc")
descriptions = fields.One2many('gn_cc.cc.group.description', string="Description de groupes associés à la convention", inverse_name="cc")
actual_values = fields.One2many('gn_cc.cc.point_value', string="Valeurs du point en vigueur", compute='_compute_actual_values')
actual_descriptions = fields.One2many('gn_cc.cc.group.description', compute='_compute_actual_descriptions')
coeffs = fields.One2many('gn_cc.cc.group.coeff', string="Coefficient minimum des groupes", inverse_name="cc")
actual_coeffs = fields.One2many('gn_cc.cc.group.coeff', compute='_compute_actual_coeffs')
@api.depends('point_values')
def _compute_actual_values(self):
for cc in self:
# Reset the value first
cc.actual_values = [(5,)]
# This dict will hold the actual point_value ID for each 'name'
actual_records = {}
# create a default comparison_date so we can use a context var to call this method in other modules (payslip)
comparison_date = self.env.context.get('comparison_date', fields.Date.today())
# Iterate over all point_values to find the latest but not in the future
for pv in cc.point_values.sorted(key='start_date', reverse=True):
# If we haven't added a record for this 'name' yet, do it
if pv.name not in actual_records and pv.start_date <= comparison_date:
actual_records[pv.name] = pv
# Now, set the actual_values field to the IDs of the actual records
actual_ids = [rec.id for rec in actual_records.values()]
cc.actual_values = [(6, 0, actual_ids)]
@api.depends('descriptions')
def _compute_actual_descriptions(self):
for cc in self:
# Reset the value first
cc.actual_descriptions = [(5,)]
# This dict will hold the actual point_value ID for each 'name'
actual_records = {}
# create a default comparison_date so we can use a context var to call this method in other modules (payslip)
comparison_date = self.env.context.get('comparison_date', fields.Date.today())
# Iterate over all point_values to find the latest but not in the future
for desc in cc.descriptions.sorted(key='start_date', reverse=True):
key = (desc.group, desc.criteria)
# If we haven't added a record for this 'name' yet, do it
if key not in actual_records and desc.start_date <= comparison_date:
actual_records[key] = desc
# Now, set the actual_values field to the IDs of the actual records
actual_ids = [rec.id for rec in actual_records.values()]
cc.actual_descriptions = [(6, 0, actual_ids)]
@api.depends('coeffs')
def _compute_actual_coeffs(self):
for cc in self:
# Reset the value first
cc.actual_coeffs = [(5,)]
# This dict will hold the actual point_value ID for each 'name'
actual_records = {}
# create a default comparison_date so we can use a context var to call this method in other modules (payslip)
comparison_date = self.env.context.get('comparison_date', fields.Date.today())
# Iterate over all point_values to find the latest but not in the future
for coeff in cc.coeffs.sorted(key='start_date', reverse=True):
# If we haven't added a record for this 'name' yet, do it
if coeff.group not in actual_records and coeff.start_date <= comparison_date:
actual_records[coeff.group] = coeff
# Now, set the actual_values field to the IDs of the actual records
actual_ids = [rec.id for rec in actual_records.values()]
cc.actual_coeffs = [(6, 0, actual_ids)]
@api.model
def create(self, vals):
new_record = super(ConventionCollective, self).create(vals)
self.env.user.company_id.write({'cc': new_record})
return new_record
class ConventionCollectiveCoeffMin(models.Model):
_name = "gn_cc.cc.group.coeff"
_description = "Coefficient minimum pour le groupe d'emploi"
_order = 'start_date'
@api.model
def _default_cc(self):
return self.env.user.company_id.cc
cc = fields.Many2one('gn_cc.cc', required=True, default='_default_cc', string="Convention collective associée")
@api.model
def _get_groups_selection(self):
_logger.info("Current compagnie: %s, Current CC: %s, Groups: %s", self.env.user.company_id.name, self.env.user.company_id.cc.idcc, self.env.user.company_id.cc.groups)
if self.env.user.company_id.cc and self.env.user.company_id.cc.groups:
groups_list = [(group.strip(), group.strip()) for group in self.env.user.company_id.cc.groups.split(';')]
return groups_list
else:
return []
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
group = fields.Selection(selection='_get_groups_selection', required=True, string="Groupe associé au coefficient")
coeff_min = fields.Integer(string="Coefficient Minimum", required=True)
class ConventionCollectiveGroupDescription(models.Model):
_name = "gn_cc.cc.group.description"
_description = "Descriptions des groupes d'emploi"
_order = 'start_date'
@api.model
def _default_cc(self):
return self.env.user.company_id.cc
@api.model
def _get_groups_selection(self):
if self.env.user.company_id.cc and self.env.user.company_id.cc.groups:
groups_list = [(group.strip(), group.strip()) for group in self.env.user.company_id.cc.groups.split(';')]
return groups_list
else:
return []
@api.model
def _get_criterias_selection(self):
if self.env.user.company_id.cc and self.env.user.company_id.cc.criterias:
criterias_list = [(criteria.strip(), criteria.strip()) for criteria in self.env.user.company_id.cc.criterias.split(';')]
return criterias_list
else:
return []
cc = fields.Many2one('gn_cc.cc', required=True, default=_default_cc, string="Convention collective associée")
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
group = fields.Selection(selection='_get_groups_selection', required=True, string="Groupe")
criteria = fields.Selection(selection='_get_criterias_selection', required=True, string="Critère de classification")
description = fields.Text(required=True)
class ConventionCollectivePointValue(models.Model):
_name = "gn_cc.cc.point_value"
_description = "Valeur du point"
_order = 'start_date, cc'
@api.model
def _default_cc(self):
return self.env.user.company_id.cc
@api.model
def _get_point_cat_selection(self):
if self.env.user.company_id.cc and self.env.user.company_id.cc.point_cat:
point_cat_list = [(cat.strip(), cat.strip()) for cat in self.env.user.company_id.cc.point_cat.split(';')]
return point_cat_list
else:
return []
start_date = fields.Date('From', required=True)
cc = fields.Many2one('gn_cc.cc', required=True, default=_default_cc, string="Convention collective associée")
name = fields.Selection(selection='_get_point_cat_selection', required=True, string="Catégorie de point")
value = fields.Monetary(string='Valeur du Point', help='Valeur du point défini par les partenaires sociaux', required=True)
currency_id = fields.Many2one(
'res.currency',
string='Currency',
required=True,
default=lambda self: self.env.user.company_id.currency_id.id,
help="Currency"
)

@ -7,5 +7,6 @@ from odoo import fields, models
class ResCompany(models.Model):
_inherit = 'res.company'
cc = fields.Many2one('gn_payroll.cc', "Convention Collective appliquée")
cc = fields.Many2one('gn_cc.cc', string="Convention Collective appliquée")

@ -0,0 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_gn_cc_cc_user,gn_cc_cc_user,model_gn_cc_cc,base.group_user,1,0,0,0
access_gn_cc_cc_admin,gn_cc_cc_admin,model_gn_cc_cc,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_cc_cc_group_coeff_user,gn_cc_cc_group_coeff_user,model_gn_cc_cc_group_coeff,base.group_user,1,0,0,0
access_gn_cc_cc_group_coeff_admin,gn_cc_cc_group_coeff_admin,model_gn_cc_cc_group_coeff,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_cc_cc_group_description_user,gn_cc_cc_group_description_user,model_gn_cc_cc_group_description,base.group_user,1,0,0,0
access_gn_cc_cc_group_description_admin,gn_cc_cc_group_description_admin,model_gn_cc_cc_group_description,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_cc_cc_point_value_user,gn_cc_cc_point_value_user,model_gn_cc_cc_point_value,base.group_user,1,0,0,0
access_gn_cc_cc_point_value_admin,gn_cc_cc_point_value_admin,model_gn_cc_cc_point_value,hr_contract.group_hr_contract_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_gn_cc_cc_user gn_cc_cc_user model_gn_cc_cc base.group_user 1 0 0 0
3 access_gn_cc_cc_admin gn_cc_cc_admin model_gn_cc_cc hr_contract.group_hr_contract_manager 1 1 1 1
4 access_gn_cc_cc_group_coeff_user gn_cc_cc_group_coeff_user model_gn_cc_cc_group_coeff base.group_user 1 0 0 0
5 access_gn_cc_cc_group_coeff_admin gn_cc_cc_group_coeff_admin model_gn_cc_cc_group_coeff hr_contract.group_hr_contract_manager 1 1 1 1
6 access_gn_cc_cc_group_description_user gn_cc_cc_group_description_user model_gn_cc_cc_group_description base.group_user 1 0 0 0
7 access_gn_cc_cc_group_description_admin gn_cc_cc_group_description_admin model_gn_cc_cc_group_description hr_contract.group_hr_contract_manager 1 1 1 1
8 access_gn_cc_cc_point_value_user gn_cc_cc_point_value_user model_gn_cc_cc_point_value base.group_user 1 0 0 0
9 access_gn_cc_cc_point_value_admin gn_cc_cc_point_value_admin model_gn_cc_cc_point_value hr_contract.group_hr_contract_manager 1 1 1 1

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_cc_form_view" model="ir.ui.view">
<field name="name">gn_cc.cc.form</field>
<field name="model">gn_cc.cc</field>
<field name="arch" type="xml">
<form string="Conventions Collectives">
<sheet string="Convention Collective">
<div class="oe_title">
<h1>
<field name="name" placeholder="Nom complet de la convention collective" />
</h1>
</div>
<group>
<group>
<field name="idcc" />
<field name="groups" placeholder="Liste des groupes, séparés par un point-virgule"/>
<field name="criterias" placeholder="Liste des critères de classement des groupes, séparés par un retour à la ligne"/>
<field name="point_cat" placeholder="Liste des catégories de points, séparés par un retour à la ligne"/>
<field name="actual_values" context="{'default_cc': active_id}" options="{'create': True, 'delete': True}">
<tree editable="bottom">
<field name="name" help="catégorie de point"/>
<field name="start_date"/>
<field name="value"/>
</tree>
</field>
<field name="actual_descriptions" context="{'default_cc': active_id}" widget="one2many_list" options="{'create': True, 'delete': True}">
<tree editable="bottom">
<field name="start_date"/>
<field name="group"/>
<field name="criteria"/>
<field name="description"/>
</tree>
</field>
<field name="actual_coeffs" context="{'default_cc': active_id}" widget="one2many_list" options="{'create': True, 'delete': True}">
<tree editable="bottom">
<field name="start_date"/>
<field name="group"/>
<field name="coeff_min"/>
</tree>
</field>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="gn_cc.cc_configuration" model="ir.actions.act_window">
<field name="name">Conventions Collectives</field>
<field name="res_model">gn_cc.cc</field>
<field name="view_mode">tree,form</field>
</record>
<record id="gn_cc.cc_group_coeff_configuration" model="ir.actions.act_window">
<field name="name">Coefficient minimum</field>
<field name="res_model">gn_cc.cc.group.coeff</field>
<field name="view_mode">tree,form</field>
</record>
<record id="gn_cc.cc_group_description_configuration" model="ir.actions.act_window">
<field name="name">Description du groupe</field>
<field name="res_model">gn_cc.cc.group.description</field>
<field name="view_mode">tree,form</field>
</record>
<record id="gn_cc.cc_point_value_configuration" model="ir.actions.act_window">
<field name="name">Valeur du point</field>
<field name="res_model">gn_cc.cc.point_value</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>

@ -3,8 +3,8 @@
<record id="res_company_form_gn_payroll" model="ir.ui.view">
<field name="name">res.company.form.gnpayroll.fr</field>
<field name="model">res.company</field>
<field name="priority">30</field>
<field name="inherit_id" ref="account.view_company_form"/>
<field name="priority" eval="30"/>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='company_registry']" position="after">

@ -0,0 +1,37 @@
# GN Avenants
Module de gestion des avenants aux contrats
## How to install
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
2. Refresh list of Applications in UI
3. Search for 'gn_holidays' and click on **Install**
## Changelog
- v16.0.0.0.5 (2024/03/19):
- Remove next_contract_id and related_contract_ids as it is a nightmare to recompute and avid possible infinite loop. We already have contract's history view per employee.
- v16.0.0.0.4 (2024/03/13):
- Create Action for Fixed-Term contract renewal
- Readonly attribute for date_end field in contract
- v16.0.0.0.3 (2024/03/12):
- Liens entre contrats liés passés et futurs
- model method and Cron job for contract status automation (!! NOT TESTED !!)
- Add date_validity_start and date_validity_end to keep date_start and date_end from contract and keep track of child contracts (avenants) date ranges.
- Add link to previous and next contract
- v16.0.0.0.2 (2024/03/10):
- Bouton de création d'un avenant
- Vues des contrats précédents
- v16.0.0.0.1 (2024/03/02):
- Création du module
## Issues
- [x] Needs a View for Previous contract (cf issue #9)
- [x] Needs an Action to Create a New Contract linked with a Previous one (cf issue #10)
- [x] Creating a New Contract (Avenant) should lead to changements to parent_contract
- [x] associated_contracts should be in the past, and also in the future, so recomputed automatically
- [x] Manage contract's status automatically
- [] No CDI should have a date_end
- [] ensure date_validity_start and date_validity_end are inside date_start and date_end
- [] failing logic for contracts concurential dates and states

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

@ -0,0 +1,19 @@
{
"name": "Gestion des Contrats: Typologie et Gestion des Avenants",
"version": "16.0.0.0.5",
"category": "HR",
"summary": "Permet de relier entre eux les contrats",
"author": "Le Garage Numérique",
"maintainers": ["makayabou"],
"website": "https://odoo.legaragenumerique.fr",
"depends": [
"hr",
"hr_contract",
],
"data": [
"data/gn_contract.xml",
"data/gn_contract_cron.xml",
"views/gn_contract.xml",
],
"license": "LGPL-3",
}

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_employee"/>
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_worker"/>
<record id="gn_contract_structure_type_employe" model="hr.payroll.structure.type">
<field name="name">Employé(e)</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_contract_structure_type_cadre" model="hr.payroll.structure.type">
<field name="name">Cadre</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_contract_structure_type_service_civique" model="hr.payroll.structure.type">
<field name="name">Volontaire en Service civique</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_contract_structure_type_intern" model="hr.payroll.structure.type">
<field name="name">Stagiaire</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_contract_structure_type_benevolent" model="hr.payroll.structure.type">
<field name="name">Administrateur(-trice) bénévole</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_contract_contract_cae" model="hr.contract.type">
<field name="name">PEC - CAE</field>
</record>
<record id="gn_contract_contract_apprentissage" model="hr.contract.type">
<field name="name">Contrat d'apprentissage</field>
</record>
<record id="gn_contract_contract_cdd" model="hr.contract.type">
<field name="name">CDD</field>
</record>
<record id="gn_contract_contract_cdi" model="hr.contract.type">
<field name="name">CDI</field>
</record>
<record id="gn_contract_contract_service_civique" model="hr.contract.type">
<field name="name">Service civique</field>
</record>
<record id="gn_contract_contract_admin" model="hr.contract.type">
<field name="name">Administrateur bénévole</field>
</record>
<record id="gn_contract_contract_internship" model="hr.contract.type">
<field name="name">Stage</field>
</record>
</odoo>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="1">
<record id="cron_update_contract_status" model="ir.cron">
<field name="name">Update Contract Status</field>
<field name="model_id" ref="model_hr_contract"/>
<field name="state">code</field>
<field name="code">model.cron_update_contract_status()</field>
<field name="active" eval="True"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="nextcall" eval="(datetime.now() + timedelta(hours=1)).strftime('%Y-%m-%d 00:10:00')"/>
<field name="doall" eval="False"/>
</record>
</data>
</odoo>

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import gn_contract
from . import gn_employee

@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from odoo import fields, models, api
from pytz import timezone
import logging
_logger = logging.getLogger(__name__)
class GnHrContract(models.Model):
_inherit = "hr.contract"
previous_contract_id = fields.Many2one("hr.contract", string="Contrat précédent")
date_validity_start = fields.Date(string="Date de prise en compte de l'avenant")
date_validity_end = fields.Date(string="Fin de prise en compte de l'avenant")
lineage_sequence_number = fields.Integer(string="Numéro de séquence dans l'historique du salarié", compute='_compute_lineage_sequence_number', store=True)
@api.depends('previous_contract_id')
def _compute_lineage_sequence_number(self):
for contract in self:
if contract.previous_contract_id:
previous_contract = contract.previous_contract_id
previous_number = previous_contract.lineage_sequence_number
contract.lineage_sequence_number = previous_number + 1
else:
contract.lineage_sequence_number = 0
@api.onchange('date_validity_end')
def update_state(self):
for record in self:
if record.date_validity_end <= fields.Date.context_today(self):
record.state = 'cancel'
def create_child_contract(self):
self.ensure_one()
default_vals = {
'name': "Avenant au contrat " + self.name,
'employee_id': self.employee_id.id,
'structure_type_id': self.structure_type_id.id,
'previous_contract_id': self.id,
'date_start': self.date_start,
'date_end': self.date_end,
'date_validity_start': fields.Date.context_today(self),
'date_validity_end': self.date_end,
'resource_calendar_id': self.resource_calendar_id.id,
'department_id': self.department_id.id,
'job_id': self.job_id.id,
'contract_type_id': self.contract_type_id.id,
'hr_responsible_id': self.hr_responsible_id.id,
'wage': self.wage,
'notes': self.notes,
'schedule_pay': self.schedule_pay,
'struct_id': self.struct_id.id,
}
new_contract = self.create(default_vals)
self.date_validity_end = fields.Date.from_string(default_vals['date_validity_start']) - timedelta(days=1)
return {
'type': 'ir.actions.act_window',
'name': 'Avenant',
'view_mode': 'form',
'res_model': 'hr.contract',
'res_id': new_contract.id,
'target': 'current',
}
def action_renew_contract(self):
self.ensure_one()
date_start = fields.Date.from_string(self.date_end) + timedelta(days=1)
duration = fields.Date.from_string(self.date_end) - fields.Date.from_string(self.date_start)
date_end = date_start + duration
return {
'type': 'ir.actions.act_window',
'name': 'Renouveller le contrat',
'view_mode': 'form',
'res_model': 'hr.contract',
'context': {
'default_name': "Renouvellement du contrat " + self.name,
'default_employee_id': self.employee_id.id,
'default_structure_type_id': self.structure_type_id.id,
'default_previous_contract_id': self.id,
'default_date_start': date_start,
'default_date_end': date_end,
'default_date_validity_start': date_start,
'default_date_validity_end': date_end,
'default_resource_calendar_id': self.resource_calendar_id.id,
'default_department_id': self.department_id.id,
'default_job_id': self.job_id.id,
'default_contract_type_id': self.contract_type_id.id,
'default_hr_responsible_id': self.hr_responsible_id.id,
'default_wage': self.wage,
'default_notes': self.notes,
'default_schedule_pay': self.schedule_pay,
'default_struct_id': self.struct_id.id,
},
'target': 'current',
}
def cron_update_contract_status(self):
"""Scheduled action to update contract statuses based on start and end dates."""
today = fields.Date.context_today(self)
contracts_to_close = self.search([('date_validity_end', '<=', today), ('status', '=', 'open')])
for contract in contracts_to_close:
contract.ensure_one()
contract.write({'status': 'cancel'})
contracts_to_open = self.search([('date_start', '<=', today), ('date_validity_start', '<=', today), ('status', '=', 'draft'), '|', ('date_validity_end', '>', today), ('date_validity_end', '=', False)])
for contract in contracts_to_open:
contract.ensure_one()
contract.write({'status': 'open'})
if contract.previous_contract_id:
contract.previous_contract_id.write({'status': 'close',
'date_validity_end': contract.date_validity_start - timedelta(days=1)
})

@ -0,0 +1,27 @@
from odoo import models, fields, api
class GnHrEmployee(models.Model):
_inherit = 'hr.employee'
employee_type = fields.Selection([
('employee', 'Employé'),
('student', 'Étudiant'),
('trainee', 'Stagiaire'),
('volunteer', 'Volontaire'),
('benevolent', 'Bénévole'),
], string='Employee Type', default='employee', required=True,
help="The employee type. Although the primary purpose may seem to categorize employees, this field has also an impact in the Contract History. Only Employee type is supposed to be under contract and will have a Contract History.")
anciennete_start_date = fields.Date(compute='_compute_anciennete_start_date', groups='hr_group.hr_user', store=True)
@api.depends('contract_ids.state', 'contract_ids.date_start')
def _compute_anciennete_start_date(self):
for employee in self:
if employee.contract_id:
previous_contract = employee.contract_id
while previous_contract.previous_contract_id:
previous_contract = previous_contract.previous_contract_id
employee.anciennete_start_date = previous_contract.date_start
else:
employee.anciennete_start_date = False

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_contract.hr_contract_avenant" model="ir.ui.view">
<field name="name">hr.contract.form.avenant</field>
<field name="model">hr.contract</field>
<field name="priority" eval="30"/>
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='date_start']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='date_start']" position="after">
<field name="previous_contract_id" attrs="{'readonly': True, 'invisible': [('previous_contract_id', '=', False)]}"/>
<field name="date_start" attrs="{'readonly': [('previous_contract_id', '!=', False)]}"/>
<field name="date_validity_start" attrs="{'invisible': [('previous_contract_id', '=', False)]}"/>
</xpath>
<xpath expr="//field[@name='date_end']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='date_end']" position="after">
<field name="date_end" attrs="{'readonly': [('previous_contract_id', '!=', False), ('date_end', '=', False)]}"/>
<field name="date_validity_end" attrs="{'invisible': [('previous_contract_id', '=', False)]}"/>
</xpath>
<header>
<button name="create_child_contract" type="object" string="Créer un avenant" class="oe_highlight"/>
<button name="action_renew_contract" type="object" string="Renouveller un CDD" class="oe_highlight" attrs="{'invisible': [('date_end', '=', False)]}"/>
</header>
</field>
</record>
</odoo>

@ -0,0 +1,19 @@
# GN Holidays
Module de gestion des congés et absences des employés
## How to install
1. Clone this repository in your odoo's extra-modules folder (i.e. /mnt/extra-addons)
2. Refresh list of Applications in UI
3. Search for 'gn_holidays' and click on **Install**
## Changelog
- v16.0.0.0.1 (2024/03/01):
- Création du module
- Ajouts des jours feriés pour 2024
- Ajout des types d'absence
## Issues
- [] Need prevention mechanism when CP or Compensatory Day is asked on a Public Holiday (cf issue #8)

@ -0,0 +1,22 @@
{
"name": "France - Congés",
"version": "16.0.0.0.1",
"category": "HR",
"summary": "Configuration des congés et absences",
"author": "Le Garage Numérique",
"maintainers": ["makayabou"],
"website": "https://odoo.legaragenumerique.fr",
"depends": [
"hr",
"hr_contract",
"l10n_fr_oca",
"hr_work_entry",
"hr_work_entry_holidays",
"hr_holidays_public",
],
"data": [
"data/gn_holidays_public.xml",
"data/gn_holidays.xml",
],
"license": "LGPL-3",
}

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="0">
<!-- Work Entry Type -->
<record id="hr_work_entry_contract.work_entry_type_compensatory" model="hr.work.entry.type">
<field name="display_name">Congé compensatoire (récup')</field>
<field name="code">RTT</field>
<field name="color">15</field>
<field name="is_leave" eval="True"/>
</record>
<record id="hr_work_entry_contract.work_entry_type_home_working" model="hr.work.entry.type">
<field name="name">Télé-travail</field>
<field name="code">TT</field>
<field name="color">1</field>
<field name="is_leave" eval="True"/>
</record>
<record id="hr_work_entry_contract.work_entry_type_unpaid_leave" model="hr.work.entry.type">
<field name="name">Absence non-rémunérée</field>
<field name="color">2</field>
<field name="code">ABS</field>
<field name="is_leave" eval="True"/>
</record>
<record id="hr_work_entry_contract.work_entry_type_sick_leave" model="hr.work.entry.type">
<field name="name">Congé Maladie</field>
<field name="code">AM</field>
<field name="is_leave" eval="True"/>
<field name="color">9</field>
</record>
<record id="hr_work_entry_contract.work_entry_type_legal_leave" model="hr.work.entry.type">
<field name="name">Congé payé</field>
<field name="code">CP</field>
<field name="is_leave" eval="True"/>
<field name="color">6</field>
</record>
<!--Congés payés-->
<record id="hr_holidays.holiday_status_cl" model="hr.leave.type">
<field name="name">Paid Time Off</field>
<field name="requires_allocation">yes</field>
<field name="employee_requests">no</field>
<field name="leave_validation_type">hr</field>
<field name="allocation_validation_type">officer</field>
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave"/>
<field name="allocation_notif_subtype_id" ref="hr_holidays.mt_leave_allocation"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="request_unit">day</field>
<field name="support_document" eval="False"/>
<field name="time_type">leave</field>
<field name="exclude_public_holidays" eval="True"/>
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_legal_leave"/>
<field name="icon_id" ref="hr_holidays.icon_14"/>
<field name="color">2</field>
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
<field name="sequence">1</field>
</record>
<!-- Arrêt maladie -->
<record id="hr_holidays.holiday_status_sl" model="hr.leave.type">
<field name="name">Sick Time Off</field>
<field name="requires_allocation">no</field>
<field name="color_name">red</field>
<field name="leave_validation_type">no_validation</field>
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave_sick"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="request_unit">day</field>
<field name="support_document" eval="True"/>
<field name="time_type">leave</field>
<field name="exclude_public_holidays" eval="False"/>
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_sick_leave"/>
<field name="icon_id" ref="hr_holidays.icon_22"/>
<field name="color">3</field>
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
<field name="sequence">2</field>
</record>
<!-- Compensatory Days -->
<record id="hr_holidays.holiday_status_comp" model="hr.leave.type">
<field name="name">Compensatory Days</field>
<field name="requires_allocation">yes</field>
<field name="employee_requests">yes</field>
<field name="leave_validation_type">hr</field>
<field name="allocation_validation_type">officer</field>
<field name="request_unit">hour</field>
<field name="support_document" eval="True"/>
<field name="time_type">leave</field>
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_compensatory"/>
<field name="exclude_public_holidays" eval="True"/>
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="icon_id" ref="hr_holidays.icon_4"/>
<field name="color">4</field>
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
<field name="sequence">4</field>
</record>
<!--Absence non-rémunérée -->
<record id="hr_holidays.holiday_status_unpaid" model="hr.leave.type">
<field name="name">Unpaid</field>
<field name="requires_allocation">no</field>
<field name="leave_validation_type">hr</field>
<field name="allocation_validation_type">officer</field>
<field name="request_unit">hour</field>
<field name="unpaid" eval="True"/>
<field name="time_type">leave</field>
<field name="support_document" eval="True"/>
<field name="exclude_public_holidays" eval="True"/>
<field name="work_entry_type_id" ref="hr_work_entry_contract.work_entry_type_unpaid_leave"/>
<field name="leave_notif_subtype_id" ref="hr_holidays.mt_leave_unpaid"/>
<field name="responsible_id" ref="base.user_admin"/>
<field name="icon_id" ref="hr_holidays.icon_28"/>
<field name="color">5</field>
<field name="company_id" eval="False"/> <!-- Explicitely set to False for it to be available to all companies -->
<field name="sequence">3</field>
</record>
<!-- Plan de cumul des congés payés-->
<record id="cumul_cp" model="hr.leave.accrual.plan">
<field name="name">Cumul des congés payés</field>
<field name="time_off_type_id" ref="hr_holidays.holiday_status_cl"/>
</record>
<!-- Régle de cumul des Congés payés-->
<record id="cumul_standard" model="hr.leave.accrual.level">
<field name="start_count">0</field>
<field name="start_type">month</field>
<field name="is_based_on_worked_time" eval="True"/>
<field name="added_value">2.5</field>
<field name="added_value_type">days</field>
<field name="frequency">monthly</field>
<field name="maximum_leave">90</field>
<field name="action_with_unused_accruals">postponed</field>
<field name="postpone_max_days">60</field>
<field name="accrual_plan_id" ref="gn_holidays.cumul_cp"/>
</record>
</data>
<delete model="hr.work.entry.type" id="hr_work_entry_contract.work_entry_type_leave"/>
</odoo>

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data noupdate="0">
<!-- Jour fériés 2024 -->
<record id="jours_feries_2024" model="hr.holidays.public">
<field name="year">2024</field>
<field name="country_id" ref="base.fr"/>
</record>
<record id="jour_ferie_jour_de_l_an" model="hr.holidays.public.line">
<field name="name">Jour de l'an</field>
<field name="date" eval="'2024-01-01'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_lundi_de_paques" model="hr.holidays.public.line">
<field name="name">Lundi de Pâques</field>
<field name="date" eval="'2024-04-01'"/>
<field name="variable_date" eval="True"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_fete_du_travail" model="hr.holidays.public.line">
<field name="name">Fête du Travail</field>
<field name="date" eval="'2024-05-01'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_victoire_1945" model="hr.holidays.public.line">
<field name="name">Victoire 1945</field>
<field name="date" eval="'2024-05-08'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_ascension" model="hr.holidays.public.line">
<field name="name">Ascension</field>
<field name="date" eval="'2024-05-09'"/>
<field name="variable_date" eval="True"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_pentecote" model="hr.holidays.public.line">
<field name="name">Pentecôte</field>
<field name="date" eval="'2024-05-20'"/>
<field name="variable_date" eval="True"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_fete_nationale" model="hr.holidays.public.line">
<field name="name">Fête Nationale</field>
<field name="date" eval="'2024-07-14'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_assomption" model="hr.holidays.public.line">
<field name="name">Assomption</field>
<field name="date" eval="'2024-08-15'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_toussaint" model="hr.holidays.public.line">
<field name="name">Toussaint</field>
<field name="date" eval="'2024-11-01'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_armistrice_1918" model="hr.holidays.public.line">
<field name="name">Armistrice 1918</field>
<field name="date" eval="'2024-11-11'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
<record id="jour_ferie_noel" model="hr.holidays.public.line">
<field name="name">Jour de Noël</field>
<field name="date" eval="'2024-12-25'"/>
<field name="variable_date" eval="False"/>
<field name="year_id" ref="jours_feries_2024"/>
</record>
</data>
</odoo>

@ -1,6 +1,6 @@
{
"name": "France - Paye",
"version": "16.0.0.0.2",
"version": "16.0.0.0.4",
"category": "Payroll",
"summary": "Configuration de la paie",
"author": "Le Garage Numérique",
@ -8,17 +8,21 @@
"website": "https://odoo.legaragenumerique.fr",
"depends": [
"payroll",
"hr_contract"
"hr_contract",
"hr_work_entry",
"hr_work_entry_holidays",
"hr_holidays_public",
"contacts"
],
"data": [
"data/gn_payroll_cc.xml",
"views/gn_payroll_contract.xml",
"views/gn_payroll_calendar.xml",
"data/gn_payroll_contract.xml",
"data/gn_payroll_salary.xml",
"data/gn_payroll_sickness.xml",
#"data/gn_payroll_sickness.xml",
"data/gn_payroll_time.xml",
"data/gn_payroll_universal.xml",
"data/gn_payroll_structure.xml",
"views/gn_payroll_company.xml",
#"data/gn_payroll_structure.xml",
"security/ir.model.access.csv"
],
"license": "LGPL-3",

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="gn_payroll.cc_1418" model="gn_payroll.cc">
<field name="name">Convention Eclat</field>
<field name="idcc">1418</field>
</record>
</odoo>

@ -1,46 +1,63 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_employee"/>
<delete model="hr.payroll.structure.type" id="hr_contract.structure_type_worker"/>
<record id="gn_payroll_structure_type_employe" model="hr.payroll.structure.type">
<field name="name">Employé(e)</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_payroll_structure_type_cadre" model="hr.payroll.structure.type">
<field name="name">Cadre</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_payroll_structure_type_service_civique" model="hr.payroll.structure.type">
<field name="name">Volontaire en Service civique</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_payroll_structure_type_intern" model="hr.payroll.structure.type">
<field name="name">Stagiaire</field>
<field name="country_id" eval="False"/>
</record>
<record id="payroll_structure_type_benevolent" model="hr.payroll.structure.type">
<field name="name">Administrateur bénévole</field>
<field name="country_id" eval="False"/>
</record>
<record id="gn_payroll_contract_cae" model="hr.contract.type">
<field name="name">PEC - CAE</field>
</record>
<record id="gn_payroll_contract_apprentissage" model="hr.contract.type">
<field name="name">Contrat d'apprentissage</field>
</record>
<record id="gn_payroll_contract_cdd" model="hr.contract.type">
<field name="name">CDD</field>
</record>
<record id="gn_payroll_contract_cdi" model="hr.contract.type">
<field name="name">CDI</field>
</record>
<record id="gn_payroll_contract_service_civique" model="hr.contract.type">
<field name="name">Service civique</field>
</record>
<record id="gn_payroll_contract_admin" model="hr.contract.type">
<field name="name">Administrateur bénévole</field>
</record>
<record id="gn_payroll_contract_internship" model="hr.contract.type">
<field name="name">Stage</field>
</record>
<record id="gn_payroll_contract_entretien_type_annuel" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Entretien annuel d'évaluation</field>
<field name="month_delay">12</field>
<field name="start_point">endofyear</field>
<field name="auto">True</field>
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"></field>
</record>
<record id="gn_payroll_contract_entretien_type_periodique" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Entretien professionnel périodique</field>
</record>
<record id="gn_payroll_contract_entretien_type_periodique_retour" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Entretien professionnel périodique de retour de congé long</field>
<field name="parent_id" ref="gn_payroll_contract_entretien_type_periodique"/>
<field name="month_delay">0</field>
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
</record>
<record id="gn_payroll_contract_entretien_type_periodique_initial" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Entretien professionnel périodique des 2 ans</field>
<field name="parent_id" ref="gn_payroll_contract_entretien_type_periodique"/>
<field name="start_point">contract</field>
<field name="month_delay">24</field>
<field name="auto">True</field>
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
</record>
<record id="gn_payroll_contract_entretien_type_periodique_bilan" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Entretien professionnel bilan des 6 ans</field>
<field name="parent_id" ref="gn_payroll_contract_entretien_type_periodique"/>
<field name="start_point">contract</field>
<field name="month_delay">72</field>
<field name="auto">True</field>
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
</record>
<record id="gn_payroll_contract_entretien_type_valorisation" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Entretien de valorisation des acquis professionnels</field>
<field name="start_point">contract</field>
<field name="month_delay">48</field>
<field name="generate_subcontract" eval="True"/>
<field name="auto">True</field>
<field name="cc_ids" eval="[(6, 0, [ref('gn_payroll.cc_3442')])]"/>
</record>
<record id="gn_payroll_contract_entretien_type_modif_cc" model="gn_payroll.hr.contract.entretien.type">
<field name="name">Modification conventionnelle</field>
</record>
<data noupdate="1">
<!-- Define the sequence for the Entretien model -->
<record id="seq_entretien" model="ir.sequence">
<field name="name">Entretien Sequence</field>
<field name="code">gn_payroll.hr.contract.entretien</field>
<field name="prefix">ENT</field>
<field name="padding">5</field>
<field name="number_increment">1</field>
</record>
</data>
<menuitem
id="menu_human_resources_configuration_contract_entretien"
name="Entretiens"
parent="hr.menu_hr_employee_payroll"
sequence="35"
action="gn_payroll.hr_contract_entretien"
groups="hr.group_hr_manager"/>
</odoo>

@ -4,6 +4,71 @@
<field name="name">Rémunération</field>
<field name="code">REMUNERATION</field>
</record>
<record id="CONTRACT_WAGE" model="hr.salary.rule">
<field name="name">Salaire contractuel</field>
<field name="code">CONTRACT_WAGE</field>
<field name="sequence" eval="100"/>
<field name="category_id" ref="gn_payroll.REMUNERATION"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
result = contract.wage
</field>
</record>
<record id="WAGEV1" model="hr.salary.rule">
<field name="name">Rémunération des points de class V1</field>
<field name="code">WAGEV1</field>
<field name="sequence" eval="100"/>
<field name="category_id" ref="gn_payroll.REMUNERATION"/>
<field name="condition_select">python</field>
<field name="condition_python">
if payslip.company_id.cc.idcc == "3442":
result = True
</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
payslip_date_from = payslip.date_from
cc = payslip.company_id.cc.with_context(comparison_date=payslip_date_from)
cc_id = cc.id
actual_v1 = 0
for value in cc.actual_values:
if value['name'] == "V1":
actual_v1 = value.value
break
actual_gpA = 0
for coeff in cc.actual_coeffs:
if coeff['group'] == "A":
actual_gpA = coeff['coeff_min']
break
result_qty = actual_gpA
result = actual_v1
</field>
</record>
<record id="WAGEV2" model="hr.salary.rule">
<field name="name">Rémunération des points de class V2</field>
<field name="code">WAGEV2</field>
<field name="sequence" eval="100"/>
<field name="category_id" ref="gn_payroll.REMUNERATION"/>
<field name="condition_select">python</field>
<field name="condition_python">
if payslip.company_id.cc.idcc == "3442":
result = True
</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
payslip_date_from = payslip.date_from
cc = payslip.company_id.cc.with_context(comparison_date=payslip_date_from)
cc_id = cc.id
actual_v1 = 0
for value in cc.actual_values:
if value['name'] == "V2":
actual_v2 = value.value
break
result_qty = 173
result = actual_v2
</field>
</record>
<record id="BRUT" model="hr.salary.rule">
<field name="name">Salaire Brut</field>
<field name="code">BRUT</field>
@ -17,22 +82,10 @@ if inputs.BRUT and inputs.BRUT.amount > 0:
elif inputs.BRUT and inputs.BRUT.amount == -1 :
result = 0
else:
hours_month = 0
dic_days = {0: 0, 1:0, 2: 0, 3: 0, 4:0}
for x in contract.resource_calendar_id.attendance_ids:
dic_days[int(x.dayofweek)] += (x.hour_to - x.hour_from)
# Calculating hours workable for the month
for x in range(1, int(JOURSMOIS) + 1):
day = payslip.date_from.replace(day = x).weekday()
if day in dic_days:
hours_month += dic_days[day]
hours_std = sum([(x.hour_to - x.hour_from) for x in contract.resource_calendar_id.attendance_ids]) * 52 /12
result = round( HOURS * contract.wage / hours_month , 2) + categories.ALW + categories.INDEMNCOT
#result = round( HOURS * contract.wage / hours_std , 2) + categories.ALW + categories.INDEMNCOT
retenue = 0
if worked_days.ABS and worked_days.ABS.hours:
retenue = round( worked_days.ABS.hours * contract.wage / worked_days.WORK100.hours , 2)
result = contract.wage + retenue + categories.ALW + categories.INDEMNCOT
</field>
</record>
<record id="BRUTSC" model="hr.salary.rule">
@ -43,10 +96,15 @@ else:
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
if inputs.BRUT and inputs.BRUT.amount > 0:
result = inputs.BRUT.amount
else:
hours_std = sum([( x.hour_to - x.hour_from ) for x in contract.resource_calendar_id.attendance_ids]) * 52 / 12
if inputs.BRUT and inputs.BRUT.amount > 0:
result = inputs.BRUT.amount + categories.ALW + categories.INDEMNCOT
elif inputs.BRUT and inputs.BRUT.amount == -1 :
result = 0
else:
retenue = 0
if worked_days.ABS and worked_days.ABS.hours:
retenue = round( worked_days.ABS.hours * contract.wage / worked_days.WORK100.hours , 2)
result = retenue + categories.ALW + categories.INDEMNCOT
result = round( HOURS * TXSERVICECIVIQUE / hours_std , 2)
</field>
</record>

@ -4,6 +4,11 @@
<field name="name">Gestion des arrêts maladie</field>
<field name="code">SICKNESS</field>
</record>
<record id="work_entry_type_sickness" model="hr.work.entry.type">
<field name="name">Sickness</field>
<field name="color">0</field>
<field name="code">SICKNESSTYPE</field>
</record>
<record id ="CALENDAR_SICKNESS_DAYS" model="hr.salary.rule">
<field name="name">Arrêt maladie (Jours Calendaires)</field>
<field name="code">CALENDAR_SICKNESS_DAYS</field>

@ -5,18 +5,20 @@
<field name="company_id" eval="1"/>
<field name="rule_ids" eval="[
(6, 0, [
ref('USUAL_HOURS'),
ref('EXPECTED_HOURS'),
ref('CALENDAR_SICKNESS_DAYS'),
ref('NON_WORKED_SICKNESS_DAYS'),
ref('NON_WORKED_SICKNESS_DAYS'),
ref('WAITING_SICKNESS_DAYS'),
ref('IJSS_SICKNESS'),
ref('HOURS'),
ref('BRUT'),
ref('JOURSMOIS'),
ref('SMICH'),
ref('PMSS'),
ref('WAGE'),
ref('TXREMBTRANS')
ref('TXREMBTRANS'),
ref('COEFF'),
ref('BRUT'),
])
]"/>
</record>

@ -4,84 +4,15 @@
<field name="name">Calcul des heures</field>
<field name="code">TIME</field>
</record>
<record id="HOURS" model="hr.salary.rule">
<record id="PAYED_HOURS" model="hr.salary.rule">
<field name="name">Heures rémunérées</field>
<field name="code">HOURS</field>
<field name="sequence" eval="100" />
<field name="category_id" ref="gn_payroll.TIME" />
<field name="code">PAYED_HOURS</field>
<field name="sequence" eval="100"/>
<field name="category_id" ref="gn_payroll.TIME"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
day = 0
hours = 0
hours_month = 0
hours_std = sum([(x.hour_to - x.hour_from) for x in contract.resource_calendar_id.attendance_ids]) * 52 /12
# calculation for Service civique
if contract.struct_id.code == "VOLSERVCIV":
if payslip.date_from.day > 1:
result = hours_std * (31 - payslip.date_from.day) / 30
elif JOURSMOIS > payslip.date_to.day:
result = hours_std * payslip.date_to.day / 30
else:
result = hours_std
# other calculations
else:
dic_days = {0: 0, 1:0, 2: 0, 3: 0, 4:0}
for x in contract.resource_calendar_id.attendance_ids:
if ((not x.date_from) or ( payslip.date_from >= x.date_from )) and \
((not x.date_to) or ( x.date_to >= payslip.date_to )):
dic_days[int(x.dayofweek)] += (x.hour_to - x.hour_from)
# Calculating missed hours when payslip doesn't start / end at the beginning/ end of the month
if payslip.date_from.day > 1:
for x in range(payslip.date_from.day, 1, -1):
day = payslip.date_from.replace(day = x).weekday()
if day in dic_days:
hours += dic_days[day]
if JOURSMOIS > payslip.date_to.day:
for x in range(payslip.date_to.day + 1, int(JOURSMOIS)):
day = payslip.date_to.replace(day = x).weekday()
if day in dic_days:
hours += dic_days[day]
hours_missed = hours
# Calculating missed hours when absence or sickday
maladie = inputs.MALADIE.amount if inputs.MALADIE and inputs.MALADIE.amount > 0.0 else 0
absences = inputs.ABSENCE.amount if inputs.ABSENCE and inputs.ABSENCE.amount > 0.0 else 0
calendaires = inputs.CALENDAIRE.amount if inputs.CALENDAIRE and inputs.CALENDAIRE.amount > 0.0 else 0
if ( maladie != 0 or absences != 0):
if calendaires + absences == JOURSMOIS:
hours_std = 0
else:
days_off = absences + maladie
hours_off = days_off * contract.resource_calendar_id.hours_per_day
hours_missed += hours_off
# Final calculations
if worked_days.WORK100 and worked_days.WORK100.number_of_hours:
hours_done = worked_days.WORK100.number_of_hours
else:
hours_done = hours_std
####result = hours_done - hours_missed
#result = hours_done * hours_std / (hours_missed + hours_done)
#result = hours_std * (hours_done - hours_missed) / hours_done
result = hours_done - hours_missed
####result = hours_std - hours_missed
result = worked_days.WORK100.number_of_hours + (worked_days.ABS.number_of_hours if worked_days.ABS else 0)
</field>
</record>
<record id="ABSENCE" model="hr.rule.input">
<field name="name">Absences non rémunérées ( hors arrêt maladie)</field>
<field name="code">ABSENCE</field>
<field name="input_id" ref="gn_payroll.HOURS" />
</record>
</odoo>

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import gn_payroll_cc
from . import gn_payroll_company
from . import gn_payroll_employee
from . import gn_payroll_contract
from . import gn_payroll_employee
#from . import gn_payroll_payslip

@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ConventionCollective(models.Model):
_name = "gn_payroll.cc"
_description = "Convention Collective"
_order = 'idcc, name'
def copy(self, default=None):
raise UserError(_('Duplicating a company is not allowed. Please create a new company instead.'))
name = fields.Char(string='Nom complet de la Convention Collective', required=True, store=True, readonly=False)
active = fields.Boolean(default=True)
idcc = fields.Integer(string="IDCC", help='Used to order Conventions Collectives in the switcher', default=10)

@ -0,0 +1,226 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from dateutil.relativedelta import relativedelta
from datetime import date, datetime
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
class GnCalendarEvent(models.Model):
_inherit = "calendar.event"
is_entretien = fields.Boolean(string="Concerne un entretien professionnel", default=False)
entretien_id = fields.Many2one("gn_payroll.hr.contract.entretien", string="Entretien professionnel associé")
entretien_ids = fields.Many2one("gn_payroll.hr.contract.entretien", string="Entretiens professionnels associés")
class GnHrContract(models.Model):
_inherit = "hr.contract"
#previous_contract_id = fields.Many2one("hr.contract", string="Contrat précédent (en cas d'avenant, de renouvellement ou de changement de poste)", default=False)
#contract_lineage_sequence_number = fields.Integer(string="Numéro de séquence dans l'historique du salarié", default=0)
computed_wage = fields.Integer(string="Salaire négocié (en fonction des éléments de la fiche de poste)", compute='_compute_wage', readonly=True, store=True)
#entretien_type_ids = fields.Many2many('gn_payroll.hr.contract.entretien.type', string="Catégories d'entretiens prévues au contrat")
#entretien_sequence_number = fields.Integer(string="Entretien Sequence Number", default=1)
entretien_ids = fields.One2many('gn_payroll.hr.contract.entretien', inverse_name='main_contract_id', string="Entretiens liés au contrat")
career_ids = fields.One2many('gn_payroll.hr.contract.career', readonly=True, inverse_name='contract_id', string="Fiche de poste associée")
# def update_entretiens_names(self):
# for contract in self:
# sequence_number = 1
# for entretien in contract.entretien_ids.sorted('start'):
# entretien.name = 'Entretien n°{}: {}'.format(sequence_number, entretien.type_id.name )
# sequence_number += 1
# @api.model
# def _get_groups_selection(self):
# if self.env.user.company_id.cc and self.env.user.company_id.cc.groups:
# groups_list = [(group.strip(), group.strip()) for group in self.env.user.company_id.cc.groups.split(';')]
# return groups_list
# else:
# return []
# Pas à supprimer complètement, mais à reprendre
@api.depends('indice', 'classification_group')
def _compute_wage(self):
for contract in self:
contract.computed_wage = contract.wage
return True
if contract.company_id.cc and contract.company_id.cc.idcc =="3442":
group_coeff = cc.actual_coeffs.filtered(lambda r: r.group == contract.classification_group).ensure_one()['coeff_min']
if group_coeff > employee.contract_id.indice:
raise UserError("L'indice minimum conventionnel pour le poste est %s, veuillez ajuster l'indice du contract en conséquence.", group_coeff)
@api.model
def create(self, vals):
contract = super(GnHrContract, self).create(vals)
if not contract.parent_contract_id:
cc_id = contract.company_id.cc.id
entretien_types = self.env['gn_payroll.hr.contract.entretien.type'].search([
('cc_ids', 'in', [cc_id]),
('auto', '=', True)
])
for entretien_type in entretien_types:
self.env['gn_payroll.hr.contract.entretien'].create({
'employee_id': contract.employee_id.user_partner_id.id,
'main_contract_id': contract.id,
'type_id': entretien_type.id,
})
#contract.entretien_sequence_number += 1
contract_lineage_sequence_number += 1
return contract
class EntretienType(models.Model):
_name='gn_payroll.hr.contract.entretien.type'
_description = "Type d'entretien d'évaluation ou de modification du contrat"
cc_ids = fields.Many2many('gn_payroll.cc', string="Conventions collectives rattachées")
name = fields.Char(string="Type de l'entretien", required = True)
auto = fields.Boolean("Est généré automatiquement à la création du contrat")
start_point = fields.Selection(string="Point de départ des entretiens", selection=[('endofyear', "En fin d'année"), ('contract', "À la date anniversaire du contrat"), ('absence', "Au retour d'un congé long")])
month_delay = fields.Integer(string="Nombre de mois entre chaque entretien")
generate_subcontract = fields.Boolean("Génère la création d'un avenant au contrat")
parent_id = fields.Many2one('gn_payroll.hr.contract.entretien.type', string="Type d'entretien parent", ondelete="cascade")
child_ids = fields.One2many('gn_payroll.hr.contract.entretien.type', 'parent_id', string="Types d'entretien enfants")
@api.constrains('parent_id')
def _check_parent_id(self):
for record in self:
if record.parent_id == record:
raise ValidationError("A record cannot select itself as a parent.")
if record.parent_id and record.parent_id.parent_id:
raise ValidationError("You cannot select a parent type which already has a parent.")
@api.onchange('parent_id')
def _onchange_parent_id(self):
if self.parent_id:
if self.parent_id == self:
self.parent_id = False
return {
'warning': {
'title': "Invalid Parent Selection",
'message': "A record cannot select itself as a parent.",
},
'domain': {'parent_id': domain}
}
if self.parent_id.parent_id:
self.parent_id = False
return {
'warning': {
'title': "Invalid Parent Selection",
'message': "You cannot select a parent type which already has a parent.",
}
}
class Entretien(models.Model):
_name = 'gn_payroll.hr.contract.entretien'
_description = "Entretien légal d'évaluation ou de modification du contrat"
ref = fields.Char(string="Référence", readonly=True)
name = fields.Char(string="Titre", readonly=True)
employee_id = fields.Many2one('res.partner', string="Employé", required=True, domain=[("employee_ids", "!=", False)], relation="contract_entretien_employee_rel")
other_participant_ids = fields.Many2many('res.partner', string="Autres participants à l'entretien (délégué salariale, etc.)", relation="contract_entretien_other_participant_rel")
interviewer_id = fields.Many2one('res.partner', domain=[("employee_ids", "!=", False)], string="Interviewer", relation="contract_entretien_interviewer_rel")
other_interviewer_ids = fields.Many2many('res.partner', domain=[("employee_ids", "!=", False)], string="Participants à l'entretien", relation="contract_entretien_other_interviewer_rel")
main_contract_id = fields.Many2one('hr.contract', string = "Contrat concerné", required=True)
running_contract_id = fields.Many2one('hr.contract', string="Avenant concerné")
type_id = fields.Many2one('gn_payroll.hr.contract.entretien.type', string="Type d'entretien", required=True)
first_date = fields.Date(string="Premier entretien", compute='_compute_first_date', store=True)
next_date = fields.Date(string="Date du prochain entretien", compute='_compute_next_entretien_date', store=True)
report = fields.Html(string="Compte-rendu")
event_ids = fields.One2many('calendar.event', 'entretien_ids', string="Rendez-vous programmés")
event_id = fields.One2many('calendar.event', 'entretien_id', string="Rendez-vous programmé", compute='_get_date_from_event', store=True)
start = fields.Datetime("Date et heure du rendez-vous", compute='_get_date_from_event', store=True)
generate_subcontract = fields.Boolean(
string="Génére la création d'un avenant",
default=lambda self: self._get_default_generate_subcontract()
)
generation_start = fields.Datetime(string="Date de prise en compte des aménagements")
generated_contract_id = fields.Many2one('hr.contract', string="Avenant généré")
generated_career_id = fields.Many2one('gn_payroll.hr.contract.career', string="Fiche de poste")
def _get_default_generate_subcontract(self):
return self.type_id.generate_subcontract if self.type_id else False
def update_main_contract(self):
# Updates entretien's num by date for employee,
# Called in _get_date_from_event on event_ids access
for entretien in self:
if entretien['main_contract_id']:
contract = self.env['hr.contract'].browse(entretien['main_contract_id'].id)
contract.update_entretiens_names()
@api.depends('type_id.start_point', 'type_id.month_delay')
def _compute_first_date(self):
for entretien in self:
if entretien.type_id and entretien.type_id.start_point:
start_point = entretien.type_id.start_point
if start_point == "contract" and entretien.main_contract_id.date_start and entretien.type_id.month_delay:
contract_start_date = fields.Date.from_string(entretien.main_contract_id.date_start)
entretien.first_date = contract_start_date + relativedelta(months=+entretien.type_id.month_delay)
elif start_point == "endofyear":
today = fields.Date.today()
entretien.first_date = date(today.year, 12, today.day)
elif start_point == "absence":
entretien.first_date = fields.Date.today()
@api.depends('event_ids')
def _get_date_from_event(self):
for entretien in self:
try:
if len(entretien.event_ids) > 0 and entretien.event_ids[0].start:
#entretien.event_id = self.env['calendar.event'].browse(entretien.event_ids[0].id)
entretien.event_id = entretien.event_ids[0]
_logger.info("Entretien has event_id: %s", entretien.event_id)
entretien.start = datetime.combine(entretien.event_ids[0].start, datetime.min.time())
else:
entretien.start = datetime.combine(entretien.first_date, datetime.min.time()) if entretien.first_date else datetime.now()
except Exception as e:
_logger.error("Error occurred while setting start date: %s", e)
entretien.start = datetime.combine(fields.Date.today(), datetime.min.time())
_logger.info("Date defined as start in entretien: %s", entretien.start)
self.update_main_contract()
@api.depends('first_date', 'type_id.month_delay')
def _compute_next_entretien_date(self):
for entretien in self:
if entretien.first_date and entretien.type_id.month_delay:
next_entretien_date = fields.Date.from_string(entretien.first_date) + relativedelta(months=entretien.type_id.month_delay)
entretien.next_date = fields.Date.to_string(next_entretien_date)
@api.model
def create(self, vals):
result = super(Entretien, self).create(vals)
employee_type = self.env['res.partner'].browse(vals['employee_id'])
_logger.info("Employee id is : %s", employee_type.company_type)
# Generate a sequence number for the name field
if not vals.get('ref'):
vals['ref'] = self.env['ir.sequence'].next_by_code('gn_payroll.hr.contract.entretien') or "New"
_logger.info("Ref for entretien : %s", vals['ref'])
# Fetch contract to regenerated associated entretiens names
if vals.get('main_contract_id'):
contract = self.env['hr.contract'].browse(vals['main_contract_id'])
contract.update_entretiens_names()
if vals.get('event_ids'):
existing_entretien = self.search([('event_ids', 'in', vals['event_id'])], limit=1)
if existing_entretien:
raise ValidationError("An entretien is already associated with this event.")
return result

@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
import logging
_logger = logging.getLogger(__name__)
class ContractCareer(models.Model):
_name = "gn_payroll.career"
_description = "Analyse du poste"
_order = 'start_date, employee_id'
start_date = fields.Date('From', required=True, default=lambda self: fields.Date.today())
contract_id = fields.Many2one('gn_payroll.hr.contract', 'career_id', required=True, string="Contrat ou Avenant Associé")
main_contract_id = fields.Many2one('gn_payroll.hr.contract', compute='_compute_main_contract', string="Contract principal associé")
entretien_id = fields.Many2one('gn_payroll.hr.contract.entretien', string="Entretien professionnel associé")
classification_group = fields.Selection(selection='_get_groups_selection', string="Groupe associé au coefficient")
indice = fields.Integer(string="indice du salaire négocié (nombre de points)")
@api.constrains('entretien_id')
def _check_entretien_uniqueness(self):
for record in self:
if record.entretien_id:
# Check if any other career records have the same entretien_id
existing = self.search([
('id', '!=', record.id),
('entretien_id', '=', record.entretien_id.id),
])
if existing:
raise ValidationError("An entretien can only be associated with one career.")
@api.constrains('entretien_id')
def _check_contract_uniqueness(self):
for record in self:
if record.contract_id:
# Check if any other career records have the same entretien_id
existing = self.search([
('id', '!=', record.id),
('contract_id', '=', record.contract_id.id),
])
if existing:
raise ValidationError("An contract can only be associated with one career.")
@api.model
def create(self, vals):
career = super(ContractCareer, self).create(vals)
contract_id = vals.get('contract_id')
if contract_id:
contract = self.env['gn_payroll.hr.contract'].browse(contract_id)
if contract.parent_contract_id:
career.write({'main_contract_id': contract.parent_contract_id.id})
return career

@ -1,12 +1,17 @@
from odoo import models, fields
from odoo import models, fields, api
class GnPayrollHrEmployee(models.Model):
_inherit = 'hr.employee'
employee_type = fields.Selection([
('employee', 'Employé'),
('student', 'Étudiant'),
('trainee', 'Stagiaire'),
('volunteer', 'Volontaire'),
('benevolent', 'Bénévole'),
], string='Employee Type', default='employee', required=True,
help="The employee type. Although the primary purpose may seem to categorize employees, this field has also an impact in the Contract History. Only Employee type is supposed to be under contract and will have a Contract History.")
anciennete_start_date = fields.Date(compute='_compute_anciennete_start_date', groups='hr_group.hr_user', store=True)
@api.depends('contract_ids.state', 'contract_ids.date_start')
def _compute_anciennete_start_date(self):
for employee in self:
if employee.contract_id:
if employee.contract_id.parent_contract_id:
employee.anciennete_start_date = employee.contract_id.parent_contract_id.date_start
else:
employee.anciennete_start_date = employee.contract_id.date_start
else:
employee.anciennete_start_date = False

@ -0,0 +1,59 @@
from odoo import models, api
from datetime import datetime, time
class GnPayrollHrPayslip(models.Model):
_inherit = 'hr.payslip'
def _compute_sickness_days(self, contract, day_from, day_to):
"""
Worked days computation
@return: returns a list containing the total worked_days for the period
of the payslip. This returns the FULL work days expected for the resource
calendar selected for the employee (it don't substract leaves by default).
"""
work_data = contract.employee_id._get_work_days_data_batch(
day_from,
day_to,
calendar=contract.resource_calendar_id,
compute_leaves=False,
)
return {
"name": "Sickness days",
"sequence": 1,
"code": "SICKNESS",
"number_of_days": work_data[contract.employee_id.id]["days"],
"number_of_hours": work_data[contract.employee_id.id]["hours"],
"contract_id": contract.id,
}
@api.model
def get_worked_day_lines(self, contracts, date_from, date_to):
"""
@param contracts: Browse record of contracts
@return: returns a list of dict containing the input that should be
applied for the given contract between date_from and date_to
"""
res = []
for contract in contracts.filtered(
lambda contract: contract.resource_calendar_id
):
day_from = datetime.combine(date_from, time.min)
day_to = datetime.combine(date_to, time.max)
day_contract_start = datetime.combine(contract.date_start, time.min)
# Support for the hr_public_holidays module.
contract = contract.with_context(
employee_id=self.employee_id.id, exclude_public_holidays=True
)
# only use payslip day_from if it's greather than contract start date
if day_from < day_contract_start:
day_from = day_contract_start
# == compute leave days == #
leaves = self._compute_leave_days(contract, day_from, day_to)
res.extend(leaves)
# == compute worked days == #
attendances = self._compute_worked_days(contract, day_from, day_to)
res.append(attendances)
# == compute sicknss days == #
sickness = self._compute_sickness_days(contract, day_from, day_to)
res.append(sickness)
return res

@ -1,3 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_gn_payroll_cc_user,gn_payroll_cc_user,model_gn_payroll_cc,base.group_user,1,0,0,0
acces_gn_payroll_cc_admin,gn_payroll_cc_admin,model_gn_payroll_cc,hr_contract.group_hr_contract_manager,1,1,0,0
access_gn_payroll_hr_contract_entretien_user,gn_payroll_hr_contract_entretien_user,model_gn_payroll_hr_contract_entretien,base.group_user,1,0,0,0
access_gn_payroll_hr_contract_entretien_admin,gn_payroll_hr_contract_entretien_admin,model_gn_payroll_hr_contract_entretien,hr_contract.group_hr_contract_manager,1,1,1,1
access_gn_payroll_hr_contract_entretien_type_user,gn_payroll_hr_contract_entretien_type_user,model_gn_payroll_hr_contract_entretien_type,base.group_user,1,0,0,0
access_gn_payroll_hr_contract_entretien_type_admin,gn_payroll_hr_contract_entretien_type_admin,model_gn_payroll_hr_contract_entretien_type,hr_contract.group_hr_contract_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_gn_payroll_cc_user access_gn_payroll_hr_contract_entretien_user gn_payroll_cc_user gn_payroll_hr_contract_entretien_user model_gn_payroll_cc model_gn_payroll_hr_contract_entretien base.group_user 1 0 0 0
3 acces_gn_payroll_cc_admin access_gn_payroll_hr_contract_entretien_admin gn_payroll_cc_admin gn_payroll_hr_contract_entretien_admin model_gn_payroll_cc model_gn_payroll_hr_contract_entretien hr_contract.group_hr_contract_manager 1 1 0 1 0 1
4 access_gn_payroll_hr_contract_entretien_type_user gn_payroll_hr_contract_entretien_type_user model_gn_payroll_hr_contract_entretien_type base.group_user 1 0 0 0
5 access_gn_payroll_hr_contract_entretien_type_admin gn_payroll_hr_contract_entretien_type_admin model_gn_payroll_hr_contract_entretien_type hr_contract.group_hr_contract_manager 1 1 1 1

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_view_calendar_event_form" model="ir.ui.view">
<field name="name">gncalendar.event.form</field>
<field name="model">calendar.event</field>
<field name="inherit_id" ref="calendar.view_calendar_event_form"/>
<field name="priority" eval="1"/>
<field name="arch" type="xml">
<data>
<xpath expr="//field[@name='categ_ids']" position="after">
<field name="is_entretien"/>
<field name="entretien_ids" domain="[('event_id', '=', False)]" attrs="{'invisible': [('is_entretien', '=', False)]}"/>
</xpath>
</data>
</field>
</record>
</odoo>

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gn_payroll_entretien_form_view" model="ir.ui.view">
<field name="name">gn_payroll.hr.contract.entretien.form</field>
<field name="model">gn_payroll.hr.contract.entretien</field>
<field name="arch" type="xml">
<form string="Entretiens">
<sheet string="Entretien">
<div class="oe_title">
<h1>
<field name="name" placeholder="Entretien" />
</h1>
</div>
<group>
<field name="ref"/>
<field name="employee_id"/>
<field name="interviewer_id"/>
<field name="other_interviewer_ids"/>
<field name="other_participant_ids"/>
<field name="event_ids"/>
<field name="event_id"/>
<field name="start"/>
<field name="first_date"/>
<field name="next_date"/>
<field name="main_contract_id"/>
<field name="running_contract_id"/>
<field name="generated_contract_id"/>
<field name="type_id"/>
<field name="report"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="gn_payroll.hr_contract_entretien" model="ir.actions.act_window">
<field name="name">Entretiens</field>
<field name="res_model">gn_payroll.hr.contract.entretien</field>
<field name="view_mode">tree,form</field>
</record>
<record id="gn_payroll.hr_contract_entretien_type" model="ir.actions.act_window">
<field name="name">Types d'Entretiens</field>
<field name="res_model">gn_payroll.hr.contract.entretien.type</field>
<field name="view_mode">tree,form</field>
</record>
</odoo>
Loading…
Cancel
Save