From 6205cca9033ec7594a76daf8aff22d54b62bd339 Mon Sep 17 00:00:00 2001 From: Florian Egger Date: Wed, 9 Sep 2026 13:43:10 +0200 Subject: [PATCH] [ADD] odoo-at-payroll: repo bootstrap (AGENTS, skills, README, CHANGELOG, private skeleton) --- .agents/MEMORY.md | 56 + .agents/skills/agent-memory/SKILL.md | 50 + .agents/skills/odoo19-development/SKILL.md | 130 ++ .../references/coding_guidelines.rst | 1395 +++++++++++++++++ .../references/git_guidelines.rst | 145 ++ .agents/skills/payroll/SKILL.md | 151 ++ .gitignore | 3 + AGENTS.md | 188 +++ README.md | 42 + addons/l10n_at_hr_payroll_private/README.md | 9 + docs/CHANGELOG.md | 17 + 11 files changed, 2186 insertions(+) create mode 100644 .agents/MEMORY.md create mode 100644 .agents/skills/agent-memory/SKILL.md create mode 100644 .agents/skills/odoo19-development/SKILL.md create mode 100644 .agents/skills/odoo19-development/references/coding_guidelines.rst create mode 100644 .agents/skills/odoo19-development/references/git_guidelines.rst create mode 100644 .agents/skills/payroll/SKILL.md create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 README.md create mode 100644 addons/l10n_at_hr_payroll_private/README.md create mode 100644 docs/CHANGELOG.md diff --git a/.agents/MEMORY.md b/.agents/MEMORY.md new file mode 100644 index 0000000..6faa005 --- /dev/null +++ b/.agents/MEMORY.md @@ -0,0 +1,56 @@ +# Agent Memory — odoo-at-payroll + +## Current focus + +**Repo-Split aus gem360 abgeschlossen** (2026-09-09): Kern-Modul +`l10n_at_hr_payroll` (SV-Werte 2026, TASY-Import) + `l10n_at_gemeinde_payroll` +(AP1 Katalog, AP2 Entgelt-Engine) migriert; siehe `docs/CHANGELOG.md` und +`personalverrechnung/PLAN-repo-split.md`. Nächster Schritt: **AP3 (Lohnsteuer) +im Kern-Modul** gemäß `personalverrechnung/IMPLEMENTIERUNGSPLAN-Bgld.md` +(Abschnitt 4.2, Regel `LSTL`) — Implementierungsplan wartet weiterhin auf +die Freigaben des Bgld.-Auftrags (AP0 zuerst). + +## Completed + +- **Migration gem360 → odoo-at-payroll** (2026-09-09): dev-payroll nach + origin gepusht (Backup), History-Import per `git filter-repo` + (Branch hier: `main`), Modul-Split (TASY/SV-Layer in den Kern), + Model-Rename `l10n.at.payroll.tasy.*`, Skills/AGENTS/MEMORY neu + aufgesetzt, Manifest-Beschreibung auf AP1+AP2-Stand korrigiert. +- Vorher (aus gem360-Historie): AP1 Katalogmodell (35 Gruppen/433 Werte + 2026), AP2 Entgelt-Engine (13 Regeln, Structure GEMBG.BGLD), SV-Werte + aus ÖGK TASY-LSWH; 3 Testsuiten (Katalog, Entgelt, TASY-Import). + +## Open issues / blockers + +- gem360-seitig gilt: Branch `dev-payroll` dort vorerst erhalten, aber + **keine Payroll-Implementierung mehr in gem360** und kein Merge nach + dev/va_module (Guardrails im gem360-Handoff). Die Brücke + `l10n_at_gemeinde_payroll_vrv` (AP7) wird in gem360 gegen getaggte + Releases dieses Repos gebaut. +- Bgld.-Auftrag: AP0 offen (Bestandsaufnahme Pilotmandant, ELDA/FinOnl- + Specs, VRV-Buchungsmatrix, offene RIS-Verifikationen) — + `IMPLEMENTIERUNGSPLAN-Bgld.md` Abschnitt 8. +- Odoo-Upgrades künftig in beiden Repos (gem360 + dieses) gegen denselben + Odoo-Pin validieren. + +## Decisions & conventions + +- SKILL.md braucht YAML-Frontmatter (name = Verzeichnisname, description, + disable-model-invocation). +- Abhängigkeitsrichtung: gem360 → dieses Repo (nur die Brücke hängt an + beiden), nie umgekehrt. +- Tests nur auf Wegwerf-DB (nie `gem360_dev`); je Änderung py_compile + + XML-Well-formedness-Check. +- `.firecrawl/` bleibt lokal unversioniert (Rechts-Rohquellen); bei Fehlen + neu beschaffen — niemals aus Trainingswerten arbeiten. + +## Files that matter right now + +- `personalverrechnung/PLAN-repo-split.md` — Migration (abgeschlossen) +- `personalverrechnung/IMPLEMENTIERUNGSPLAN-Bgld.md` — AP3+ (Freigabe-Gate) +- `personalverrechnung/RECHTSQUELLEN-Bgld.md` — verbindliche Rechtslage +- `personalverrechnung/ANGEBOTS-KALKULATION-Bgld-Personalverrechnung.md` — + AP-Rahmen, Prämissen P1–P10 +- `addons/l10n_at_hr_payroll/` — Kern (SV/TASY; AP3 landet hier) +- `addons/l10n_at_gemeinde_payroll/` — GemBG (AP1+AP2) \ No newline at end of file diff --git a/.agents/skills/agent-memory/SKILL.md b/.agents/skills/agent-memory/SKILL.md new file mode 100644 index 0000000..9fac936 --- /dev/null +++ b/.agents/skills/agent-memory/SKILL.md @@ -0,0 +1,50 @@ +--- +name: agent-memory +description: | + Persistent handoff memory for multi-session agent work on the + odoo-at-payroll project. Read this skill at the start of every new agent + conversation so the current thread can bootstrap context from the + repository rather than from chat history. +disable-model-invocation: false +--- + +## Always read at the start of a new conversation + +1. `AGENTS.md` — mandatory project workflow. +2. This skill (`agent-memory/SKILL.md`). +3. `.agents/MEMORY.md` — current handoff log. +4. All other skills applicable to the task (see `AGENTS.md` skill + selection). + +## Purpose + +Zed agent threads do not share conversation history. This project therefore +keeps the shared state in the repository itself: + +- `.agents/MEMORY.md` — rolling handoff log: current focus, completed work, + open blockers, decisions, files that matter. +- `/RUNBOOK.md` — operational memory for recurring workflows + (imports, validation, deployment). + +Both files are ordinary markdown under git, so their history is preserved. + +## After significant work + +Update `.agents/MEMORY.md`: + +- **Current focus**: one-line summary of what the thread was working on. +- **Completed**: concrete outcomes, file paths, commits. +- **Open issues / blockers**: anything unresolved at the end of the thread. +- **Decisions & conventions**: choices that future threads must respect. +- **Files that matter right now**: paths the next thread should read first. + +Update a domain `RUNBOOK.md` when the task reveals a reusable observation: + +- non-obvious mappings or workarounds; +- validation steps that caught errors; +- commands or snippets that should be reused. + +## When starting a new thread + +Paste a brief handoff if helpful, but **do not rely on it**. Always verify +the actual current state from the memory files, git log, and the relevant code. \ No newline at end of file diff --git a/.agents/skills/odoo19-development/SKILL.md b/.agents/skills/odoo19-development/SKILL.md new file mode 100644 index 0000000..956963a --- /dev/null +++ b/.agents/skills/odoo19-development/SKILL.md @@ -0,0 +1,130 @@ +--- +name: odoo19-development +description: | + Guidance for developing and maintaining Odoo 19 Enterprise modules. +disable-model-invocation: false +--- + +## Required source material + +Before implementation, consult: + +- Odoo 19 source available in the repository/environment; +- Odoo 19 development coding guidelines; +- Odoo 19 Git guidelines; +- existing implementations of the functionality being changed. + +Do not rely solely on knowledge from other Odoo versions. + +## Framework-first development + +Odoo already provides extensive infrastructure. Before writing custom code, +search for an existing implementation. + +Look for: + +- existing models; +- inherited models; +- mixins; +- computed fields; +- constraints; +- ORM helpers; +- views; +- actions; +- security mechanisms; +- `account.report` infrastructure; +- existing localization patterns; +- standard accounting functionality. + +Extend or compose existing functionality whenever practical. + +Avoid: + +- monkey patching; +- duplicating standard Odoo functionality; +- unnecessary overrides; +- custom infrastructure where an Odoo mechanism exists; +- version-specific APIs copied from older Odoo releases. + +## API verification + +Every referenced API must be verified. + +Before using a model, field, method, XML ID, module, package, or framework API: + +1. search the current Odoo 19 source; +2. verify the exact name and signature/behavior; +3. inspect callers or existing implementations where useful; +4. only then use it. + +Never invent plausible Odoo APIs or Python dependencies. + +## Repository exploration + +Use search strategically. + +For an unfamiliar feature: + +1. find the relevant model; +2. find existing implementations; +3. inspect inheritance; +4. inspect views/actions/security; +5. inspect tests; +6. identify the smallest appropriate extension point. + +Do not start implementation immediately after finding the first apparently +relevant file. + +## Odoo conventions + +Follow the project's Odoo 19 coding guidelines. + +Prefer: + +- ORM operations; +- declarative fields and constraints; +- proper model inheritance; +- standard security mechanisms; +- standard views and actions; +- existing framework abstractions. + +Avoid unnecessary SQL, low-level manipulation, and custom abstractions. + +## Odoo 19 references + +The following pinned Odoo 19 documentation is included with this skill: + +- `references/coding_guidelines.rst` +- `references/git_guidelines.rst` + +These documents are authoritative for Odoo coding and Git conventions in this +repository. + +When a conflict exists between general knowledge and these references, follow +the pinned Odoo 19 references. + +## Testing + +Every functional change should have appropriate tests. + +Tests should verify behavior rather than implementation details. + +For accounting functionality, include relevant: + +- company behavior; +- currency behavior; +- dates/fiscal periods; +- posted vs draft records; +- reconciliation; +- access rights; +- accounting edge cases. + +## Maintainability + +Optimize for long-term Odoo upgradeability. + +Prefer small, idiomatic extensions over large replacements of standard behavior. + +Avoid unrelated refactoring. + +If standard functionality is intentionally not reused, document why. diff --git a/.agents/skills/odoo19-development/references/coding_guidelines.rst b/.agents/skills/odoo19-development/references/coding_guidelines.rst new file mode 100644 index 0000000..d6736e8 --- /dev/null +++ b/.agents/skills/odoo19-development/references/coding_guidelines.rst @@ -0,0 +1,1395 @@ +.. highlight:: python + +================= +Coding guidelines +================= + +This page introduces the Odoo Coding Guidelines. Those aim to improve the +quality of Odoo Apps code. Indeed proper code improves readability, eases +maintenance, helps debugging, lowers complexity and promotes reliability. +These guidelines should be applied to every new module and to all new development. + +.. warning:: + + When modifying existing files in **stable version** the original file style + strictly supersedes any other style guidelines. In other words please never + modify existing files in order to apply these guidelines. It avoids disrupting + the revision history of code lines. Diff should be kept minimal. For more + details, see our `pull request guide `_. + +.. warning:: + + When modifying existing files in **master (development) version** apply those + guidelines to existing code only for modified code or if most of the file is + under revision. In other words modify existing files structure only if it is + going under major changes. In that case first do a **move** commit then apply + the changes related to the feature. + +Module structure +================ + +.. warning:: + + For modules developed by the community, it is strongly recommended to name + your module with a prefix like your company name. + +Directories +----------- + +A module is organized in important directories. Those contain the business logic; +having a look at them should make you understand the purpose of the module. + +- *data/* : demo and data xml +- *models/* : models definition +- *controllers/* : contains controllers (HTTP routes) +- *views/* : contains the views and templates +- *static/* : contains the web assets, separated into *css/, js/, img/, lib/, ...* + +Other optional directories compose the module. + +- *wizard/* : regroups the transient models (``models.TransientModel``) and their views +- *report/* : contains the printable reports and models based on SQL views. Python objects and XML views are included in this directory +- *tests/* : contains the Python tests + + +File naming +----------- + +File naming is important to quickly find information through all odoo addons. +This section explains how to name files in a standard odoo module. As an +example we use a `plant nursery `_ application. +It holds two main models *plant.nursery* and *plant.order*. + +Concerning *models*, split the business logic by sets of models belonging to +a same main model. Each set lies in a given file named based on its main model. +If there is only one model, its name is the same as the module name. Each +inherited model should be in its own file to help understanding of impacted +models. + +.. code-block:: text + + addons/plant_nursery/ + |-- models/ + | |-- plant_nursery.py (first main model) + | |-- plant_order.py (another main model) + | |-- res_partner.py (inherited Odoo model) + +Concerning *security*, three main files should be used: + +- First one is the definition of access rights done in a :file:`ir.model.access.csv` file. +- User groups are defined in :file:`_groups.xml`. +- Record rules are defined in :file:`_security.xml`. + +.. code-block:: text + + addons/plant_nursery/ + |-- security/ + | |-- ir.model.access.csv + | |-- plant_nursery_groups.xml + | |-- plant_nursery_security.xml + | |-- plant_order_security.xml + +Concerning *views*, backend views should be split like models and suffixed +by ``_views.xml``. Backend views are list, form, kanban, activity, graph, pivot, .. +views. To ease split by model in views main menus not linked to specific actions +may be extracted into an optional ``_menus.xml`` file. Templates (QWeb +pages used notably for portal / website display) are put in separate files named +``_templates.xml``. + +.. code-block:: text + + addons/plant_nursery/ + |-- views/ + | | -- plant_nursery_menus.xml (optional definition of main menus) + | | -- plant_nursery_views.xml (backend views) + | | -- plant_nursery_templates.xml (portal templates) + | | -- plant_order_views.xml + | | -- plant_order_templates.xml + | | -- res_partner_views.xml + +Concerning *data*, split them by purpose (demo or data) and main model. Filenames +will be the main_model name suffixed by ``_demo.xml`` or ``_data.xml``. For instance +for an application having demo and data for its main model as well as subtypes, +activities and mail templates all related to mail module: + +.. code-block:: text + + addons/plant_nursery/ + |-- data/ + | |-- plant_nursery_data.xml + | |-- plant_nursery_demo.xml + | |-- mail_data.xml + +Concerning *controllers*, generally all controllers belong to a single controller +contained in a file named ``.py``. An old convention in Odoo is to +name this file ``main.py`` but it is considered as outdated. If you need to inherit +an existing controller from another module do it in ``.py``. +For example adding portal controller in an application is done in ``portal.py``. + +.. code-block:: text + + addons/plant_nursery/ + |-- controllers/ + | |-- plant_nursery.py + | |-- portal.py (inheriting portal/controllers/portal.py) + | |-- main.py (deprecated, replaced by plant_nursery.py) + +Concerning *static files*, Javascript files follow globally the same logic as +python models. Each component should be in its own file with a meaningful name. +For instance, the activity widgets are located in ``activity.js`` of mail module. +Subdirectories can also be created to structure the 'package' (see web module +for more details). The same logic should be applied for the templates of JS +widgets (static XML files) and for their styles (scss files). Don't link +data (image, libraries) outside Odoo: do not use an URL to an image but copy +it in the codebase instead. + +Concerning *wizards*, naming convention is the same of for python models: +``.py`` and ``_views.xml``. Both are put in the wizard +directory. This naming comes from old odoo applications using the wizard +keyword for transient models. + +.. code-block:: text + + addons/plant_nursery/ + |-- wizard/ + | |-- make_plant_order.py + | |-- make_plant_order_views.xml + +Concerning *statistics reports* done with python / SQL views and classic views +naming is the following : + +.. code-block:: text + + addons/plant_nursery/ + |-- report/ + | |-- plant_order_report.py + | |-- plant_order_report_views.xml + +Concerning *printable reports* which contain mainly data preparation and Qweb +templates naming is the following : + +.. code-block:: text + + addons/plant_nursery/ + |-- report/ + | |-- plant_order_reports.xml (report actions, paperformat, ...) + | |-- plant_order_templates.xml (xml report templates) + +The complete tree of our Odoo module therefore looks like + +.. code-block:: text + + addons/plant_nursery/ + |-- __init__.py + |-- __manifest__.py + |-- controllers/ + | |-- __init__.py + | |-- plant_nursery.py + | |-- portal.py + |-- data/ + | |-- plant_nursery_data.xml + | |-- plant_nursery_demo.xml + | |-- mail_data.xml + |-- models/ + | |-- __init__.py + | |-- plant_nursery.py + | |-- plant_order.py + | |-- res_partner.py + |-- report/ + | |-- __init__.py + | |-- plant_order_report.py + | |-- plant_order_report_views.xml + | |-- plant_order_reports.xml (report actions, paperformat, ...) + | |-- plant_order_templates.xml (xml report templates) + |-- security/ + | |-- ir.model.access.csv + | |-- plant_nursery_groups.xml + | |-- plant_nursery_security.xml + | |-- plant_order_security.xml + |-- static/ + | |-- img/ + | | |-- my_little_kitten.png + | | |-- troll.jpg + | |-- lib/ + | | |-- external_lib/ + | |-- src/ + | | |-- js/ + | | | |-- widget_a.js + | | | |-- widget_b.js + | | |-- scss/ + | | | |-- widget_a.scss + | | | |-- widget_b.scss + | | |-- xml/ + | | | |-- widget_a.xml + | | | |-- widget_a.xml + |-- views/ + | |-- plant_nursery_menus.xml + | |-- plant_nursery_views.xml + | |-- plant_nursery_templates.xml + | |-- plant_order_views.xml + | |-- plant_order_templates.xml + | |-- res_partner_views.xml + |-- wizard/ + | |--make_plant_order.py + | |--make_plant_order_views.xml + +.. note:: File names should only contain ``[a-z0-9_]`` (lowercase + alphanumerics and ``_``) + +.. warning:: Use correct file permissions : folder 755 and file 644. + +.. _contributing/development/xml_guidelines: + +XML files +========= + +Format +------ + +To declare a record in XML, the **record** notation (using **) is recommended: + +- Place ``id`` attribute before ``model`` +- For field declaration, ``name`` attribute is first. Then place the + *value* either in the ``field`` tag, either in the ``eval`` + attribute, and finally other attributes (widget, options, ...) + ordered by importance. + +- Try to group the record by model. In case of dependencies between + action/menu/views, this convention may not be applicable. +- Use naming convention defined at the next point +- The tag ** is only used to set not-updatable data with ``noupdate=1``. + If there is only not-updatable data in the file, the ``noupdate=1`` can be + set on the ```` tag and do not set a ```` tag. + +.. code-block:: xml + + + view.name + object_name + + + + + + + + + +Odoo supports custom tags acting as syntactic sugar: + +- menuitem: use it as a shortcut to declare a ``ir.ui.menu`` +- template: use it to declare a QWeb View requiring only the ``arch`` section of the view. + +These tags are preferred over the *record* notation. + + +XML IDs and naming +------------------ + +Security, View and Action +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the following pattern : + +* For a menu: :samp:`{}_menu`, or :samp:`{}_menu_{do_stuff}` for submenus. +* For a view: :samp:`{}_view_{}`, where *view_type* is + ``kanban``, ``form``, ``list``, ``search``, ... +* For an action: the main action respects :samp:`{}_action`. + Others are suffixed with :samp:`_{}`, where *detail* is a + lowercase string briefly explaining the action. This is used only if + multiple actions are declared for the model. +* For window actions: suffix the action name by the specific view information + like :samp:`{}_action_view_{}`. +* For a group: :samp:`{}_group_{}` where *group_name* + is the name of the group, generally 'user', 'manager', ... +* For a rule: :samp:`{}_rule_{}` where + *concerned_group* is the short name of the concerned group ('user' + for the 'model_name_group_user', 'public' for public user, 'company' + for multi-company rules, ...). + +Name should be identical to xml id with dots replacing underscores. Actions +should have a real naming as it is used as display name. + +.. code-block:: xml + + + + model.name.view.form + ... + + + + model.name.view.kanban + ... + + + + + Model Main Action + ... + + + + Model Access Children + + + + + + + + + ... + + + + ... + + + + ... + + +Inheriting XML +~~~~~~~~~~~~~~ + +Xml Ids of inheriting views should use the same ID as the original record. +It helps finding all inheritance at a glance. As final Xml Ids are prefixed +by the module that creates them there is no overlap. + +Naming should contain an ``.inherit.{details}`` suffix to ease understanding +the override purpose when looking at its name. + +.. code-block:: xml + + + model.view.form.inherit.module2 + + ... + + +New primary views do not require the inherit suffix as those are new records +based upon the first one. + +.. code-block:: xml + + + model.view.form.module2 + + primary + ... + + +.. _contributing/development/python_guidelines: + +Python +====== + +.. warning:: + + Do not forget to read the :ref:`Security Pitfalls ` + section as well to write secure code. + +PEP8 options +------------ + +Using a linter can help show syntax and semantic warnings or errors. Odoo +source code tries to respect Python standard, but some of them can be ignored. + +- E501: line too long +- E301: expected 1 blank line, found 0 +- E302: expected 2 blank lines, found 1 + +Imports +------- + +The imports are ordered as + +#. External libraries (one per line sorted and split in python stdlib) +#. Imports of ``odoo`` submodules +#. Imports from Odoo addons (rarely, and only if necessary) + +Inside these 3 groups, the imported lines are alphabetically sorted. + +.. code-block:: python + + # 1 : imports of python lib + import base64 + import re + import time + from datetime import datetime + # 2 : imports of odoo + from odoo import Command, _, api, fields, models # ASCIIbetically ordered + from odoo.fields import Domain + from odoo.tools.safe_eval import safe_eval as eval + # 3 : imports from odoo addons + from odoo.addons.web.controllers.main import login_redirect + from odoo.addons.website.models.website import slug + +Idiomatics of Programming (Python) +---------------------------------- + +- Always favor *readability* over *conciseness* or using the language features or idioms. +- Don't use ``.clone()`` + +.. code-block:: python + + # bad + new_dict = my_dict.clone() + new_list = old_list.clone() + # good + new_dict = dict(my_dict) + new_list = list(old_list) + +- Python dictionary : creation and update + +.. code-block:: python + + # -- creation empty dict + my_dict = {} + my_dict2 = dict() + + # -- creation with values + # bad + my_dict = {} + my_dict['foo'] = 3 + my_dict['bar'] = 4 + # good + my_dict = {'foo': 3, 'bar': 4} + + # -- update dict + # bad + my_dict['foo'] = 3 + my_dict['bar'] = 4 + my_dict['baz'] = 5 + # good + my_dict.update(foo=3, bar=4, baz=5) + my_dict = dict(my_dict, **my_dict2) + +- Use meaningful variable/class/method names +- Useless variable : Temporary variables can make the code clearer by giving + names to objects, but that doesn't mean you should create temporary variables + all the time: + +.. code-block:: python + + # pointless + schema = kw['schema'] + params = {'schema': schema} + # simpler + params = {'schema': kw['schema']} + +- Multiple return points are OK, when they're simpler + +.. code-block:: python + + # a bit complex and with a redundant temp variable + def axes(self, axis): + axes = [] + if type(axis) == type([]): + axes.extend(axis) + else: + axes.append(axis) + return axes + + # clearer + def axes(self, axis): + if type(axis) == type([]): + return list(axis) # clone the axis + else: + return [axis] # single-element list + +- Know your builtins : You should at least have a basic understanding of all + the Python builtins (http://docs.python.org/library/functions.html) + +.. code-block:: python + + value = my_dict.get('key', None) # very very redundant + value = my_dict.get('key') # good + +Also, ``if 'key' in my_dict`` and ``if my_dict.get('key')`` have very different +meaning, be sure that you're using the right one. + +- Learn list comprehensions : Use list comprehension, dict comprehension, and + basic manipulation using ``map``, ``filter``, ``sum``, ... They make the code + easier to read. + +.. code-block:: python + + # not very good + cube = [] + for i in res: + cube.append((i['id'],i['name'])) + # better + cube = [(i['id'], i['name']) for i in res] + +- Collections are booleans too : In python, many objects have "boolean-ish" value + when evaluated in a boolean context (such as an if). Among these are collections + (lists, dicts, sets, ...) which are "falsy" when empty and "truthy" when containing + items: + +.. code-block:: python + + bool([]) is False + bool([1]) is True + bool([False]) is True + +So, you can write ``if some_collection:`` instead of ``if len(some_collection):``. + + +- Iterate on iterables + +.. code-block:: python + + # creates a temporary list and looks bar + for key in my_dict.keys(): + "do something..." + # better + for key in my_dict: + "do something..." + # accessing the key,value pair + for key, value in my_dict.items(): + "do something..." + +- Use dict.setdefault + +.. code-block:: python + + # longer.. harder to read + values = {} + for element in iterable: + if element not in values: + values[element] = [] + values[element].append(other_value) + + # better.. use dict.setdefault method + values = {} + for element in iterable: + values.setdefault(element, []).append(other_value) + +- As a good developer, document your code (docstring on methods, simple + comments for tricky part of code) +- In additions to these guidelines, you may also find the following link + interesting: https://david.goodger.org/projects/pycon/2007/idiomatic/handout.html + (a little bit outdated, but quite relevant) + +Programming in Odoo +------------------- + +- Avoid to create generators and decorators: only use the ones provided by + the Odoo API. +- As in python, use ``filtered``, ``mapped``, ``sorted``, ... methods to + ease code reading and performance. + +Propagate the context +~~~~~~~~~~~~~~~~~~~~~ + +The context is a ``frozendict`` that cannot be modified. To call a method with +a different context, the ``with_context`` method should be used : + +.. code-block:: python + + records.with_context(new_context).do_stuff() # all the context is replaced + records.with_context(**additionnal_context).do_other_stuff() # additionnal_context values override native context ones + +.. warning:: + Passing parameter in context can have dangerous side-effects. + + Since the values are propagated automatically, some unexpected behavior may appear. + Calling ``create()`` method of a model with *default_my_field* key in context + will set the default value of *my_field* for the concerned model. + But if during this creation, other objects (such as sale.order.line, on sale.order creation) + having a field name *my_field* are created, their default value will be set too. + +If you need to create a key context influencing the behavior of some object, +choose a good name, and eventually prefix it by the name of the module to +isolate its impact. A good example are the keys of ``mail`` module : +*mail_create_nosubscribe*, *mail_notrack*, *mail_notify_user_signature*, ... + +Think extendable +~~~~~~~~~~~~~~~~ + +Functions and methods should not contain too much logic: having a lot of small +and simple methods is more advisable than having few large and complex methods. +A good rule of thumb is to split a method as soon as it has more than one +responsibility (see http://en.wikipedia.org/wiki/Single_responsibility_principle). + +Hardcoding a business logic in a method should be avoided as it prevents to be +easily extended by a submodule. + +.. code-block:: python + + # do not do this + # modifying the domain or criteria implies overriding whole method + def action(self): + ... # long method + partners = self.env['res.partner'].search(complex_domain) + emails = partners.filtered(lambda r: arbitrary_criteria).mapped('email') + + # better but do not do this either + # modifying the logic forces to duplicate some parts of the code + def action(self): + ... + partners = self._get_partners() + emails = partners._get_emails() + + # better + # minimum override + def action(self): + ... + partners = self.env['res.partner'].search(self._get_partner_domain()) + emails = partners.filtered(lambda r: r._filter_partners()).mapped('email') + +The above code is over extendable for the sake of example but the readability +must be taken into account and a tradeoff must be made. + +Also, name your functions accordingly: small and properly named functions are +the starting point of readable/maintainable code and tighter documentation. + +This recommendation is also relevant for classes, files, modules and packages. +(See also http://en.wikipedia.org/wiki/Cyclomatic_complexity) + +Never commit the transaction +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Odoo framework is in charge of providing the transactional context for +all RPC calls. +All ``cr.commit()`` calls outside of the server framework must +have an **explicit comment** explaining why they are absolutely necessary, why +they are indeed correct, and why they do not break the transactions. Otherwise +they can and will be removed! + +The principle is that a new database cursor is opened at the beginning of each +RPC call, and committed when the call has returned, just before transmitting the +answer to the RPC client, approximately like this: + +.. code-block:: python + + def execute(self, db_name, uid, obj, method, *args, **kw): + db, pool = pooler.get_db_and_pool(db_name) + # create transaction cursor + cr = db.cursor() + try: + res = pool.execute_cr(cr, uid, obj, method, *args, **kw) + cr.commit() # all good, we commit + except Exception: # try to be more specific + cr.rollback() # error, rollback everything atomically + raise + finally: + cr.close() # always close cursor opened manually + return res + +If any error occurs during the execution of the RPC call, the transaction is +rolled back atomically, preserving the state of the system. + +Similarly, the system also provides a dedicated transaction during the execution +of tests suites and scheduled actions. + +The consequence is that if you manually call ``cr.commit()`` anywhere there is +a very high chance that you will break the system in various ways, because you +will cause partial commits, and thus partial and unclean rollbacks, causing +among others: + +#. inconsistent business data, usually data loss +#. workflow desynchronization, documents stuck permanently +#. tests that can't be rolled back cleanly, and will start polluting the + database, and triggering error (this is true even if no error occurs + during the transaction) + +Here is the very simple rule: + You should **NEVER** call ``cr.commit()`` or ``cr.rollback()`` yourself, + **UNLESS** you have explicitly created your own database cursor! + And the situations in which you need to do this are exceptional! + + And by the way if you did create your own cursor, then you need to handle + error cases and proper rollback, as well as properly close the cursor when + you're done with it. + +And contrary to popular belief, you do not even need to call ``cr.commit()`` +in the following situations: + +- in the ``_auto_init()`` method of an *models.Model* object: this is taken + care of by the addons initialization method, or by the ORM transaction when + creating custom models +- in reports: the ``commit()`` is handled by the framework too, so you can + update the database even from within a report +- within *models.Transient* methods: these methods are called exactly like + regular *models.Model* ones, within a transaction and with the corresponding + ``cr.commit()/rollback()`` at the end +- etc. (see general rule above if you are in doubt!) + +Avoid catching exceptions +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Catch only specific exceptions, and avoid overly broad exception handling. +Uncaught exceptions will be logged and handled properly by the framework. + +You should be specific about the types you catch and handle them +accordingly, and you should limit the scope of your try-catch block as much +as possible. + +.. code-block:: python + + # BAD CODE + try: + do_something() + except Exception as e: + # if we caught a ValidationError, we did not rollback and we left the + # ORM in an undefined state + _logger.warning(e) + +For scheduled actions, you should rollback the changes if you catch errors and +wish to continue. Scheduled actions run in a separate transaction, so you can +rollback or commit directly when you signal progress. + +.. seealso:: + :ref:`reference/actions/cron` + +If you must handle framework exceptions, you must use **savepoints** +to isolate your function as much as possible. +This will flush the computations when entering the block and rollback changes +properly in case of exceptions. + +.. code-block:: python + + try: + with self.env.cr.savepoint(): + do_stuff() + except ...: + ... + +.. warning:: + + After you start more than 64 savepoints during a single transaction, + PostgreSQL will slow down. + In all cases, if the server runs replicas, savepoints have a huge overhead. + If you process records and savepoint in a loop, for example when processing + records one by one for a batch, limit the size of the batch. + If you have more records, the function should maybe become a scheduled job + or you have to accept the performance penalty. + +Use translation method correctly +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Odoo uses a GetText-like method named "underscore" ``_()`` to indicate that +a static string used in the code needs to be translated at runtime. +That method is available at ``self.env._`` using the language of the +environment. + +A few very important rules must be followed when using it, in order for it to +work and to avoid filling the translations with useless junk. + +Basically, this method should only be used for static strings written manually +in the code, it will not work to translate field values, such as Product names, +etc. This must be done instead using the translate flag on the corresponding +field. + +The method accepts optional positional or named parameter +The rule is very simple: calls to the underscore method should always be in +the form ``self.env._('literal string')`` and nothing else: + +.. code-block:: python + + _ = self.env._ + + # good: plain strings + error = _('This record is locked!') + + # good: strings with formatting patterns included + error = _('Record %s cannot be modified!', record) + + # ok too: multi-line literal strings + error = _("""This is a bad multiline example + about record %s!""", record) + error = _('Record %s cannot be modified' \ + 'after being validated!', record) + + # bad: tries to translate after string formatting + # (pay attention to brackets!) + # This does NOT work and messes up the translations! + error = _('Record %s cannot be modified!' % record) + + # bad: formatting outside of translation + # This won't benefit from fallback mechanism in case of bad translation + error = _('Record %s cannot be modified!') % record + + # bad: dynamic string, string concatenation, etc are forbidden! + # This does NOT work and messes up the translations! + error = _("'" + que_rec['question'] + "' \n") + + # bad: field values are automatically translated by the framework + # This is useless and will not work the way you think: + error = _("Product %s is out of stock!") % _(product.name) + # and the following will of course not work as already explained: + error = _("Product %s is out of stock!" % product.name) + + # Instead you can do the following and everything will be translated, + # including the product name if its field definition has the + # translate flag properly set: + error = _("Product %s is not available!", product.name) + + +Also, keep in mind that translators will have to work with the literal values +that are passed to the underscore function, so please try to make them easy to +understand and keep spurious characters and formatting to a minimum. Translators +must be aware that formatting patterns such as ``%s`` or ``%d``, newlines, etc. +need to be preserved, but it's important to use these in a sensible and obvious +manner: + +.. code-block:: python + + # Bad: makes the translations hard to work with + error = "'" + question + _("' \nPlease enter an integer value ") + + # Ok (pay attention to position of the brackets too!) + error = _("Answer to question %s is not valid.\n" \ + "Please enter an integer value.", question) + + # Better + error = _("Answer to question %(title)s is not valid.\n" \ + "Please enter an integer value.", title=question) + +In general in Odoo, when manipulating strings, prefer ``%`` over ``.format()`` +(when only one variable to replace in a string), and prefer ``%(varname)`` instead +of position (when multiple variables have to be replaced). This makes the +translation easier for the community translators. + +Symbols and Conventions +----------------------- + +- Model name (using the dot notation, prefix by the module name) : + - When defining an Odoo Model : use singular form of the name (*res.partner* + and *sale.order* instead of *res.partnerS* and *saleS.orderS*) + - When defining an Odoo Transient (wizard) : use ``.`` + where *related_base_model* is the base model (defined in *models/*) related + to the transient, and *action* is the short name of what the transient do. Avoid the *wizard* word. + For instance : ``account.invoice.make``, ``project.task.delegate.batch``, ... + - When defining *report* model (SQL views e.i.) : use + ``.report.``, based on the Transient convention. + +- Odoo Python Class : use Pascal case (Object-oriented style). + + +.. code-block:: python + + class AccountInvoice(models.Model): + ... + +- Variable name : + - use Pascal case for model variable + - use underscore lowercase notation for common variable. + - suffix your variable name with *_id* or *_ids* if it contains a record id or list of id. Don't use ``partner_id`` to contain a record of res.partner + +.. code-block:: python + + Partner = self.env['res.partner'] + partners = Partner.browse(ids) + partner_id = partners[0].id + +- ``One2Many`` and ``Many2Many`` fields should always have *_ids* as suffix (example: sale_order_line_ids) +- ``Many2One`` fields should have *_id* as suffix (example : partner_id, user_id, ...) +- Method conventions + - Compute Field : the compute method pattern is *_compute_* + - Search method : the search method pattern is *_search_* + - Default method : the default method pattern is *_default_* + - Selection method: the selection method pattern is *_selection_* + - Onchange method : the onchange method pattern is *_onchange_* + - Constraint method : the constraint method pattern is *_check_* + - Action method : an object action method is prefix with *action_*. + Since it uses only one record, add ``self.ensure_one()`` + at the beginning of the method. + +- In a Model attribute order should be + #. Private attributes (``_name``, ``_description``, ``_inherit``, ...) + #. Default method and ``default_get`` + #. Field declarations + #. SQL constraints and indexes + #. Compute, inverse and search methods in the same order as field declaration + #. Selection method (methods used to return computed values for selection fields) + #. Constrains methods (``@api.constrains``) and onchange methods (``@api.onchange``) + #. CRUD methods (ORM overrides) + #. Action methods + #. And finally, other business methods. + +.. code-block:: python + + class Event(models.Model): + # Private attributes + _name = 'event.event' + _description = 'Event' + + # Default methods + def _default_name(self): + ... + + # Fields declaration + name = fields.Char(string='Name', default=_default_name) + seats_reserved = fields.Integer(string='Reserved Seats', store=True + readonly=True, compute='_compute_seats') + seats_available = fields.Integer(string='Available Seats', store=True + readonly=True, compute='_compute_seats') + price = fields.Integer(string='Price') + event_type = fields.Selection(string="Type", selection='_selection_type') + + # compute and search fields, in the same order of fields declaration + @api.depends('seats_max', 'registration_ids.state', 'registration_ids.nb_register') + def _compute_seats(self): + ... + + @api.model + def _selection_type(self): + return [] + + # Constraints and onchanges + @api.constrains('seats_max', 'seats_available') + def _check_seats_limit(self): + ... + + @api.onchange('date_begin') + def _onchange_date_begin(self): + ... + + # CRUD methods (and name_search, _search, ...) overrides + @api.model + def create(self, vals_list): + ... + + # Action methods + def action_validate(self): + self.ensure_one() + ... + + # Business methods + def mail_user_confirm(self): + ... + +.. _contributing/development/js_guidelines: + +Javascript +========== + +Static files organization +------------------------- + +Odoo addons have some conventions on how to structure various files. We explain +here in more details how web assets are supposed to be organized. + +The first thing to know is that the Odoo server will serve (statically) all files +located in a *static/* folder, but prefixed with the addon name. So, for example, +if a file is located in *addons/web/static/src/js/some_file.js*, then it will be +statically available at the url *your-odoo-server.com/web/static/src/js/some_file.js* + +The convention is to organize the code according to the following structure: + +- *static*: all static files in general + + - *static/lib*: this is the place where js libs should be located, in a sub folder. + So, for example, all files from the *jquery* library are in *addons/web/static/lib/jquery* + - *static/src*: the generic static source code folder + + - *static/src/css*: all css files + - *static/fonts* + - *static/img* + - *static/src/js* + + - *static/src/js/tours*: end user tour files (tutorials, not tests) + + - *static/src/scss*: scss files + - *static/src/xml*: all qweb templates that will be rendered in JS + + - *static/tests*: this is where we put all test related files. + + - *static/tests/tours*: this is where we put all tour test files (not tutorials). + +Javascript coding guidelines +---------------------------- + +- ``use strict;`` is recommended for all javascript files +- Use a linter (jshint, ...) +- Never add minified Javascript Libraries +- Use Pascal case for class declaration + +More precise JS guidelines are detailed in the `github wiki `_. +You may also have a look at existing API in Javascript by looking Javascript +References. + +.. _contributing/coding_guidelines/scss: + +CSS and SCSS +============ + +.. _contributing/coding_guidelines/scss/formatting: + +Syntax and Formatting +--------------------- + +.. tabs:: + + .. code-tab:: html SCSS + + .o_foo, .o_foo_bar, .o_baz { + height: $o-statusbar-height; + + .o_qux { + height: $o-statusbar-height * 0.5; + } + } + + .o_corge { + background: $o-list-footer-bg-color; + } + + .. code-tab:: css CSS + + .o_foo, .o_foo_bar, .o_baz { + height: 32px; + } + + .o_foo .o_quux, .o_foo_bar .o_quux, .o_baz .o_qux { + height: 16px; + } + + .o_corge { + background: #EAEAEA; + } + +- four (4) space indents, no tabs; +- columns of max. 80 characters wide; +- opening brace (`{`): empty space after the last selector; +- closing brace (`}`): on its own new line; +- one line for each declaration; +- meaningful use of whitespace. + +.. spoiler:: Suggested Stylelint settings + + .. code-block:: html + + "stylelint.config": { + "rules": { + // https://stylelint.io/user-guide/rules + + // Avoid errors + "block-no-empty": true, + "shorthand-property-no-redundant-values": true, + "declaration-block-no-shorthand-property-overrides": true, + + // Stylistic conventions + "indentation": 4, + + "function-comma-space-after": "always", + "function-parentheses-space-inside": "never", + "function-whitespace-after": "always", + + "unit-case": "lower", + + "value-list-comma-space-after": "always-single-line", + + "declaration-bang-space-after": "never", + "declaration-bang-space-before": "always", + "declaration-colon-space-after": "always", + "declaration-colon-space-before": "never", + + "block-closing-brace-empty-line-before": "never", + "block-opening-brace-space-before": "always", + + "selector-attribute-brackets-space-inside": "never", + "selector-list-comma-space-after": "always-single-line", + "selector-list-comma-space-before": "never-single-line", + } + }, + +.. _contributing/coding_guidelines/scss/properties_order: + +Properties order +---------------- + +Order properties from the "outside" in, starting from `position` and ending with decorative rules +(`font`, `filter`, etc.). + +:ref:`Scoped SCSS variables ` and +:ref:`CSS variables ` must be placed at the very +top, followed by an empty line separating them from other declarations. + +.. code-block:: html + + .o_element { + $-inner-gap: $border-width + $legend-margin-bottom; + + --element-margin: 1rem; + --element-size: 3rem; + + @include o-position-absolute(1rem); + display: block; + margin: var(--element-margin); + width: calc(var(--element-size) + #{$-inner-gap}); + border: 0; + padding: 1rem; + background: blue; + font-size: 1rem; + filter: blur(2px); + } + +.. _contributing/coding_guidelines/scss/naming_conventions: + +Naming Conventions +------------------ + +Naming conventions in CSS are incredibly useful in making your code more strict, transparent and +informative. + +| Avoid `id` selectors, and prefix your classes with `o_`, where `` is the + technical name of the module (`sale`, `im_chat`, ...) or the main route reserved by the module + (for website modules mainly, i.e. : `o_forum` for the `website_forum` module). +| The only exception for this rule is the webclient: it simply uses the `o_` prefix. + +Avoid creating hyper-specific classes and variable names. When naming nested elements, opt for the +"Grandchild" approach. + +.. rst-class:: bg-light +.. example:: + + .. container:: alert alert-danger + + Don't + + .. code-block:: html + +
+
+ + Entry + +
+
+ + .. container:: alert alert-success + + Do + + .. code-block:: html + +
+
+ + Entry + +
+
+ +Besides being more compact, this approach eases maintenance because it limits the need of renaming +when changes occur at the DOM. + +.. _contributing/coding_guidelines/scss/scss_variables: + +SCSS Variables +~~~~~~~~~~~~~~ + +Our standard convention is `$o-[root]-[element]-[property]-[modifier]`, with: + +* `$o-` + The prefix. +* `[root]` + Either the component **or** the module name (components take priority). +* `[element]` + An optional identifier for inner elements. +* `[property]` + The property/behavior defined by the variable. +* `[modifier]` + An optional modifier. + +.. example:: + + .. code-block:: scss + + $o-block-color: value; + $o-block-title-color: value; + $o-block-title-color-hover: value; + +.. _contributing/coding_guidelines/scss/scoped_scss_variables: + +SCSS Variables (scoped) +~~~~~~~~~~~~~~~~~~~~~~~ + +These variables are declared within blocks and are not accessible from the outside. +Our standard convention is `$-[variable name]`. + +.. example:: + + .. code-block:: html + + .o_element { + $-inner-gap: compute-something; + + margin-right: $-inner-gap; + + .o_element_child { + margin-right: $-inner-gap * 0.5; + } + } + +.. seealso:: + `Variables scope on the SASS Documentation + `_ + +.. _contributing/coding_guidelines/scss/mixins: + +SCSS Mixins and Functions +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Our standard convention is `o-[name]`. Use descriptive names. When naming functions, use verbs in +the imperative form (e.g.: `get`, `make`, `apply`...). + +Name optional arguments in the :ref:`scoped variables form +`, so `$-[argument]`. + +.. example:: + + .. code-block:: html + + @mixin o-avatar($-size: 1.5em, $-radius: 100%) { + width: $-size; + height: $-size; + border-radius: $-radius; + } + + @function o-invert-color($-color, $-amount: 100%) { + $-inverse: change-color($-color, $-hue: hue($-color) + 180); + + @return mix($-inverse, $-color, $-amount); + } + +.. seealso:: + - `Mixins on the SASS Documentation `_ + - `Functions on the SASS Documentation `_ + +.. _contributing/coding_guidelines/scss/css_variables: + +CSS Variables +~~~~~~~~~~~~~ + +In Odoo, the use of CSS variables is strictly DOM-related. Use them to **contextually** adapt the +design and layout. + +Our standard convention is BEM, so `--[root]__[element]-[property]--[modifier]`, with: + +* `[root]` + Either the component **or** the module name (components take priority). +* `[element]` + An optional identifier for inner elements. +* `[property]` + The property/behavior defined by the variable. +* `[modifier]` + An optional modifier. + +.. example:: + + .. code-block:: scss + + .o_kanban_record { + --KanbanRecord-width: value; + --KanbanRecord__picture-border: value; + --KanbanRecord__picture-border--active: value; + } + + // Adapt the component when rendered in another context. + .o_form_view { + --KanbanRecord-width: another-value; + --KanbanRecord__picture-border: another-value; + --KanbanRecord__picture-border--active: another-value; + } + +.. _contributing/coding_guidelines/scss/variables_use: + +Use of CSS Variables +-------------------- + +In Odoo, the use of CSS variables is strictly DOM-related, meaning that are used to **contextually** +adapt the design and layout rather than to manage the global design-system. These are typically used +when a component's properties can vary in specific contexts or in other circumstances. + +We define these properties inside the component's main block, providing default fallbacks. + +.. example:: + + .. code-block:: scss + :caption: :file:`my_component.scss` + + .o_MyComponent { + color: var(--MyComponent-color, #313131); + } + + .. code-block:: scss + :caption: :file:`my_dashboard.scss` + + .o_MyDashboard { + // Adapt the component in this context only + --MyComponent-color: #017e84; + } + +.. seealso:: + `CSS variables on MDN web docs + `_ + +.. _contributing/coding_guidelines/scss/css_scss_variables_use: + +CSS and SCSS Variables +~~~~~~~~~~~~~~~~~~~~~~ + +Despite being apparently similar, `CSS` and `SCSS` variables behave very differently. The main +difference is that, while `SCSS` variables are **imperative** and compiled away, `CSS` variables are +**declarative** and included in the final output. + +.. seealso:: + `CSS/SCSS variables difference on the SASS Documentation + `_ + +In Odoo, we take the best of both worlds: using the `SCSS` variables to define the design-system +while opting for the `CSS` ones when it comes to contextual adaptations. + +The implementation of the previous example should be improved by adding SCSS variables in order to +gain control at the top-level and ensure consistency with other components. + +.. example:: + + .. code-block:: scss + :caption: :file:`secondary_variables.scss` + + $o-component-color: $o-main-text-color; + $o-dashboard-color: $o-info; + // [...] + + .. code-block:: text + :caption: :file:`component.scss` + + .o_component { + color: var(--MyComponent-color, #{$o-component-color}); + } + + .. code-block:: text + :caption: :file:`dashboard.scss` + + .o_dashboard { + --MyComponent-color: #{$o-dashboard-color}; + } + +.. _contributing/coding_guidelines/scss/root: + +The `:root` pseudo-class +~~~~~~~~~~~~~~~~~~~~~~~~ + +Defining CSS variables on the `:root` pseudo-class is a technique we normally **don't use** in +Odoo's UI. The practice is commonly used to access and modify CSS variables globally. We perform +this using SCSS instead. + +Exceptions to this rule should be fairly apparent, such as templates shared across bundles that +require a certain level of contextual awareness in order to be rendered properly. diff --git a/.agents/skills/odoo19-development/references/git_guidelines.rst b/.agents/skills/odoo19-development/references/git_guidelines.rst new file mode 100644 index 0000000..c5a9f1a --- /dev/null +++ b/.agents/skills/odoo19-development/references/git_guidelines.rst @@ -0,0 +1,145 @@ +============== +Git guidelines +============== + +Configure your git +------------------ + +Based on ancestral experience and oral tradition, the following things go a long +way towards making your commits more helpful: + +- Be sure to define both the user.email and user.name in your local git config + + .. code-block:: text + + git config --global + +- Be sure to add your full name to your Github profile here. Please feel fancy + and add your team, avatar, your favorite quote, and whatnot ;-) + +Commit message structure +------------------------ + +Commit message has four parts: tag, module, short description and full +description. Try to follow the preferred structure for your commit messages + +.. code-block:: text + + [TAG] module: describe your change in a short sentence (ideally < 50 chars) + + Long version of the change description, including the rationale for the change, + or a summary of the feature being introduced. + + Please spend a lot more time describing WHY the change is being done rather + than WHAT is being changed. This is usually easy to grasp by actually reading + the diff. WHAT should be explained only if there are technical choices + or decision involved. In that case explain WHY this decision was taken. + + End the message with references, such as task or bug numbers, PR numbers, and + OPW tickets, following the suggested format: + task-123 (related to task) + Fixes #123 (close related issue on Github) + Closes #123 (close related PR on Github) + opw-123 (related to ticket) + +Tag and module name +------------------- + +Tags are used to prefix your commit. They should be one of the following + +- **[FIX]** for bug fixes: mostly used in stable version but also valid if you + are fixing a recent bug in development version; +- **[REF]** for refactoring: when a feature is heavily rewritten; +- **[ADD]** for adding new modules; +- **[REM]** for removing resources: removing dead code, removing views, + removing modules, ...; +- **[REV]** for reverting commits: if a commit causes issues or is not wanted + reverting it is done using this tag; +- **[MOV]** for moving files: use git move and do not change content of moved file + otherwise Git may loose track and history of the file; also used when moving + code from one file to another; +- **[REL]** for release commits: new major or minor stable versions; +- **[IMP]** for improvements: most of the changes done in development version + are incremental improvements not related to another tag; +- **[MERGE]** for merge commits: used in forward port of bug fixes but also as + main commit for feature involving several separated commits; +- **[CLA]** for signing the Odoo Individual Contributor License; +- **[I18N]** for changes in translation files; +- **[PERF]** for performance patches; +- **[CLN]** for code cleanup; +- **[LINT]** for linting passes; + +After tag comes the modified module name. Use the technical name as functional +name may change with time. If several modules are modified, list them or use +various to tell it is cross-modules. Unless really required or easier avoid +modifying code across several modules in the same commit. Understanding module +history may become difficult. + +Commit message header +--------------------- + +After tag and module name comes a meaningful commit message header. It should be +self explanatory and include the reason behind the change. Do not use single words +like "bugfix" or "improvements". Try to limit the header length to about 50 characters +for readability. + +Commit message header should make a valid sentence once concatenated with +``if applied, this commit will
``. For example ``[IMP] base: prevent to +archive users linked to active partners`` is correct as it makes a valid sentence +``if applied, this commit will prevent users to archive...``. + +Commit message full description +------------------------------- + +In the message description specify the part of the code impacted by your changes +(module name, lib, transversal object, ...) and a description of the changes. + +First explain WHY you are modifying code. What is important if someone goes back +to your commit in about 4 decades (or 3 days) is why you did it. It is the +purpose of the change. + +What you did can be found in the commit itself. If there was some technical choices +involved it is a good idea to explain it also in the commit message after the why. +For Odoo R&D developers "PO team asked me to do it" is not a valid why, by the way. + +Please avoid commits which simultaneously impact multiple modules. Try to split +into different commits where impacted modules are different. It will be helpful +if we need to revert changes in a given module separately. + +Don't hesitate to be a bit verbose. Most people will only see your commit message +and judge everything you did in your life just based on those few sentences. +No pressure at all. + +**You spend several hours, days or weeks working on meaningful features. Take +some time to calm down and write clear and understandable commit messages.** + +If you are an Odoo R&D developer the WHY should be the purpose of the task you +are working on. Full specifications make the core of the commit message. +**If you are working on a task that lacks purpose and specifications please +consider making them clear before continuing.** + +Finally here are some examples of correct commit messages : + +.. code-block:: text + + [REF] models: use `parent_path` to implement parent_store + + This replaces the former modified preorder tree traversal (MPTT) with the + fields `parent_left`/`parent_right`[...] + + [FIX] account: remove frenglish + + [...] + + Closes #22793 + Fixes #22769 + + [FIX] website: remove unused alert div, fixes look of input-group-btn + + Bootstrap's CSS depends on the input-group-btn + element being the first/last child of its parent. + This was not the case because of the invisible + and useless alert. + +.. note:: Use the long description to explain the *why* not the + *what*, the *what* can be seen in the diff diff --git a/.agents/skills/payroll/SKILL.md b/.agents/skills/payroll/SKILL.md new file mode 100644 index 0000000..647b695 --- /dev/null +++ b/.agents/skills/payroll/SKILL.md @@ -0,0 +1,151 @@ +--- +name: payroll +description: | + Guidance for implementing Austrian payroll on the Odoo 19 hr_payroll + engine: Bgld. GemBG 2014 besoldung (Entlohnungsgruppen/-stufen, + quarterly Sonderzahlung), Lohnsteuer, SV/Dienstgeberbeitrag, the + shared AT core module l10n_at_hr_payroll, VRV account posting via the + gem360 bridge module l10n_at_gemeinde_payroll_vrv, L16/eSV + melde-dateien, annual Bezügeanpassung updates, and multi-Bundesland + extension. Use for any work on addons/l10n_at_hr_payroll, + addons/l10n_at_gemeinde_payroll or the legal foundations in + personalverrechnung/. +disable-model-invocation: false +--- + +## Applicability + +Use this skill whenever work touches the Austrian payroll localization or +its foundations: the core module `addons/l10n_at_hr_payroll` (SV values, +TASY import, upcoming Lohnsteuer/Meldewesen/Belege), the municipal module +`addons/l10n_at_gemeinde_payroll` (Bgld. GemBG besoldung, payslip +structures/salary rules), Lohnsteuer, SV/DB contributions, payroll +reports and melde-dateien (L16, eSV/ELDA), annual value updates +(Bezügeanpassung), or extending the payroll to another Bundesland or to +the general private economy (`l10n_at_hr_payroll_private`). + +Skills do not replace the mandatory `AGENTS.md` workflow. For payroll +work, combine this skill with: + +- `odoo19-development/SKILL.md` (always); +- for GL posting / VRV work on the bridge module + `l10n_at_gemeinde_payroll_vrv` (which lives in the gem360 repository), + additionally consult the gem360 repo's `odoo19-accounting`, `vrv2015` + and `accounting-review` skills. + +## Required project sources (read before planning) + +1. `personalverrechnung/PLAN-payroll-bgld.md` — project frame, scope + variants, estimates and their revisions. +2. `personalverrechnung/RECHTSQUELLEN-Bgld.md` — the verified legal + inventory (status marks: ✅ verified / ⚠ plausible / ❓ open). + Treat these marks as binding: never code a ❓ value without + resolving it against a primary source first. +3. `personalverrechnung/ANGEBOTS-KALKULATION-Bgld-Personalverrechnung.md` + — approved scope (APs), Prämissen P1–P10, options and exclusions. + Changes that violate a Prämisse or an exclusion need explicit user + approval first. +4. Raw legal material (local, not versioned): `.firecrawl/` contains + the consolidated Bgld. GemBG 2014, GemBÜG 2014, the 2026 + Bezügeanpassung tables (RV 0715), official SV-Werte 2026, and + ASVG/FLAG extracts. If `.firecrawl/` is missing, re-fetch from RIS + (ris.bka.gv.at) and sozialversicherung.at before working from + memory. + +## Legal model (Burgenland — verified 2026-09) + +- **One single legal source for the running payroll:** Bgld. GemBG 2014 + (LGBl. Nr. 42/2014). All Gemeindebedienstete hold privatrechtliche + Dienstverhältnisse; no new öffentlich-rechtliche Dienstverhältnisse + (§ 1 Abs 3), no Kollektivvertrag, no AZG/UrlG/AngG/ATV references. + The GemBG itself governs Dienstzeit (40 h, § 33), Urlaub (28/33 + Arbeitstage, §§ 92–94), Abfertigung (§ 130), Karenz (§§ 106 ff. with + Bgld. MVKG, LGBl. 16/2005). +- **Besoldung ab 1. 1. 2021** (IVa. Hauptstück, §§ 132 ff): + Entlohnungsgruppen, Entlohnungsstufen, Besoldungsdienstalter, + Vorrückungen; Zulagen incl. Kinderzulage (uncut for part-time), + Funktions-/Erschwernis-/Journaldienst-/Sonn-Feiertags-/Bereitschafts- + components (§§ 61–63, 74–87). +- **Sonderzahlung is quarterly:** 50 % of the Monatsentgelt (incl. + Kinderzulage) per Kalendervierteljahr — not a classic 13th/14th. + The lohnsteuerliche mapping to begünstigte Bezüge (§ 68 Abs 5/6 EStG) + is a designated open point (see below). +- **€ values come from annual Bgld. Bezügeanpassungs-Sammelgesetzen** + (tables in Anlagen/§-Ersetzungen; 2026: RV 0715/XXIII. GP / LGBl. + 50/2026) — never from a KV catalog, never hard-coded from a prior + year. +- **Out of GemBG scope:** Lehrlinge (§ 1 Abs 2 Z 1), Gemeindeorgane + (Bürgermeisterbezüge per Gemeindegesetz/Bgm-PensionsG 1979), Freie + Städte Eisenstadt/Rust (Hauptstück VI Sonderregeln). +- **Federal law supplies:** EStG (§ 33 tariff, § 68 Pauschalbesteuerung, + § 84 Lohnzettel/L16), LStR 2002 (findok), ASVG contributions and + limits (values: annual "Beitragsrechtliche Werte" PDF, + sozialversicherung.at), öffentlicher FLAF-Dienstgeberbeitrag + (§ 49a ASVG), KommStG exemption for the municipality's own employees, + Meldewesen (FinanzOnline, ELDA/eSV). + +## Architecture rules + +- Build strictly on the Odoo 19 `hr_payroll` engine: structures per + employee group, salary rules, rule parameters. No parallel engine, + no side tables for entitlements that the engine can express. +- **Shared federal core:** sector-neutral rules and values (Lohnsteuer, + SV/DB, Meldewesen, Belege, annual values) live in + `l10n_at_hr_payroll`; both the municipal module and the future + general product consume the core. Never fork core values. +- Annual and legal values (LSt tariff, SV rates, limits, + Bezügeanpassung tables) live in `hr.rule.parameter` data or + equivalent data files — never as literals inside rule code. +- Per-Bundesland separation from day one: katalog data, structures and + state-specific rules go into clearly separated data/models (Bgld. + first). A follow-up Bundesland must be expressible as data plus few + specific rules, not as a rewrite. +- GL posting via `hr_payroll_account` into VRV accounts (Anlage 3b + Klassen 4/5/6) with Ansatz/MVAG attribution happens **in the gem360 + repository** via the bridge module `l10n_at_gemeinde_payroll_vrv` + (depends on this repo's `l10n_at_gemeinde_payroll` plus gem360's + `l10n_at_vrv2015`, EHH/FHH behaviour, provisions with + Rückstellungskonten and reconciliation anchors Anlage 1a/2a). This + repo stays free of any VRV dependency. +- Meldewesen is **file-based**: produce validated L16 (FinanzOnline) + and eSV/ELDA-XML export files plus Prüfprotokolle. Transmission, + certificates and Behördenkonten remain with the client or their tax + advisor (Prämisse P7) — do not build transmission infrastructure + without explicit approval. + +## Verify before assuming (open points — resolve via RIS/findok) + +- Öffentlicher FLAF-Dienstgeberbeitrag: exact current rate + § 49a Abs 8/9 ASVG (historisch 4,5 %). +- KommStG 1993 § 6 (own employees exempt) — confirm the text. +- Bgld. MVKG full text (RIS-GesNr 20000326) for Karenz/MSchG details. +- Quartalsweise Sonderzahlung ↔ LSt begünstigte Bezüge (LStR 2002). +- Personalvertretungsumlage/AK-Umlage question for GemBG-Bedienstete; + Krankengeldumlage (§ 62 ASVG) for public employers; SV-Träger + confirmation (ÖGK Bgld.) from the client's payroll history. + +Any further legal value must be taken from +`personalverrechnung/RECHTSQUELLEN-Bgld.md` or its named primary +sources (RIS, sozialversicherung.at, usp.gv.at, findok). If a value is +not verified there, stop and resolve it first — do not use training +knowledge for legal numbers. + +## Annual change management (Wartung) + +Each calendar year: fetch the new Bgld. Bezügeanpassung tables, the +SV-Werte PDF and LSt parameter changes; update rule-parameter data; +run the regression suite; document in `docs/CHANGELOG.md` and in +`personalverrechnung/` notes. + +## Testing + +- Rule-level unit tests for every GemBG, Lohnsteuer and SV component, + including Rechenfälle for Eckfälle: Ein-/Austritt im Monat, + Teilzeit/Herabsetzung, quartalsweise SZ, Geringfügigkeit, Karenz, + Jubiläumszuwendung, Über-/Mehrdienstleistung. +- Golden-Master against the client's reference runs as soon as they + exist (Angebotsphase: none available — see Prämissen P1/P10); the + contracted Parallellauf (2–3 months) is part of acceptance. +- Municipal GL posting tests live with the bridge in the gem360 repo + and must reconcile payroll postings against the Anlage-1a/2a engines + of `l10n_at_vrv2015` there. \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..802b1f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.pyc +.firecrawl/ \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..03b45bf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,188 @@ +## Agent memory + +Before starting work in a new agent conversation: + +- read this `AGENTS.md`; +- read the `agent-memory/SKILL.md` skill; +- read `.agents/MEMORY.md` for the current project handoff. + +Update `.agents/MEMORY.md` when the task produces significant state, blockers, +or decisions. + +## Skills + +Skills are task-specific instructions and must be read before planning work. + +Before producing a plan, identify all skills applicable to the task and read +their `SKILL.md` files. + +Skills are self-contained directories under `.agents/skills/`. + +When a skill is selected, read its `SKILL.md` and consult any relevant +documentation under that skill's `references/` directory. + +Skills are task-specific guidance, not a replacement for the mandatory +project workflow in this file. The agent must follow `AGENTS.md` even when +no skill applies. + +### Skill selection + +- Odoo/Python/module development: + `odoo19-development/SKILL.md` +- Any payroll work (besoldung, Lohnsteuer, SV, Meldewesen, annual value + updates, extending the modules in this repo): + `payroll/SKILL.md` + +Multiple skills may apply. Read all applicable skills. + +For example, implementing the Lohnsteuer core (AP3) normally requires: + +1. `odoo19-development/SKILL.md` +2. `payroll/SKILL.md` + +Work on the VRV bridge module (`l10n_at_gemeinde_payroll_vrv`) happens in +the gem360 repository; there, additionally consult the gem360 skills +(`odoo19-accounting`, `vrv2015`, `accounting-review`). + +## Mandatory development workflow + +All non-trivial implementation tasks follow this workflow: + +### 1. Understand + +Before changing anything: + +- read `AGENTS.md`; +- read all applicable skills; +- inspect the existing implementation; +- search the Odoo 19 source for existing functionality; +- inspect relevant tests; +- inspect relevant legal source material under `personalverrechnung/` + and `.firecrawl/` (local, not versioned); +- identify dependencies and integration points. + +Do not modify files during this phase. + +### 2. Plan + +Produce a concise implementation plan containing: + +- understanding of the requirement; +- relevant existing Odoo functionality; +- files/components likely to change; +- proposed implementation; +- data/model/report implications; +- tests to add or modify; +- relevant legal/accounting considerations; +- potential risks or unresolved questions. + +### 3. Wait for approval + +**STOP after presenting the plan.** + +Do not create, modify, delete, or rename files until the user explicitly approves +the plan. + +Questions and clarification are allowed during this phase. + +### 4. Implement + +After approval: + +- implement the approved plan; +- prefer existing Odoo functionality over custom implementations; +- keep the change as small and upgrade-friendly as practical; +- do not introduce unrelated refactoring; +- follow Odoo 19 coding and Git guidelines. + +If implementation reveals that the approved plan is materially wrong or +incomplete, stop and explain the discrepancy rather than silently expanding +scope. + +### 5. Validate + +Run appropriate tests and checks. + +At minimum: + +- relevant Odoo tests; +- module installation/update checks where appropriate; +- Python/XML validation; +- linting/static checks available in the repository. + +Validate the legal semantics (Lohnsteuer/SV values against the verified +sources in `personalverrechnung/`) as well as technical correctness. + +### 6. Review + +Before considering the task complete, review the resulting changes for: + +- correctness; +- Odoo framework integration; +- reuse of existing Odoo functionality; +- maintainability; +- security/access rights; +- multi-company behavior; +- unnecessary custom code; +- regression risk. + +## Verify before assuming + +Never invent or assume the existence of an API. + +Before using a: + +- Python package; +- Odoo module; +- Odoo model; +- field; +- method; +- XML ID; +- configuration key; +- external API; + +verify that it exists in the current Odoo 19 Enterprise source, +installed environment, or declared project dependencies. + +Do not infer existence from model naming conventions, previous Odoo versions, +documentation alone, training knowledge, or similarly named components. + +If existence cannot be verified, stop and report the uncertainty. + +In particular, never add a dependency merely because it would be a plausible +package name. + +## Prefer framework functionality + +Before implementing custom functionality, search the Odoo 19 Enterprise and +community source available to the project for an existing mechanism. + +Prefer, in order: + +1. existing Odoo functionality that already satisfies the requirement; +2. extending an existing Odoo mechanism; +3. composing existing Odoo mechanisms; +4. a small project-specific implementation only when the above are insufficient. + +Do not duplicate functionality merely because implementing it locally appears +simpler. + +When choosing a custom implementation over an existing Odoo mechanism, explain +the reason in the plan. + +## Git + +Follow the Odoo 19 Git guidelines. + +When working with Git: + +- Keep commits focused and logically coherent. +- Prefer one module per commit where practical. +- Do not mix unrelated changes. +- Review the staged diff before committing. +- Never commit secrets, credentials, client data, local configuration, or + unrelated generated files. +- Do not create a commit unless the user explicitly asks for one or the task + explicitly requires it. +- When committing, use the Odoo commit-message conventions configured in + Zed's `agent.commit_message_instructions`. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf5af18 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# odoo-at-payroll + +Österreichische Personalverrechnung auf Odoo 19 Enterprise. Eigenes Repo +seit 2026-09-09 — Überführung aus `gem360-git` per History-Import; siehe +[`personalverrechnung/PLAN-repo-split.md`](personalverrechnung/PLAN-repo-split.md). + +## Module + +| Modul | Zweck | +|---|---| +| `l10n_at_hr_payroll` | **Bundes-Kern** (sektor-neutral): SV-Werte 2026 als `hr.rule.parameter`-Seed, TASY-Import-Wizard; folgen: Lohnsteuer (AP3), Meldewesen L16/eSV (AP5), Belege (AP6) | +| `l10n_at_gemeinde_payroll` | **Bgld. GemBG 2014**: Katalogmodell (AP1), Entgelt-Engine (AP2); AP3+ gemäß `personalverrechnung/IMPLEMENTIERUNGSPLAN-Bgld.md` | +| `l10n_at_hr_payroll_private` | **General-AT** (KV-basierte Privatwirtschaft) — Gerüst, eigener Plan folgt | + +VRV-Buchung für Gemeinden (Anlage 3b, Ansatz/MVAG, EHH/FHH) läuft über +die Brücke `l10n_at_gemeinde_payroll_vrv` im **gem360-Repo** (AP7). +Abhängigkeitsrichtung: gem360 → dieses Repo, **nie** umgekehrt. + +## Entwicklung + +- Odoo 19 Enterprise: Geschwister-Checkout `../odoo` (gleicher Stand wie + die gem360-Entwicklung; Pin bei Odoo-Upgrades in beiden Repos validieren). +- Planung/Rechtsquellen: `personalverrechnung/` (PLAN, RECHTSQUELLEN, + IMPLEMENTIERUNGSPLAN, Angebotskalkulation). +- Rechts-Rohquellen: `.firecrawl/` — **lokal, unversioniert**; falls + fehlend, neu von RIS/sozialversicherung.at beschaffen (siehe + payroll-Skill), niemals aus Trainingswerten arbeiten. + +## Tests + +Wegwerf-DB verwenden (nie `gem360_dev`): + +```sh +python3 ../odoo/odoo-bin -d \ + -i l10n_at_hr_payroll,l10n_at_gemeinde_payroll \ + --test-tags /l10n_at_hr_payroll,/l10n_at_gemeinde_payroll \ + --test-enable --stop-after-init \ + --addons-path=/addons,/addons,addons +``` + +Dazu je Änderung: `py_compile` über alle Python-Dateien und +XML-Well-formedness-Check (Konvention wie im gem360/vrv2015-Workflow). \ No newline at end of file diff --git a/addons/l10n_at_hr_payroll_private/README.md b/addons/l10n_at_hr_payroll_private/README.md new file mode 100644 index 0000000..e75d9f1 --- /dev/null +++ b/addons/l10n_at_hr_payroll_private/README.md @@ -0,0 +1,9 @@ +# l10n_at_hr_payroll_private (Gerüst) + +General-AT-Payroll (KV-basierte Privatwirtschaft: KV-Gehaltskataloge, +AZG/UrlG-Arbeitszeit, 13./14. Bezüge, Abfertigung Neu) — **folgt als +eigener, zur Freigabe vorzulegender Plan.** + +Rahmen: `personalverrechnung/PLAN-payroll-bgld.md` (Abschnitt 4, +Schätzrahmen) und `personalverrechnung/PLAN-repo-split.md` (Ziel-Struktur, +~125–175 PT Zusatzaufwand). Kein Manifest/Code bis zur Freigabe. \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..e73f73b --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,17 @@ +# CHANGELOG — odoo-at-payroll + +## 2026-09-09 — Repo-Split aus gem360 (Migration) + +- History-Import der Payroll-Commits aus `gem360-git` (Branch + `dev-payroll`, pfad-gefiltert via `git filter-repo`; siehe + `personalverrechnung/PLAN-repo-split.md`). Branch heißt hier `main`. +- `l10n_at_hr_payroll` (19.0.1.0.0) ausgegründet: SV-Werte 2026 als + `hr.rule.parameter`-Seed, TASY-Mapping + Import-Wizard; Modelle + umbenannt `gemeinde.payroll.tasy.*` → `l10n.at.payroll.tasy.*`. +- `l10n_at_gemeinde_payroll` → 19.0.1.4.0: hängt nun an + `l10n_at_hr_payroll` statt `l10n_at_vrv2015` (VRV-Verbleib: Brücke + `l10n_at_gemeinde_payroll_vrv` im gem360-Repo, AP7); Beschreibung auf + AP1+AP2-Stand korrigiert. +- Bootstrap: `AGENTS.md`, Skills (payroll, odoo19-development, + agent-memory), README, CHANGELOG; `l10n_at_hr_payroll_private` als + Gerüst (README only). \ No newline at end of file