diff --git a/hr_expense_advance_clearing/README.rst b/hr_expense_advance_clearing/README.rst new file mode 100644 index 000000000..fca7fd165 --- /dev/null +++ b/hr_expense_advance_clearing/README.rst @@ -0,0 +1,154 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +============================= +Employee Advance and Clearing +============================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:9cfb71123c60ec1d5ee097d027b63702b0f0841b0babdd697b92d1136536d388 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github + :target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_advance_clearing + :alt: OCA/hr-expense +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/hr-expense-19-0/hr-expense-19-0-hr_expense_advance_clearing + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/hr-expense&target_branch=19.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module lets the company pay an advance to an employee and later +clear it against the expenses they actually incur. The employee records +an advance expense, gets paid for it, then files regular expenses that +reconcile against the advance. When an advance is over-spent the +employee registers a payment for the difference, and when it is +under-spent they return the unused balance to the company. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +This module will create a new product "Employee Advance" automatically. +You will need to setup the Expense Account of this product to your +Employee Advance account manually. + +- Open Product window and search for "Employee Advance" +- On Accounting tab, select appropriate employee advance account from + your chart of account + +Note: + +- You will need the "Show Full Accounting Features" to see accounting + data +- Employee Advance account code, if not already exists, you can create + one. Use type = Current Asset and check Allow Reconciliation. + +Usage +===== + +First-time setup: open product **Employee Advance** (auto-created on +install) and set its expense account to your "Employee Advance" +current-asset account with ``Allow Reconciliation``. + +**Create an Employee Advance** + +1. Expenses → My Expenses, click New. +2. Set ``Expense Type`` to **Advance** (Employee Advance product + + own_account auto-fill). +3. Optionally set ``Clearing Product`` — it's the default product for + clearing expense lines that reference this advance. +4. Set the amount, Save. +5. Submit → Approve → Post → Register Payment. The advance is paid to + the employee. + +**Clear the Advance with a regular expense** + +1. Expenses → My Expenses, New (or pick an existing draft). +2. Leave ``Expense Type`` as **Expense** and set ``Clearing Advance`` to + the advance you want to clear against. The ``Advance Remaining`` + smart field shows what's left of the advance. +3. Submit → Approve → Post. On post, the clearing expense's receivable + line auto-reconciles against the advance's receivable line. + +What happens at post time: + +- ``clearing_total ≤ advance_residual`` → fully covered. No further + payment needed; the advance's residual updates. +- ``clearing_total > advance_residual`` → the excess remains on the + clearing expense's move. Register Payment for the difference. + +**Return unused advance** + +1. Open the paid advance with ``clearing_residual > 0``. +2. Click **Return Advance** (header button). The Register Payment wizard + opens pre-filled with the residual. +3. Confirm. The advance is now fully closed; ``clearing_residual = 0``. + +**Trip integration** (when ``hr_expense_trip`` is installed) + +A trip's ``expense_ids`` can include both an advance and the regular +expenses paid out of it. The trip module wires a "Clear Trip" button +that calls ``action_clear_advance`` on the trip's regular expenses with +the trip's advance as the target. This module does not depend on +``hr_expense_trip``; the integration is one-way from the trip side. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Ecosoft + +Contributors +------------ + +- Kitti Upariphutthiphong +- Tharathip Chaweewongphan +- Saran Lim. + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/hr-expense `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/hr_expense_advance_clearing/__init__.py b/hr_expense_advance_clearing/__init__.py new file mode 100644 index 000000000..25aa51eca --- /dev/null +++ b/hr_expense_advance_clearing/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import models +from . import wizard diff --git a/hr_expense_advance_clearing/__manifest__.py b/hr_expense_advance_clearing/__manifest__.py new file mode 100644 index 000000000..7c0c1d9ce --- /dev/null +++ b/hr_expense_advance_clearing/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +{ + "name": "Employee Advance and Clearing", + "version": "19.0.1.0.0", + "category": "Human Resources", + "author": "Ecosoft, Odoo Community Association (OCA)", + "license": "AGPL-3", + "website": "https://github.com/OCA/hr-expense", + "depends": ["hr_expense"], + "data": [ + "data/advance_product.xml", + "views/account_payment_view.xml", + "views/hr_expense_views.xml", + "views/hr_employee_views.xml", + "views/hr_employee_public_views.xml", + ], + "installable": True, +} diff --git a/hr_expense_advance_clearing/data/advance_product.xml b/hr_expense_advance_clearing/data/advance_product.xml new file mode 100644 index 000000000..2733fb032 --- /dev/null +++ b/hr_expense_advance_clearing/data/advance_product.xml @@ -0,0 +1,16 @@ + + + + Employee Advance + + service + + + + + + + + diff --git a/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot new file mode 100644 index 000000000..4ad58b224 --- /dev/null +++ b/hr_expense_advance_clearing/i18n/hr_expense_advance_clearing.pot @@ -0,0 +1,341 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_advance_clearing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"\n" +"Note: pending amount clearing is %(symbol)s%(amount)s" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "# of Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form +msgid "Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Advance (not cleared)" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Advance Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Advance Remaining" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids +msgid "Advance Sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance clearing must not contain any advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance must contain only advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance: %(name)s has no amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet +msgid "Advances" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear of this expense sheet in company currency" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base +msgid "Basic Employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clear Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count +msgid "Clearing Count" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "Clearing Product" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Clearing Sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Clearing Sheets" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clearings" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "Count advance sheet in expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Create a new expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance +#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "Employee Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance product has no payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, account must be the same payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, all taxes must be removed" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, paid by must be employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, selected product is not valid" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +msgid "Expense" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet +msgid "Expense Report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Expense created from this advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet +msgid "Expenses" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Final regiter payment amount even after advance clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "No advance report found. Let's create one!" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "No expense report found. Let's create one!" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "" +"Once you have created your advance, submit it to your manager who will " +"validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "" +"Once you have created your expense, submit it to your manager who will\n" +" validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "" +"Once you have created your expense, submit it to your manager who will " +"validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form +msgid "Optional clearing product is used during clear advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "" +"Optional: On the clear advance, the clearing product will create default " +"product line." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register +msgid "Pay" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Payable Amount" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Payment Return" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment +msgid "Payments" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Ref: Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Remaining amount to clear the selected advance sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count +msgid "Return Count" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Show reference clearing on advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Show reference return advance on advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +msgid "Show remaining advance of this employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/account_move.py:0 +msgid "" +"This operation is not allowed as some advance amount was already cleared/returned.\n" +"Please cancel those documents first." +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"You cannot return advance more than actual remaining " +"(%(symbol)s%(amount)s)%(more_info)s" +msgstr "" diff --git a/hr_expense_advance_clearing/i18n/it.po b/hr_expense_advance_clearing/i18n/it.po new file mode 100644 index 000000000..4bd9466b8 --- /dev/null +++ b/hr_expense_advance_clearing/i18n/it.po @@ -0,0 +1,424 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_advance_clearing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-07-11 21:25+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.10.4\n" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"\n" +"Note: pending amount clearing is %(symbol)s%(amount)s" +msgstr "" +"\n" +"Nota: valore liquidazione in attesa è %(symbol)s%(amount)s" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "# of Advance" +msgstr "N° di anticipo" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form +msgid "Advance" +msgstr "Anticipo" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Advance (not cleared)" +msgstr "Anticipo (non liquidato)" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Advance Clearing" +msgstr "Liquidazione anticipo" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Advance Remaining" +msgstr "Residuo anticipo" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids +msgid "Advance Sheet" +msgstr "Foglio anticipo" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance clearing must not contain any advance expense line" +msgstr "" +"La liquidazione anticipo non deve contenere nessuna riga anticipo spesa" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance must contain only advance expense line" +msgstr "L'anticipo deve contenere solo una riga anticipo spesa" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance: %(name)s has no amount to clear" +msgstr "Anticipo: %(name)s non ha alcun importo da saldare" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet +msgid "Advances" +msgstr "Anticipi" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear" +msgstr "Valore da liquidare" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear of this expense sheet in company currency" +msgstr "Valore da liquidare di questo foglio spese nella valuta dell'azienda" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base +msgid "Basic Employee" +msgstr "Dipendente base" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clear Advance" +msgstr "Liquida anticipo" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Clearing" +msgstr "Liquidazione" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count +msgid "Clearing Count" +msgstr "Conteggio liquidazione" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "Clearing Product" +msgstr "Prodotto liquidazione" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Clearing Sheet" +msgstr "Foglio liquidazione" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Clearing Sheets" +msgstr "Fogli liquidazioni" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clearings" +msgstr "Liquidazioni" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "Count advance sheet in expense report" +msgstr "Conteggio foglio anticipo nella nota spese" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Create a new expense report" +msgstr "Crea una nuova nota spese" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance +#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "Employee Advance" +msgstr "Anticipo dipendente" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance product has no payable account" +msgstr "Il prodotto anticipo dipendente non ha un conto pagabile" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, account must be the same payable account" +msgstr "Anticipo dipendente, il conto deve essere lo stesso conto pagabile" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, all taxes must be removed" +msgstr "Anticipo dipendente, devono essere rimosse tutte le tasse" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, paid by must be employee" +msgstr "Anticipo dipendente, \"pagato da\" deve essere un dipendente" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, selected product is not valid" +msgstr "Anticipo dipendente, il prodotto selezionato non è valido" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +msgid "Expense" +msgstr "Spesa" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet +msgid "Expense Report" +msgstr "Nota spese" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Expense created from this advance expense line" +msgstr "Spesa creata da questa riga anticipo spesa" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet +msgid "Expenses" +msgstr "Spese" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Final regiter payment amount even after advance clearing" +msgstr "" +"Registrazione finale importo pagamento anche dopo la liquidazione anticipata" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_move +msgid "Journal Entry" +msgstr "Registrazione contabile" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "No advance report found. Let's create one!" +msgstr "Nessuna nota spese trovata. Crearne una!" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "No expense report found. Let's create one!" +msgstr "Nessuna nota spese trovata. Crearne una!" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "" +"Once you have created your advance, submit it to your manager who will " +"validate it." +msgstr "" +"Una volta creato l'anticipo, inviarlo al proprio responsabile che lo " +"validerà." + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "" +"Once you have created your expense, submit it to your manager who will\n" +" validate it." +msgstr "" +"Una volta calcolata le spesa, inviarla al proprio responsabile che\n" +" la validerà." + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "" +"Once you have created your expense, submit it to your manager who will " +"validate it." +msgstr "" +"Una volta creata la spesa, inviarla al proprio responsabile per la " +"validazione." + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form +msgid "Optional clearing product is used during clear advance" +msgstr "" +"Il prodotto liquidazione viene usato nella liquidazione anticipo " +"opzionalmente" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "" +"Optional: On the clear advance, the clearing product will create default " +"product line." +msgstr "" +"Opzionale: nella liquidazione anticipo, il prodotto liquidazione creerà la " +"riga prodotto predefinito." + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register +msgid "Pay" +msgstr "Paga" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Payable Amount" +msgstr "Valore pagabile" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Payment Return" +msgstr "Rimborso pagamento" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment +msgid "Payments" +msgstr "Pagamenti" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Ref: Advance" +msgstr "Rif: anticipo" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Remaining amount to clear the selected advance sheet" +msgstr "Valore residuo per liquidare il foglio anticipo selezionato" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return" +msgstr "Rimborso" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return Advance" +msgstr "Rimborso anticipo" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count +msgid "Return Count" +msgstr "Conteggio rimborsi" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Show reference clearing on advance" +msgstr "Visualizza riferimento liquidazione sull'anticipo" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Show reference return advance on advance" +msgstr "Visualizza riferimento anticipo rimborso nell'anticipo" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +msgid "Show remaining advance of this employee" +msgstr "Visualizza residuo anticipo di questo dipendente" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/account_move.py:0 +msgid "" +"This operation is not allowed as some advance amount was already cleared/" +"returned.\n" +"Please cancel those documents first." +msgstr "" +"Questa operazione non è consentita perché una parte del valore anticipato è " +"già stato liquidato/reso.\n" +"Cancellare quella lista documenti." + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"You cannot return advance more than actual remaining " +"(%(symbol)s%(amount)s)%(more_info)s" +msgstr "" +"Non è possibile restituire un anticipo superiore al rimanente effettivo " +"(%(symbol)s%(amount)s)%(more_info)s" + +#, python-format +#~ msgid "" +#~ "A remaining advance return of %(amount)s %(symbol)s with the reference " +#~ "%(ref)s related to your expense %(name)s has been made." +#~ msgstr "" +#~ "È stato fatto un residuo reso anticipo di %(amount)s %(symbol)s con " +#~ "riferimento %(ref)s relativo alla sua spesa %(name)s." + +#, python-format +#~ msgid "Advance: %s has no amount to clear" +#~ msgstr "Anticipo: %s non ha un valore da compensare" + +#~ msgid "Register Payment" +#~ msgstr "Registra pagamento" + +#, python-format +#~ msgid "You can only register payment for posted journal entries." +#~ msgstr "" +#~ "Si possono registrare pagamenti solo per registrazioni contabili " +#~ "assegnate." + +#, python-format +#~ msgid "" +#~ "You can't create payments for entries belonging to different companies." +#~ msgstr "" +#~ "Non si possono creare pagamenti per entrate appartenenti a diverse " +#~ "aziende." + +#, python-format +#~ msgid "" +#~ "You can't register a payment because there is nothing left to pay on the " +#~ "selected journal items." +#~ msgstr "" +#~ "Non si può registrare il pagamento perché non c'è niente da pagare nei " +#~ "movimenti contabili selezionati." + +#, python-format +#~ msgid "" +#~ "\n" +#~ "Note: pending amount clearing is {}{}" +#~ msgstr "" +#~ "\n" +#~ "Nota: il valore da saldare in attesa è {}{}" + +#, python-format +#~ msgid "" +#~ "A remaining advance return of {} {} with the reference {} related to your " +#~ "expense {} has been made." +#~ msgstr "" +#~ "È stato eseguito un rimborso anticipo residuo di {} {} con riferimento {} " +#~ "relativo alla sua spesa {}." + +#~ msgid "Units" +#~ msgstr "Unità" + +#, python-format +#~ msgid "You cannot return advance more than actual remaining ({}{}){}" +#~ msgstr "" +#~ "Non si può rimborsare un anticipo oltre il restante effettivo ({}{}){}" diff --git a/hr_expense_advance_clearing/i18n/th.po b/hr_expense_advance_clearing/i18n/th.po new file mode 100644 index 000000000..aaba8b298 --- /dev/null +++ b/hr_expense_advance_clearing/i18n/th.po @@ -0,0 +1,343 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_advance_clearing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: th\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"\n" +"Note: pending amount clearing is %(symbol)s%(amount)s" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "# of Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form +msgid "Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Advance (not cleared)" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Advance Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Advance Remaining" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids +msgid "Advance Sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance clearing must not contain any advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance must contain only advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance: %(name)s has no amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet +msgid "Advances" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear of this expense sheet in company currency" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base +msgid "Basic Employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clear Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count +msgid "Clearing Count" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "Clearing Product" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Clearing Sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Clearing Sheets" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clearings" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "Count advance sheet in expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Create a new expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance +#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "Employee Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance product has no payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, account must be the same payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, all taxes must be removed" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, paid by must be employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, selected product is not valid" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +msgid "Expense" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet +msgid "Expense Report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Expense created from this advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet +msgid "Expenses" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Final regiter payment amount even after advance clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "No advance report found. Let's create one!" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "No expense report found. Let's create one!" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "" +"Once you have created your advance, submit it to your manager who will " +"validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "" +"Once you have created your expense, submit it to your manager who will\n" +" validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "" +"Once you have created your expense, submit it to your manager who will " +"validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form +msgid "Optional clearing product is used during clear advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "" +"Optional: On the clear advance, the clearing product will create default " +"product line." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register +msgid "Pay" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Payable Amount" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Payment Return" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment +msgid "Payments" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Ref: Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Remaining amount to clear the selected advance sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count +msgid "Return Count" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Show reference clearing on advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Show reference return advance on advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +msgid "Show remaining advance of this employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/account_move.py:0 +msgid "" +"This operation is not allowed as some advance amount was already cleared/" +"returned.\n" +"Please cancel those documents first." +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"You cannot return advance more than actual remaining " +"(%(symbol)s%(amount)s)%(more_info)s" +msgstr "" diff --git a/hr_expense_advance_clearing/i18n/th_TH.po b/hr_expense_advance_clearing/i18n/th_TH.po new file mode 100644 index 000000000..aae7b4f28 --- /dev/null +++ b/hr_expense_advance_clearing/i18n/th_TH.po @@ -0,0 +1,343 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_expense_advance_clearing +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: th_TH\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"\n" +"Note: pending amount clearing is %(symbol)s%(amount)s" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "# of Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_account_payment__advance_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_employee_public_view_form +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_employee_form +msgid "Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Advance (not cleared)" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Advance Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Advance Remaining" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_employee_base.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_base__advance_sheet_ids +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_employee_public__advance_sheet_ids +msgid "Advance Sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance clearing must not contain any advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance must contain only advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Advance: %(name)s has no amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_advance_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_advance_sheet +msgid "Advances" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_residual +msgid "Amount to clear of this expense sheet in company currency" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_employee_base +msgid "Basic Employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clear Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_sheet_view_search +msgid "Clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_count +msgid "Clearing Count" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "Clearing Product" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Clearing Sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +msgid "Clearing Sheets" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Clearings" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_base__advance_count +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_employee_public__advance_count +msgid "Count advance sheet in expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "Create a new expense report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__advance +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__advance +#: model:product.template,name:hr_expense_advance_clearing.product_emp_advance_product_template +msgid "Employee Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance product has no payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, account must be the same payable account" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, all taxes must be removed" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, paid by must be employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense.py:0 +msgid "Employee advance, selected product is not valid" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_search +msgid "Expense" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_hr_expense_sheet +msgid "Expense Report" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Expense created from this advance expense line" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.actions.act_window,name:hr_expense_advance_clearing.action_my_hr_expense_sheet +#: model:ir.ui.menu,name:hr_expense_advance_clearing.menu_my_hr_expense_sheet +msgid "Expenses" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Final regiter payment amount even after advance clearing" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "No advance report found. Let's create one!" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "No expense report found. Let's create one!" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_advance_sheet +msgid "" +"Once you have created your advance, submit it to your manager who will " +"validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_hr_expense_sheet_advance_clearing +msgid "" +"Once you have created your expense, submit it to your manager who will\n" +" validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.actions.act_window,help:hr_expense_advance_clearing.action_my_hr_expense_sheet +msgid "" +"Once you have created your expense, submit it to your manager who will " +"validate it." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.hr_expense_view_form +msgid "Optional clearing product is used during clear advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense__clearing_product_id +msgid "" +"Optional: On the clear advance, the clearing product will create default " +"product line." +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment_register +msgid "Pay" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__amount_payable +msgid "Payable Amount" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/hr_expense_sheet.py:0 +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Payment Return" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model,name:hr_expense_advance_clearing.model_account_payment +msgid "Payments" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense__av_line_id +msgid "Ref: Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_residual +msgid "Remaining amount to clear the selected advance sheet" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_account_payment_search +#: model_terms:ir.ui.view,arch_db:hr_expense_advance_clearing.view_hr_expense_sheet_form +msgid "Return Advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,field_description:hr_expense_advance_clearing.field_hr_expense_sheet__return_count +msgid "Return Count" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__clearing_sheet_ids +msgid "Show reference clearing on advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__payment_return_ids +msgid "Show reference return advance on advance" +msgstr "" + +#. module: hr_expense_advance_clearing +#: model:ir.model.fields,help:hr_expense_advance_clearing.field_hr_expense_sheet__advance_sheet_id +msgid "Show remaining advance of this employee" +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/models/account_move.py:0 +msgid "" +"This operation is not allowed as some advance amount was already cleared/" +"returned.\n" +"Please cancel those documents first." +msgstr "" + +#. module: hr_expense_advance_clearing +#. odoo-python +#: code:addons/hr_expense_advance_clearing/wizard/account_payment_register.py:0 +msgid "" +"You cannot return advance more than actual remaining " +"(%(symbol)s%(amount)s)%(more_info)s" +msgstr "" diff --git a/hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py b/hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py new file mode 100644 index 000000000..744f63668 --- /dev/null +++ b/hr_expense_advance_clearing/migrations/19.0.1.0.0/post-migration.py @@ -0,0 +1,75 @@ +# Copyright 2024 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging + +from openupgradelib import openupgrade + +_logger = logging.getLogger(__name__) + + +@openupgrade.migrate() +def migrate(env, version): + """Move the surviving 18.0 line-level advance/clearing data onto the 19.0 + per-expense fields (expense_type and clearing_advance_id).""" + cr = env.cr + + # 1) expense_type from the 18.0 line-level `advance` boolean. The old + # column lingers on hr_expense until ORM cleanup, so it is readable here. + if openupgrade.column_exists(cr, "hr_expense", "advance"): + openupgrade.logged_query( + cr, + """ + UPDATE hr_expense + SET expense_type = CASE WHEN advance THEN 'advance' ELSE 'expense' END + WHERE expense_type IS NULL + """, + ) + # expense_type is required; default any remaining rows to 'expense'. + openupgrade.logged_query( + cr, + "UPDATE hr_expense SET expense_type = 'expense' WHERE expense_type IS NULL", + ) + + # 2) clearing_advance_id from the 18.0 line-level `av_line_id`: it linked a + # clearing expense to the advance expense it cleared — the exact + # semantics of the new per-expense clearing_advance_id. + if openupgrade.column_exists(cr, "hr_expense", "av_line_id"): + openupgrade.logged_query( + cr, + """ + UPDATE hr_expense clr + SET clearing_advance_id = adv.id + FROM hr_expense adv + WHERE clr.av_line_id = adv.id + AND adv.expense_type = 'advance' + AND clr.clearing_advance_id IS NULL + """, + ) + + # 3) Sheet-level links the line-level pass could not reconstruct are NOT + # auto-linked (a clearing report grouped many expenses against one + # advance; blindly linking every line would over-attribute the draw-down + # and corrupt the residual). Instead, report them for manual review. + if openupgrade.table_exists(cr, "_ou_haac_sheet_link"): + cr.execute( + """ + SELECT DISTINCT clr.id + FROM hr_expense clr + JOIN _ou_haac_sheet_link link ON clr.former_sheet_id = link.sheet_id + WHERE clr.clearing_advance_id IS NULL + AND clr.expense_type = 'expense' + """ + ) + unresolved = [r[0] for r in cr.fetchall()] + if unresolved: + _logger.warning( + "hr_expense_advance_clearing: %d expense(s) belonged to a " + "clearing report (sheet-level advance_sheet_id) but had no " + "line-level av_line_id; clearing_advance_id left empty for " + "manual review: %s", + len(unresolved), + unresolved, + ) + openupgrade.logged_query(cr, "DROP TABLE _ou_haac_sheet_link") + + _logger.info("hr_expense_advance_clearing: advance/clearing data migrated.") diff --git a/hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py b/hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py new file mode 100644 index 000000000..132549290 --- /dev/null +++ b/hr_expense_advance_clearing/migrations/19.0.1.0.0/pre-migration.py @@ -0,0 +1,31 @@ +# Copyright 2024 ForgeFlow S.L. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +import logging + +from openupgradelib import openupgrade + +_logger = logging.getLogger(__name__) + + +@openupgrade.migrate() +def migrate(env, version): + """Stash the sheet-level advance link before core drops hr.expense.sheet, + so post-migration can flag any clearing the line-level pass misses.""" + cr = env.cr + if not openupgrade.table_exists( + cr, "hr_expense_sheet" + ) or not openupgrade.column_exists(cr, "hr_expense_sheet", "advance_sheet_id"): + _logger.info( + "hr_expense_advance_clearing: hr.expense.sheet already removed; " + "clearing links will be rebuilt from hr_expense.av_line_id." + ) + return + openupgrade.logged_query( + cr, + """ + CREATE TABLE IF NOT EXISTS _ou_haac_sheet_link AS + SELECT id AS sheet_id, advance_sheet_id + FROM hr_expense_sheet + WHERE advance_sheet_id IS NOT NULL + """, + ) diff --git a/hr_expense_advance_clearing/models/__init__.py b/hr_expense_advance_clearing/models/__init__.py new file mode 100644 index 000000000..5a9c8cfe0 --- /dev/null +++ b/hr_expense_advance_clearing/models/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import hr_expense +from . import hr_employee +from . import account_move +from . import account_payment diff --git a/hr_expense_advance_clearing/models/account_move.py b/hr_expense_advance_clearing/models/account_move.py new file mode 100644 index 000000000..16d55b48b --- /dev/null +++ b/hr_expense_advance_clearing/models/account_move.py @@ -0,0 +1,139 @@ +# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import models +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = "account.move" + + def action_post(self): + self._reroute_clearing_entries() + res = super().action_post() + self._reconcile_advance_clearing() + return res + + def _reroute_clearing_entries(self): + """A clearing entry is an internal journal entry, not a purchase + document — but core's expense posting forces it into the purchase + journal (whence a 'BILL/...' name). Move draft clearing entries to a + general journal so they read and number as journal entries.""" + for move in self: + if move.state != "draft" or move.move_type != "entry": + continue + if not move.line_ids.expense_id.filtered("clearing_advance_id"): + continue + if move.journal_id.type == "general": + continue + general = self.env["account.journal"].search( + [("type", "=", "general"), ("company_id", "=", move.company_id.id)], + limit=1, + ) + if general: + move.journal_id = general + + def _reconcile_advance_clearing(self): + """After a clearing entry posts, reconcile its employee-advance-account + credit line against the advance move's matching debit line, so the + clearing consumes the advance.""" + emp_advance = self.env.ref( + "hr_expense_advance_clearing.product_emp_advance", False + ) + if not emp_advance or not emp_advance.property_account_expense_id: + return + account_advance = emp_advance.property_account_expense_id + for move in self: + clearing = move.line_ids.expense_id.filtered("clearing_advance_id") + if not clearing: + continue + advance_moves = clearing.clearing_advance_id.account_move_id + lines = (move.line_ids | advance_moves.line_ids).filtered( + lambda line: line.account_id == account_advance and not line.reconciled + ) + if len(lines) > 1: + lines.reconcile() + + def _check_hr_advance_move_reconciled(self): + """Block draft/cancel/reverse while the advance line is still + reconciled with a clearing or return.""" + av_moves = self.filtered( + lambda m: any( + line.expense_id.expense_type == "advance" for line in m.line_ids + ) + ) + emp_advance = self.env.ref( + "hr_expense_advance_clearing.product_emp_advance", False + ) + if not emp_advance: + return + reconciled_av_move_lines = av_moves.mapped("line_ids").filtered( + lambda line: line.product_id == emp_advance and line.matching_number + ) + if reconciled_av_move_lines: + raise UserError( + self.env._( + "This operation is not allowed as some advance amount was " + "already cleared/returned.\nPlease cancel those documents " + "first." + ) + ) + + def button_draft(self): + if self.env.context.get("skip_invoice_sync"): + return super().button_draft() + self._check_hr_advance_move_reconciled() + # Clearing entries keep an explicit balance + explicit tax lines. The + # tax recompute the invoice-sync runs on reset-to-draft would otherwise + # strip the tax twice and add a spurious "Automatic Balancing Line"; + # reset those entries without the sync. + clearing_moves = self.filtered( + lambda m: m.move_type == "entry" + and m.line_ids.expense_id.filtered("clearing_advance_id") + ) + if not clearing_moves: + return super().button_draft() + (self - clearing_moves).button_draft() + return clearing_moves.with_context(skip_invoice_sync=True).button_draft() + + def button_cancel(self): + self._check_hr_advance_move_reconciled() + return super().button_cancel() + + def _reverse_moves(self, default_values_list=None, cancel=False): + self._check_hr_advance_move_reconciled() + return super()._reverse_moves( + default_values_list=default_values_list, cancel=cancel + ) + + def _compute_amount(self): + """For a clearing expense's move, report only the unreconciled + receivable/payable residual.""" + res = super()._compute_amount() + for move in self: + total_residual = 0.0 + total_residual_currency = 0.0 + for line in move.line_ids: + if line.account_type not in ("asset_receivable", "liability_payable"): + continue + # Line residual amount on a clearing expense. + clearing = line.expense_id.filtered("clearing_advance_id") + if clearing: + total_residual += line.amount_residual + total_residual_currency += line.amount_residual_currency + if total_residual and total_residual_currency: + sign = move.direction_sign + move.amount_residual = -sign * total_residual + move.amount_residual_signed = total_residual_currency + return res + + def action_force_register_payment(self): + """Allow Register Payment on a clearing expense's entry move when its + total exceeds the advance, which core would otherwise block.""" + if all( + m.move_type == "entry" + and any(line.expense_id.clearing_advance_id for line in m.line_ids) + for m in self + ): + return self.line_ids.action_register_payment(ctx={"expense_clearing": 1}) + return super().action_force_register_payment() diff --git a/hr_expense_advance_clearing/models/account_payment.py b/hr_expense_advance_clearing/models/account_payment.py new file mode 100644 index 000000000..2a9ba0698 --- /dev/null +++ b/hr_expense_advance_clearing/models/account_payment.py @@ -0,0 +1,34 @@ +# Copyright 2022 Ecosoft Co., Ltd. (https://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import api, fields, models + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + advance_id = fields.Many2one( + comodel_name="hr.expense", + string="Advance", + domain="[('expense_type', '=', 'advance')]", + readonly=True, + help="The employee advance this payment relates to — the advance it " + "pays out, or the advance whose unused balance it returns.", + ) + + def _synchronize_from_moves(self, changed_fields): + """Skip the standard move-payment sync for return-advance payments + — the move is a generic entry, not the payment's own posting flow.""" + self = ( + self.with_context(skip_account_move_synchronization=True) + if self.filtered("advance_id") + else self + ) + return super()._synchronize_from_moves(changed_fields) + + @api.model + def _get_valid_payment_account_types(self): + account_types = super()._get_valid_payment_account_types() + if self.env.context.get("hr_return_advance"): + account_types.append("asset_current") + return account_types diff --git a/hr_expense_advance_clearing/models/hr_employee.py b/hr_expense_advance_clearing/models/hr_employee.py new file mode 100644 index 000000000..ad25efcdf --- /dev/null +++ b/hr_expense_advance_clearing/models/hr_employee.py @@ -0,0 +1,44 @@ +# Copyright 2021 Ecosoft Co., Ltd. (http://ecosoft.co.th) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class HrEmployee(models.Model): + _inherit = "hr.employee" + + advance_expense_ids = fields.One2many( + comodel_name="hr.expense", + inverse_name="employee_id", + domain=[("expense_type", "=", "advance")], + readonly=True, + ) + advance_count = fields.Integer( + string="# of Advances", + compute="_compute_advance_count", + help="Count of advance expenses paid to this employee.", + ) + + def _compute_advance_count(self): + for employee in self: + employee.advance_count = len(employee.advance_expense_ids) + + def action_open_advance_clearing(self): + self.ensure_one() + return { + "name": self.env._("Employee Advances"), + "type": "ir.actions.act_window", + "res_model": "hr.expense", + "view_mode": "list,form", + "domain": [("id", "in", self.advance_expense_ids.ids)], + } + + +class HrEmployeePublic(models.Model): + _inherit = "hr.employee.public" + + advance_count = fields.Integer(related="employee_id.advance_count") + + def action_open_advance_clearing(self): + self.ensure_one() + return self.employee_id.action_open_advance_clearing() diff --git a/hr_expense_advance_clearing/models/hr_expense.py b/hr_expense_advance_clearing/models/hr_expense.py new file mode 100644 index 000000000..bfea5bb8c --- /dev/null +++ b/hr_expense_advance_clearing/models/hr_expense.py @@ -0,0 +1,418 @@ +# Copyright 2019 Kitti Upariphutthiphong +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from collections import defaultdict + +from odoo import Command, api, fields, models +from odoo.exceptions import UserError, ValidationError + + +class HrExpense(models.Model): + """Add advance/clearing handling: an advance expense paid to the employee + is cleared by reconciling later regular expenses against it.""" + + _inherit = "hr.expense" + + expense_type = fields.Selection( + selection=[("expense", "Expense"), ("advance", "Advance")], + default="expense", + required=True, + tracking=True, + help="An 'advance' is money paid to the employee up front. Regular " + "'expense' records are either reimbursements or clearings against " + "a prior advance (when clearing_advance_id is set).", + ) + clearing_advance_id = fields.Many2one( + comodel_name="hr.expense", + string="Clearing Advance", + domain="[('expense_type', '=', 'advance')," + " ('employee_id', '=', employee_id)," + " ('clearing_residual', '>', 0.0)]", + tracking=True, + ondelete="restrict", + help="When set on a regular expense, this expense clears against " + "the referenced advance. Only advances of the same employee " + "with residual > 0 are selectable.", + ) + clearing_expense_ids = fields.One2many( + comodel_name="hr.expense", + inverse_name="clearing_advance_id", + string="Clearing Expenses", + readonly=True, + help="Expenses cleared against this advance.", + ) + clearing_count = fields.Integer(compute="_compute_clearing_count") + payment_return_ids = fields.One2many( + comodel_name="account.payment", + inverse_name="advance_id", + string="Returned Payments", + readonly=True, + help="Refund payments returning unused advance money to the company.", + ) + return_count = fields.Integer(compute="_compute_return_count", compute_sudo=True) + + cleared_amount = fields.Monetary( + compute="_compute_clearing_residual", + store=True, + help="Sum of clearing expenses' totals (company currency).", + ) + returned_amount = fields.Monetary( + compute="_compute_clearing_residual", + store=True, + help="Sum of return-advance payments (company currency).", + ) + clearing_residual = fields.Monetary( + string="Amount to Clear", + compute="_compute_clearing_residual", + store=True, + help="Advance amount remaining to be cleared or returned.", + ) + advance_residual = fields.Monetary( + string="Advance Remaining", + related="clearing_advance_id.clearing_residual", + store=True, + help="Remaining amount on the linked advance (for clearing expenses).", + ) + amount_payable = fields.Monetary( + string="Payable Amount", + compute="_compute_amount_payable", + help="Register-payment amount after subtracting the cleared advance.", + ) + clearing_product_id = fields.Many2one( + comodel_name="product.product", + string="Clearing Product", + tracking=True, + domain="[('can_be_expensed', '=', True)," + " '|', ('company_id', '=', False), ('company_id', '=', company_id)]", + ondelete="restrict", + help="Optional: when set on an advance, this product is used as the " + "default for clearing expense lines.", + ) + + # ------------------------------------------------------------------------- + # Helpers + # ------------------------------------------------------------------------- + + def _get_product_advance(self): + return self.env.ref("hr_expense_advance_clearing.product_emp_advance", False) + + # ------------------------------------------------------------------------- + # Constraints + # ------------------------------------------------------------------------- + + @api.constrains("expense_type", "product_id", "tax_ids", "payment_mode") + def _check_advance(self): + """An advance expense must use the Employee Advance product, no + taxes, and be employee-paid.""" + emp_advance = self._get_product_advance() + if not emp_advance: + return True + for expense in self.filtered(lambda e: e.expense_type == "advance"): + if not emp_advance.property_account_expense_id: + raise ValidationError( + self.env._("Employee advance product has no payable account") + ) + if expense.product_id != emp_advance: + raise ValidationError( + self.env._("Employee advance, selected product is not valid") + ) + if expense.tax_ids: + raise ValidationError( + self.env._("Employee advance, all taxes must be removed") + ) + if expense.payment_mode != "own_account": + raise ValidationError( + self.env._("Employee advance, paid by must be employee") + ) + return True + + @api.constrains("clearing_advance_id", "expense_type", "employee_id") + def _check_clearing(self): + """A clearing expense must be 'expense' type, must reference an + advance of the same employee, and the advance must be paid.""" + for expense in self.filtered("clearing_advance_id"): + if expense.expense_type == "advance": + raise ValidationError( + self.env._("An advance cannot itself clear another advance.") + ) + advance = expense.clearing_advance_id + if advance.employee_id != expense.employee_id: + raise ValidationError( + self.env._("The linked advance must belong to the same employee.") + ) + + # ------------------------------------------------------------------------- + # Onchanges + defaults + # ------------------------------------------------------------------------- + + @api.onchange("expense_type") + def _onchange_expense_type(self): + if self.expense_type == "advance": + emp_advance = self._get_product_advance() + if emp_advance: + self.product_id = emp_advance + self.tax_ids = False + self.payment_mode = "own_account" + self.clearing_advance_id = False + + @api.onchange("clearing_advance_id") + def _onchange_clearing_advance_id(self): + """Pre-fill clearing product from the advance if it has one set.""" + advance = self.clearing_advance_id + if advance and advance.clearing_product_id and not self.product_id: + self.product_id = advance.clearing_product_id + + # ------------------------------------------------------------------------- + # Computes + # ------------------------------------------------------------------------- + + @api.depends( + "expense_type", + "total_amount_currency", + "clearing_expense_ids.total_amount_currency", + "clearing_expense_ids.state", + "payment_return_ids.amount", + "payment_return_ids.state", + ) + def _compute_clearing_residual(self): + for expense in self: + if expense.expense_type != "advance": + expense.cleared_amount = 0.0 + expense.returned_amount = 0.0 + expense.clearing_residual = 0.0 + continue + cleared = sum( + expense.clearing_expense_ids.filtered( + lambda e: e.state not in ("draft", "submitted", "refused") + ).mapped("total_amount_currency") + ) + returned = sum( + expense.payment_return_ids.filtered( + lambda p: p.state in ("in_process", "paid") + ).mapped("amount") + ) + expense.cleared_amount = cleared + expense.returned_amount = returned + # Clamp at 0: over-clearing consumes the whole advance (the excess + # is the employee's out-of-pocket, booked to the payable), so the + # advance has nothing left rather than a negative residual. + expense.clearing_residual = max( + expense.total_amount_currency - cleared - returned, 0.0 + ) + + @api.depends("clearing_expense_ids") + def _compute_clearing_count(self): + for expense in self: + expense.clearing_count = len(expense.clearing_expense_ids) + + @api.depends("payment_return_ids") + def _compute_return_count(self): + for expense in self: + expense.return_count = len(expense.payment_return_ids) + + @api.depends( + "total_amount_currency", + "clearing_advance_id.clearing_residual", + ) + def _compute_amount_payable(self): + """For a clearing expense: payable = total − advance available.""" + for expense in self: + if not expense.clearing_advance_id: + expense.amount_payable = 0.0 + continue + residual = expense.clearing_advance_id.clearing_residual + expense.amount_payable = max(expense.total_amount_currency - residual, 0.0) + + # ------------------------------------------------------------------------- + # Server actions + # ------------------------------------------------------------------------- + + def action_return_advance(self): + """Open Register Payment for the remaining advance residual so the + employee can refund what they didn't spend.""" + self.ensure_one() + if self.expense_type != "advance": + raise UserError(self.env._("Only advance expenses can be returned.")) + if self.clearing_residual <= 0: + raise UserError(self.env._("This advance has no residual to return.")) + # The register-payment wizard must be invoked on account.move(.line) + # records: target the advance's still-open employee-advance line so the + # returned payment reconciles against it. + account_advance = self._get_product_advance().property_account_expense_id + advance_line = self.account_move_id.line_ids.filtered( + lambda line: line.account_id == account_advance and not line.reconciled + ) + if not advance_line: + raise UserError(self.env._("No open advance line found to return.")) + return { + "type": "ir.actions.act_window", + "name": self.env._("Return Advance"), + "res_model": "account.payment.register", + "view_mode": "form", + "target": "new", + "context": { + "active_model": "account.move.line", + "active_ids": advance_line.ids, + "default_partner_type": "customer", + "default_partner_id": self.employee_id.sudo().work_contact_id.id, + "default_amount": self.clearing_residual, + "default_currency_id": self.currency_id.id, + "default_advance_id": self.id, + "hr_return_advance": 1, + }, + } + + def action_view_clearings(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "name": self.env._("Clearing Expenses"), + "res_model": "hr.expense", + "view_mode": "list,form", + "domain": [("id", "in", self.clearing_expense_ids.ids)], + } + + def action_view_returns(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "name": self.env._("Returned Payments"), + "res_model": "account.payment", + "view_mode": "list,form", + "domain": [("id", "in", self.payment_return_ids.ids)], + } + + # ------------------------------------------------------------------------- + # Posting: clearing expenses book as journal entries against the advance + # ------------------------------------------------------------------------- + + def _prepare_receipts_vals(self): + """Clearing expenses post as ``entry`` moves that credit the + employee-advance account, not as vendor receipts: the clearing + consumes the advance directly instead of raising a fresh payable. + Regular expenses keep core's ``in_receipt`` behaviour.""" + clearing = self.filtered("clearing_advance_id") + regular = self - clearing + vals_list = ( + super(HrExpense, regular)._prepare_receipts_vals() if regular else [] + ) + by_advance = defaultdict(self.browse) + for expense in clearing: + by_advance[expense.clearing_advance_id] |= expense + for advance, expenses in by_advance.items(): + vals_list.append(expenses._prepare_clearing_entry_vals(advance)) + return vals_list + + def _get_advance_analytic_distribution(self, advance_move): + """Return the advance move's analytic distribution when all of its + lines share a single one, so the clearing credit to the advance + account carries the same analytic as the original advance.""" + analytics = [ + a for a in advance_move.line_ids.mapped("analytic_distribution") if a + ] + if len({str(a) for a in analytics}) == 1: + return analytics[0] + return False + + def _prepare_clearing_entry_vals(self, advance): + """Build one ``entry`` move debiting each clearing expense's account + and crediting the employee-advance account (capped at the advance + residual; any excess goes to the employee payable so it can still be + reimbursed). Taxes are reflected via base tags only.""" + emp_advance = self._get_product_advance() + account_advance = emp_advance.property_account_expense_id + partner = advance.employee_id.sudo().work_contact_id + payable_account = partner.property_account_payable_id + # Amounts are booked in the company currency; a clearing expense in a + # foreign currency is converted (total_amount) and not tracked in its + # own currency on the entry. Multi-currency clearing is out of scope. + company_currency = self.company_id.currency_id + # The advance must be posted first: the entry credits the advance + # account and is reconciled against the advance's debit, so without a + # posted advance the credit would dangle. + advance_move = advance.account_move_id + if advance_move.state != "posted": + raise UserError( + self.env._( + "Post the advance %(name)s before clearing expenses against it.", + name=advance.name, + ) + ) + advance_analytic = self._get_advance_analytic_distribution(advance_move) + # Cap against the advance's still-unreconciled balance on the advance + # account (its real GL residual), not the count-based clearing_residual + # field — that field already nets out the approved clearings being + # posted now, which would double-count and under-credit the advance. + advance_lines = advance_move.line_ids.filtered( + lambda line: line.account_id == account_advance + ) + advance_to_clear = sum(advance_lines.mapped("amount_residual")) + line_cmds = [] + for expense in self: + name = expense._get_move_line_name() + taxes = expense.tax_ids.with_context(round=True).compute_all( + expense.price_unit or expense.total_amount, + expense.currency_id, + expense.quantity if expense.price_unit else 1, + expense.product_id, + ) + line_cmds.append( + Command.create( + { + "name": name, + "account_id": expense._get_base_account().id, + "debit": expense.total_amount, + "credit": 0.0, + "currency_id": company_currency.id, + "product_id": expense.product_id.id, + "product_uom_id": expense.product_uom_id.id, + "analytic_distribution": expense.analytic_distribution, + "tax_ids": [Command.set(expense.tax_ids.ids)], + "tax_tag_ids": [Command.set(taxes["base_tags"])], + "expense_id": expense.id, + "partner_id": partner.id, + } + ) + ) + credit = expense.total_amount + cleared = max(min(credit, advance_to_clear), 0.0) + advance_to_clear -= cleared + if cleared: + line_cmds.append( + Command.create( + { + "name": name, + "account_id": account_advance.id, + "debit": 0.0, + "credit": cleared, + "currency_id": company_currency.id, + "analytic_distribution": advance_analytic, + "expense_id": expense.id, + "partner_id": partner.id, + } + ) + ) + remainder = credit - cleared + if remainder: + line_cmds.append( + Command.create( + { + "name": name, + "account_id": payable_account.id, + "debit": 0.0, + "credit": remainder, + "currency_id": company_currency.id, + "expense_id": expense.id, + "partner_id": partner.id, + } + ) + ) + return { + **self._prepare_move_vals(), + "ref": self.env._("Advance clearing: %(name)s", name=advance.name), + "move_type": "entry", + "partner_id": partner.id, + "currency_id": company_currency.id, + "company_id": self.company_id.id, + "line_ids": line_cmds, + } diff --git a/hr_expense_advance_clearing/pyproject.toml b/hr_expense_advance_clearing/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/hr_expense_advance_clearing/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_expense_advance_clearing/readme/CONFIGURE.md b/hr_expense_advance_clearing/readme/CONFIGURE.md new file mode 100644 index 000000000..b24c274eb --- /dev/null +++ b/hr_expense_advance_clearing/readme/CONFIGURE.md @@ -0,0 +1,14 @@ +This module will create a new product "Employee Advance" automatically. +You will need to setup the Expense Account of this product to your +Employee Advance account manually. + +- Open Product window and search for "Employee Advance" +- On Accounting tab, select appropriate employee advance account from + your chart of account + +Note: + +- You will need the "Show Full Accounting Features" to see accounting + data +- Employee Advance account code, if not already exists, you can create + one. Use type = Current Asset and check Allow Reconciliation. diff --git a/hr_expense_advance_clearing/readme/CONTRIBUTORS.md b/hr_expense_advance_clearing/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..66d91554e --- /dev/null +++ b/hr_expense_advance_clearing/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +- Kitti Upariphutthiphong \<\> +- Tharathip Chaweewongphan \<\> +- Saran Lim. \<\> diff --git a/hr_expense_advance_clearing/readme/DESCRIPTION.md b/hr_expense_advance_clearing/readme/DESCRIPTION.md new file mode 100644 index 000000000..293b8854a --- /dev/null +++ b/hr_expense_advance_clearing/readme/DESCRIPTION.md @@ -0,0 +1,6 @@ +This module lets the company pay an advance to an employee and later clear it +against the expenses they actually incur. The employee records an advance +expense, gets paid for it, then files regular expenses that reconcile against +the advance. When an advance is over-spent the employee registers a payment for +the difference, and when it is under-spent they return the unused balance to the +company. diff --git a/hr_expense_advance_clearing/readme/USAGE.md b/hr_expense_advance_clearing/readme/USAGE.md new file mode 100644 index 000000000..eb09fc29c --- /dev/null +++ b/hr_expense_advance_clearing/readme/USAGE.md @@ -0,0 +1,44 @@ +First-time setup: open product **Employee Advance** (auto-created on +install) and set its expense account to your "Employee Advance" +current-asset account with `Allow Reconciliation`. + +**Create an Employee Advance** + +1. Expenses → My Expenses, click New. +2. Set `Expense Type` to **Advance** (Employee Advance product + own_account auto-fill). +3. Optionally set `Clearing Product` — it's the default product for + clearing expense lines that reference this advance. +4. Set the amount, Save. +5. Submit → Approve → Post → Register Payment. The advance is paid to + the employee. + +**Clear the Advance with a regular expense** + +1. Expenses → My Expenses, New (or pick an existing draft). +2. Leave `Expense Type` as **Expense** and set `Clearing Advance` to the + advance you want to clear against. The `Advance Remaining` smart + field shows what's left of the advance. +3. Submit → Approve → Post. On post, the clearing expense's receivable + line auto-reconciles against the advance's receivable line. + +What happens at post time: + +- `clearing_total ≤ advance_residual` → fully covered. No further + payment needed; the advance's residual updates. +- `clearing_total > advance_residual` → the excess remains on the + clearing expense's move. Register Payment for the difference. + +**Return unused advance** + +1. Open the paid advance with `clearing_residual > 0`. +2. Click **Return Advance** (header button). The Register Payment wizard + opens pre-filled with the residual. +3. Confirm. The advance is now fully closed; `clearing_residual = 0`. + +**Trip integration** (when `hr_expense_trip` is installed) + +A trip's `expense_ids` can include both an advance and the regular +expenses paid out of it. The trip module wires a "Clear Trip" button +that calls `action_clear_advance` on the trip's regular expenses with +the trip's advance as the target. This module does not depend on +`hr_expense_trip`; the integration is one-way from the trip side. diff --git a/hr_expense_advance_clearing/static/description/icon.png b/hr_expense_advance_clearing/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/hr_expense_advance_clearing/static/description/icon.png differ diff --git a/hr_expense_advance_clearing/static/description/index.html b/hr_expense_advance_clearing/static/description/index.html new file mode 100644 index 000000000..a3bd176ed --- /dev/null +++ b/hr_expense_advance_clearing/static/description/index.html @@ -0,0 +1,502 @@ + + + + + +Employee Advance and Clearing + + + +
+ + + +Odoo Community Association + +
+

Employee Advance and Clearing

+ +

Beta License: AGPL-3 OCA/hr-expense Translate me on Weblate Try me on Runboat

+

This module lets the company pay an advance to an employee and later +clear it against the expenses they actually incur. The employee records +an advance expense, gets paid for it, then files regular expenses that +reconcile against the advance. When an advance is over-spent the +employee registers a payment for the difference, and when it is +under-spent they return the unused balance to the company.

+

Table of contents

+ +
+

Configuration

+

This module will create a new product “Employee Advance” automatically. +You will need to setup the Expense Account of this product to your +Employee Advance account manually.

+
    +
  • Open Product window and search for “Employee Advance”
  • +
  • On Accounting tab, select appropriate employee advance account from +your chart of account
  • +
+

Note:

+
    +
  • You will need the “Show Full Accounting Features” to see accounting +data
  • +
  • Employee Advance account code, if not already exists, you can create +one. Use type = Current Asset and check Allow Reconciliation.
  • +
+
+
+

Usage

+

First-time setup: open product Employee Advance (auto-created on +install) and set its expense account to your “Employee Advance” +current-asset account with Allow Reconciliation.

+

Create an Employee Advance

+
    +
  1. Expenses → My Expenses, click New.
  2. +
  3. Set Expense Type to Advance (Employee Advance product + +own_account auto-fill).
  4. +
  5. Optionally set Clearing Product — it’s the default product for +clearing expense lines that reference this advance.
  6. +
  7. Set the amount, Save.
  8. +
  9. Submit → Approve → Post → Register Payment. The advance is paid to +the employee.
  10. +
+

Clear the Advance with a regular expense

+
    +
  1. Expenses → My Expenses, New (or pick an existing draft).
  2. +
  3. Leave Expense Type as Expense and set Clearing Advance to +the advance you want to clear against. The Advance Remaining +smart field shows what’s left of the advance.
  4. +
  5. Submit → Approve → Post. On post, the clearing expense’s receivable +line auto-reconciles against the advance’s receivable line.
  6. +
+

What happens at post time:

+
    +
  • clearing_total ≤ advance_residual → fully covered. No further +payment needed; the advance’s residual updates.
  • +
  • clearing_total > advance_residual → the excess remains on the +clearing expense’s move. Register Payment for the difference.
  • +
+

Return unused advance

+
    +
  1. Open the paid advance with clearing_residual > 0.
  2. +
  3. Click Return Advance (header button). The Register Payment wizard +opens pre-filled with the residual.
  4. +
  5. Confirm. The advance is now fully closed; clearing_residual = 0.
  6. +
+

Trip integration (when hr_expense_trip is installed)

+

A trip’s expense_ids can include both an advance and the regular +expenses paid out of it. The trip module wires a “Clear Trip” button +that calls action_clear_advance on the trip’s regular expenses with +the trip’s advance as the target. This module does not depend on +hr_expense_trip; the integration is one-way from the trip side.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Ecosoft
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/hr-expense project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+
+ + diff --git a/hr_expense_advance_clearing/tests/__init__.py b/hr_expense_advance_clearing/tests/__init__.py new file mode 100644 index 000000000..051a4f1d9 --- /dev/null +++ b/hr_expense_advance_clearing/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_hr_expense_advance_clearing diff --git a/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py new file mode 100644 index 000000000..d8462ab03 --- /dev/null +++ b/hr_expense_advance_clearing/tests/test_hr_expense_advance_clearing.py @@ -0,0 +1,267 @@ +# Copyright 2019 Kitti Upariphutthiphong +# Copyright 2024 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.exceptions import UserError, ValidationError +from odoo.tests import tagged + +from odoo.addons.hr_expense.tests.common import TestExpenseCommon + + +@tagged("-at_install", "post_install") +class TestHrExpenseAdvanceClearing(TestExpenseCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + advance_account = cls.company_data["default_account_deferred_expense"] + advance_account.reconcile = True + cls.emp_advance = cls.env.ref("hr_expense_advance_clearing.product_emp_advance") + cls.emp_advance.property_account_expense_id = advance_account + cls.product_a.standard_price = 0 + + def _new_advance(self, amount=1000.0): + advance = self.env["hr.expense"].create( + { + "name": f"Advance {amount}", + "employee_id": self.expense_employee.id, + "product_id": self.emp_advance.id, + "total_amount_currency": amount, + "expense_type": "advance", + "payment_mode": "own_account", + } + ) + advance.action_submit() + if advance.state == "submitted": + advance.action_approve() + return advance + + def _new_clearing(self, advance, amount): + return self.env["hr.expense"].create( + { + "name": f"Clearing {amount}", + "employee_id": self.expense_employee.id, + "product_id": self.product_a.id, + "total_amount_currency": amount, + "payment_mode": "own_account", + "clearing_advance_id": advance.id, + } + ) + + def test_advance_constraint_taxes_forbidden(self): + """An advance expense cannot carry taxes.""" + tax = self.env["account.tax"].create( + {"name": "T", "amount": 10.0, "type_tax_use": "purchase"} + ) + advance = self.env["hr.expense"].create( + { + "name": "Advance", + "employee_id": self.expense_employee.id, + "product_id": self.emp_advance.id, + "total_amount_currency": 100.0, + "expense_type": "advance", + "payment_mode": "own_account", + } + ) + with self.assertRaises(ValidationError): + advance.tax_ids = [(6, 0, tax.ids)] + + def test_clearing_constraint_same_employee(self): + """A clearing expense's advance must belong to the same employee.""" + advance = self._new_advance() + # hr.employee creation requires resource.resource access in 19.0; + # use sudo() since this test isn't exercising employee permissions. + other_emp = self.env["hr.employee"].sudo().create({"name": "Other"}) + with self.assertRaises(ValidationError): + self.env["hr.expense"].create( + { + "name": "Mismatch", + "employee_id": other_emp.id, + "product_id": self.product_a.id, + "total_amount_currency": 50.0, + "clearing_advance_id": advance.id, + } + ) + + def test_clearing_residual_tracks_drafts_vs_approved(self): + """clearing_residual excludes draft/submitted/refused clearings; + only approved+ clearings count.""" + advance = self._new_advance(1000.0) + c_draft = self._new_clearing(advance, 300.0) + self.assertEqual(c_draft.state, "draft") + self.assertEqual(advance.cleared_amount, 0.0) + self.assertEqual(advance.clearing_residual, 1000.0) + c_draft.action_submit() + if c_draft.state == "submitted": + c_draft.action_approve() + advance.invalidate_recordset(["cleared_amount", "clearing_residual"]) + self.assertEqual(advance.cleared_amount, 300.0) + self.assertEqual(advance.clearing_residual, 700.0) + + def test_amount_payable_on_clearing(self): + """A clearing expense's amount_payable = max(total − advance_residual, 0).""" + advance = self._new_advance(1000.0) + c_eq = self._new_clearing(advance, 1000.0) + self.assertEqual(c_eq.amount_payable, 0.0) + c_gt = self._new_clearing(advance, 1500.0) + self.assertEqual(c_gt.amount_payable, 500.0) + c_lt = self._new_clearing(advance, 500.0) + self.assertEqual(c_lt.amount_payable, 0.0) + + def test_employee_advance_count(self): + """advance_count + advance_expense_ids on hr.employee.""" + self.assertEqual(self.expense_employee.advance_count, 0) + self._new_advance(500.0) + self.expense_employee.invalidate_recordset(["advance_count"]) + self.assertEqual(self.expense_employee.advance_count, 1) + + def test_action_return_advance_guards(self): + """action_return_advance gates on expense_type + residual, and + returns the payment-register wizard action when valid.""" + advance = self._new_advance(500.0) + self._post(advance) + # A regular expense cannot be returned. + regular = self.env["hr.expense"].create( + { + "name": "regular", + "employee_id": self.expense_employee.id, + "product_id": self.product_a.id, + "total_amount_currency": 50.0, + "payment_mode": "own_account", + } + ) + with self.assertRaises(UserError): + regular.action_return_advance() + # The advance has residual → action returns the wizard. + self.assertGreater(advance.clearing_residual, 0) + action = advance.action_return_advance() + self.assertEqual(action["res_model"], "account.payment.register") + self.assertEqual(action["context"]["default_advance_id"], advance.id) + self.assertTrue(action["context"].get("hr_return_advance")) + # The wizard is invoked on the advance's open move line (not on a bare + # hr.expense), so the register-payment wizard accepts it. + self.assertEqual(action["context"]["active_model"], "account.move.line") + self.assertTrue(action["context"]["active_ids"]) + # Once fully cleared, residual is zero → action raises. + clearing = self._new_clearing(advance, 500.0) + clearing.action_submit() + if clearing.state == "submitted": + clearing.action_approve() + advance.invalidate_recordset(["cleared_amount", "clearing_residual"]) + self.assertEqual(advance.clearing_residual, 0.0) + with self.assertRaises(UserError): + advance.action_return_advance() + + # ------------------------------------------------------------------------- + # Posting: clearing books as a journal entry against the advance + # ------------------------------------------------------------------------- + + def _post(self, expense): + if expense.state == "draft": + expense.action_submit() + if expense.state == "submitted": + expense.action_approve() + self.post_expenses_with_wizard(expense) + + def test_clearing_posts_as_entry_and_reconciles(self): + """A clearing expense posts as a journal entry (not a vendor receipt) + crediting the advance account, reconciled against the advance.""" + account_advance = self.emp_advance.property_account_expense_id + advance = self._new_advance(1000.0) + self._post(advance) + # The advance itself keeps core's vendor-receipt behaviour. + self.assertEqual(advance.account_move_id.move_type, "in_receipt") + clearing = self._new_clearing(advance, 600.0) + self._post(clearing) + move = clearing.account_move_id + self.assertEqual(move.move_type, "entry") + # Booked as a journal entry in a general journal, not a purchase bill. + self.assertEqual(move.journal_id.type, "general") + adv_lines = move.line_ids.filtered( + lambda line: line.account_id == account_advance + ) + self.assertAlmostEqual(sum(adv_lines.mapped("credit")), 600.0) + self.assertTrue( + all(adv_lines.mapped("reconciled")), + "clearing entry's advance line must reconcile against the advance", + ) + advance.invalidate_recordset(["cleared_amount", "clearing_residual"]) + self.assertAlmostEqual(advance.clearing_residual, 400.0) + # The advance's own GL line is now partially reconciled: 600 of 1000 + # consumed, 400 still open. + advance_gl = advance.account_move_id.line_ids.filtered( + lambda line: line.account_id == account_advance + ) + self.assertAlmostEqual(sum(advance_gl.mapped("amount_residual")), 400.0) + + def test_clearing_over_advance_splits_to_payable(self): + """When the clearing exceeds the advance, the excess books to the + employee payable so it can still be reimbursed.""" + account_advance = self.emp_advance.property_account_expense_id + payable = ( + self.expense_employee.sudo().work_contact_id.property_account_payable_id + ) + advance = self._new_advance(1000.0) + self._post(advance) + clearing = self._new_clearing(advance, 1500.0) + self._post(clearing) + move = clearing.account_move_id + self.assertEqual(move.move_type, "entry") + self.assertAlmostEqual( + sum( + move.line_ids.filtered( + lambda line: line.account_id == account_advance + ).mapped("credit") + ), + 1000.0, + ) + self.assertAlmostEqual( + sum( + move.line_ids.filtered(lambda line: line.account_id == payable).mapped( + "credit" + ) + ), + 500.0, + ) + advance.invalidate_recordset(["cleared_amount", "clearing_residual"]) + self.assertEqual(advance.clearing_residual, 0.0) + # The leftover 500 stays open on the move as the amount still due to + # the employee (drives amount_payable / Register Payment). + self.assertAlmostEqual(move.amount_residual, 500.0) + + def test_multiple_clearings_group_into_one_entry(self): + """Several clearings of the same advance posted together produce a + single entry, each debiting its own expense line, jointly crediting + the advance.""" + account_advance = self.emp_advance.property_account_expense_id + advance = self._new_advance(1000.0) + self._post(advance) + c1 = self._new_clearing(advance, 300.0) + c2 = self._new_clearing(advance, 250.0) + expenses = c1 | c2 + for expense in expenses: + expense.action_submit() + if expense.state == "submitted": + expense.action_approve() + self.post_expenses_with_wizard(expenses) + self.assertEqual(c1.account_move_id, c2.account_move_id) + move = c1.account_move_id + self.assertEqual(move.move_type, "entry") + self.assertEqual(len(move.line_ids.expense_id), 2) + self.assertAlmostEqual( + sum( + move.line_ids.filtered( + lambda line: line.account_id == account_advance + ).mapped("credit") + ), + 550.0, + ) + advance.invalidate_recordset(["cleared_amount", "clearing_residual"]) + self.assertAlmostEqual(advance.clearing_residual, 450.0) + + def test_clearing_before_advance_posted_is_blocked(self): + """A clearing cannot post while its advance is still unposted — + otherwise its advance-account credit would dangle.""" + advance = self._new_advance(1000.0) # approved but NOT posted + clearing = self._new_clearing(advance, 200.0) + with self.assertRaises(UserError): + self._post(clearing) diff --git a/hr_expense_advance_clearing/views/account_payment_view.xml b/hr_expense_advance_clearing/views/account_payment_view.xml new file mode 100644 index 000000000..849fad05e --- /dev/null +++ b/hr_expense_advance_clearing/views/account_payment_view.xml @@ -0,0 +1,29 @@ + + + + account.payment.search + account.payment + + + + + + + + + + + account.payment.form + account.payment + + + + + + + + diff --git a/hr_expense_advance_clearing/views/hr_employee_public_views.xml b/hr_expense_advance_clearing/views/hr_employee_public_views.xml new file mode 100644 index 000000000..3d9b0e8d5 --- /dev/null +++ b/hr_expense_advance_clearing/views/hr_employee_public_views.xml @@ -0,0 +1,19 @@ + + + + hr.employee.public.form + hr.employee.public + + + + + + + + diff --git a/hr_expense_advance_clearing/views/hr_employee_views.xml b/hr_expense_advance_clearing/views/hr_employee_views.xml new file mode 100644 index 000000000..ed872da9b --- /dev/null +++ b/hr_expense_advance_clearing/views/hr_employee_views.xml @@ -0,0 +1,20 @@ + + + + hr.employee.form + hr.employee + + + + + + + + diff --git a/hr_expense_advance_clearing/views/hr_expense_views.xml b/hr_expense_advance_clearing/views/hr_expense_views.xml new file mode 100644 index 000000000..80be553a7 --- /dev/null +++ b/hr_expense_advance_clearing/views/hr_expense_views.xml @@ -0,0 +1,98 @@ + + + + hr.expense.view.search - advance_clearing + hr.expense + + + + + + + + + + + + hr.expense.view.form - advance_clearing + hr.expense + + +

+ +

+ + state != 'draft' or expense_type == 'advance' + 1 + + + + + + + + + expense_type == 'advance' + + + + + + +