diff --git a/README.md b/README.md index 56b6e3a7167..23fc6609b35 100644 --- a/README.md +++ b/README.md @@ -21,20 +21,24 @@ Available addons ---------------- addon | version | maintainers | summary --- | --- | --- | --- -[account_check_printing_report_base](account_check_printing_report_base/) | 18.0.1.0.0 | | Account Check Printing Report Base +[account_check_printing_report_base](account_check_printing_report_base/) | 18.0.1.0.1 | | Account Check Printing Report Base [account_due_list](account_due_list/) | 18.0.1.0.0 | | List of open credits and debits, with due date [account_due_list_payment_mode](account_due_list_payment_mode/) | 18.0.1.0.0 | | Payment Due List Payment Mode +[account_force_early_discount](account_force_early_discount/) | 18.0.1.0.0 | grindtildeath | Allow forcing financial discounts for early payments +[account_payment_line](account_payment_line/) | 18.0.1.0.0 | ChrisOForgeFlow | Payment Counterpart Lines [account_payment_method_base](account_payment_method_base/) | 18.0.1.0.0 | alexis-via | Add form and list view for account.payment.method [account_payment_notification](account_payment_notification/) | 18.0.1.0.1 | yajo rafaelbn | Notifiy upcoming payments [account_payment_promissory_note](account_payment_promissory_note/) | 18.0.1.0.1 | | Account Payment Promissory Note -[account_payment_return](account_payment_return/) | 18.0.1.0.2 | | Manage the return of your payments +[account_payment_return](account_payment_return/) | 18.0.1.0.7 | | Manage the return of your payments [account_payment_return_import](account_payment_return_import/) | 18.0.1.0.1 | | This module adds a generic wizard to import payment returnfile formats. Is only the base to be extended by anothermodules -[account_payment_return_import_iso20022](account_payment_return_import_iso20022/) | 18.0.1.1.0 | | This addon allows to import payment returns from ISO 20022 files like PAIN or CAMT. +[account_payment_return_import_iso20022](account_payment_return_import_iso20022/) | 18.0.1.1.1 | | This addon allows to import payment returns from ISO 20022 files like PAIN or CAMT. [account_payment_show_invoice](account_payment_show_invoice/) | 18.0.1.0.0 | | Extends the tree view of payments to show the paid invoices related to the payments using the vendor reference by default [account_payment_term_extension](account_payment_term_extension/) | 18.0.1.0.0 | | Adds rounding, months, weeks and multiple payment days properties on payment term lines [account_payment_tier_validation](account_payment_tier_validation/) | 18.0.1.0.1 | | Extends the functionality of Payment to support a tier validation process. +[account_payment_widget_amount](account_payment_widget_amount/) | 18.0.1.0.0 | | Extends the payment widget to be able to choose the payment amount [account_refund_early_payment_discount](account_refund_early_payment_discount/) | 18.0.1.0.0 | grindtildeath | Suppoprt early payment discount on credit notes [account_voucher_killer](account_voucher_killer/) | 18.0.1.0.0 | | Prevent the usage of payments from invoices +[partner_aging](partner_aging/) | 18.0.1.0.0 | Urvisha-OSI | Aging as a view - invoices and credits [//]: # (end addons) diff --git a/account_check_printing_report_base/README.rst b/account_check_printing_report_base/README.rst index 2e87b344b37..b84444b5c80 100644 --- a/account_check_printing_report_base/README.rst +++ b/account_check_printing_report_base/README.rst @@ -1,3 +1,7 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + ================================== Account Check Printing Report Base ================================== @@ -7,13 +11,13 @@ Account Check Printing Report Base !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:27ebc7c54703854d7c51fafb184e47fc0bbb992b6f9d4a063af3570107ee61c2 + !! source digest: sha256:fb169864e7abd3215b6cd98ebea3e8ea6d55d9e6d19440b656b555512131d2fe !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |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/licence-AGPL--3-blue.png +.. |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%2Faccount--payment-lightgray.png?logo=github @@ -76,16 +80,16 @@ Credits Authors ------- -* Eficent +* ForgeFlow * Serpent Consulting Services Pvt. Ltd. * Ursa Information Systems Contributors ------------ -- Jordi Ballester Alomar +- Jordi Ballester Alomar -- Lois Rilo Antelo +- Lois Rilo Antelo - Sandip Mangukiya diff --git a/account_check_printing_report_base/__manifest__.py b/account_check_printing_report_base/__manifest__.py index 90ae4fc6cce..d013f8c52bf 100644 --- a/account_check_printing_report_base/__manifest__.py +++ b/account_check_printing_report_base/__manifest__.py @@ -1,5 +1,5 @@ -# Copyright 2016 Eficent Business and IT Consulting Services S.L. -# (http://www.eficent.com) +# Copyright 2016 ForgeFlow, S.L. +# (http://www.forgeflow.com) # Copyright 2016 Serpent Consulting Services Pvt. Ltd. # Copyright 2017 Tecnativa. # Copyright 2018 iterativo. @@ -7,9 +7,9 @@ { "name": "Account Check Printing Report Base", - "version": "18.0.1.0.0", + "version": "18.0.1.0.1", "license": "AGPL-3", - "author": "Eficent," + "author": "ForgeFlow," "Serpent Consulting Services Pvt. Ltd.," "Ursa Information Systems," "Odoo Community Association (OCA)", diff --git a/account_check_printing_report_base/i18n/zh_TW.po b/account_check_printing_report_base/i18n/zh_TW.po new file mode 100644 index 00000000000..07225c2b7ea --- /dev/null +++ b/account_check_printing_report_base/i18n/zh_TW.po @@ -0,0 +1,153 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_check_printing_report_base +# +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: zh_TW\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: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base +msgid "Check Amount:" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model.fields,field_description:account_check_printing_report_base.field_account_journal__check_print_auto +msgid "Automatic check printing" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Balance Due" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model.fields,field_description:account_check_printing_report_base.field_res_company__account_check_printing_layout +msgid "Check Layout" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_res_company +msgid "Companies" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Dear partner" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model.fields,help:account_check_printing_report_base.field_account_journal__check_print_auto +msgid "" +"Default check for the company is printed automatically when invoice payment " +"is validated" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base +msgid "Description" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Due Date" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Invoice / Reference" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Invoice date" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_account_journal +msgid "Journal" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Original Amount" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_account_payment_register +msgid "Pay" +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Payment" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.actions.report,name:account_check_printing_report_base.action_report_check_base +msgid "Payment Check Base" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.actions.report,name:account_check_printing_report_base.action_report_check_base_a4 +msgid "Payment Check Base A4" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_account_payment +msgid "Payments" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model.fields.selection,name:account_check_printing_report_base.selection__res_company__account_check_printing_layout__account_check_printing_report_base_action_report_check_base +msgid "Print Check Base" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model.fields.selection,name:account_check_printing_report_base.selection__res_company__account_check_printing_layout__account_check_printing_report_base_action_report_check_base_a4 +msgid "Print Check Base A4" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_report_account_check_printing_report_base_report_check_base +msgid "Report Check Print" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_report_account_check_printing_report_base_report_check_base_a4 +msgid "Report Check Print for A4" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model,name:account_check_printing_report_base.model_report_account_check_printing_report_base_promissory_footer_a4 +msgid "Report Promissory Note Print for A4" +msgstr "" + +#. module: account_check_printing_report_base +#: model:ir.model.fields,help:account_check_printing_report_base.field_res_company__account_check_printing_layout +msgid "" +"Select the format corresponding to the check paper you will be printing your checks on.\n" +"In order to disable the printing feature, select 'None'." +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "Waiting for your agreement, regards." +msgstr "" + +#. module: account_check_printing_report_base +#: model_terms:ir.ui.view,arch_db:account_check_printing_report_base.report_check_base_a4_document +msgid "We attach the list of invoices paid with this check with amount of" +msgstr "" diff --git a/account_check_printing_report_base/models/account_payment.py b/account_check_printing_report_base/models/account_payment.py index a629998402e..8014ef4421a 100644 --- a/account_check_printing_report_base/models/account_payment.py +++ b/account_check_printing_report_base/models/account_payment.py @@ -1,5 +1,5 @@ -# Copyright 2016 Eficent Business and IT Consulting Services S.L. -# (http://www.eficent.com) +# Copyright 2016 ForgeFLow, S.L. +# (http://www.forgeflow.com) # Copyright 2016 Serpent Consulting Services Pvt. Ltd. # Copyright 2018 iterativo. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). diff --git a/account_check_printing_report_base/models/res_company.py b/account_check_printing_report_base/models/res_company.py index 658e144be1c..18c2bb2777c 100644 --- a/account_check_printing_report_base/models/res_company.py +++ b/account_check_printing_report_base/models/res_company.py @@ -1,5 +1,5 @@ -# Copyright 2016 Eficent Business and IT Consulting Services S.L. -# (http://www.eficent.com) +# Copyright 2016 ForgeFlow, S.L. +# (http://www.forgeflow.com) # Copyright 2016 Serpent Consulting Services Pvt. Ltd. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). diff --git a/account_check_printing_report_base/readme/CONTRIBUTORS.md b/account_check_printing_report_base/readme/CONTRIBUTORS.md index 0db76faa65d..c1c1b25a444 100644 --- a/account_check_printing_report_base/readme/CONTRIBUTORS.md +++ b/account_check_printing_report_base/readme/CONTRIBUTORS.md @@ -1,6 +1,6 @@ -- Jordi Ballester Alomar \<\> +- Jordi Ballester Alomar \<\> -- Lois Rilo Antelo \<\> +- Lois Rilo Antelo \<\> - Sandip Mangukiya \<\> diff --git a/account_check_printing_report_base/report/check_print.py b/account_check_printing_report_base/report/check_print.py index fd188e99e2c..0957974cf23 100644 --- a/account_check_printing_report_base/report/check_print.py +++ b/account_check_printing_report_base/report/check_print.py @@ -1,5 +1,5 @@ -# Copyright 2016 Eficent Business and IT Consulting Services S.L. -# (http://www.eficent.com) +# Copyright 2016 ForgeFlow, S.L. +# (http://www.forgeflow.com) # Copyright 2016 Serpent Consulting Services Pvt. Ltd. # Copyright 2018 iterativo. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). diff --git a/account_check_printing_report_base/static/description/index.html b/account_check_printing_report_base/static/description/index.html index bfd8e4dc69e..339914f7f2b 100644 --- a/account_check_printing_report_base/static/description/index.html +++ b/account_check_printing_report_base/static/description/index.html @@ -3,7 +3,7 @@ -Account Check Printing Report Base +README.rst -
-

Account Check Printing Report Base

+
+ + +Odoo Community Association + +
+

Account Check Printing Report Base

-

Beta License: AGPL-3 OCA/account-payment Translate me on Weblate Try me on Runboat

+

Beta License: AGPL-3 OCA/account-payment Translate me on Weblate Try me on Runboat

This module provides the basic framework for check printing, and a sample layout.

Table of contents

@@ -388,7 +393,7 @@

Account Check Printing Report Base

-

Configuration

+

Configuration

Add your user to ‘Show Full Accounting Features’ group.

Go to ‘Invoicing / Configuration / Settings’ and assign the desired check format on ‘Check Layout’ field. This module proposes a basic @@ -396,7 +401,7 @@

Configuration

provide formats adjusted to known check formats such as DLT103.

-

Usage

+

Usage

  • Go to ‘Invoicing / Vendors / Payments’. Select one of the payments with type ‘Check’ and print the check.
  • @@ -405,14 +410,14 @@

    Usage

-

Known issues / Roadmap

+

Known issues / Roadmap

  • When print check automatically in the payment validation process, the wizard remains opened.
-

Bug Tracker

+

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 @@ -420,21 +425,21 @@

Bug Tracker

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

-

Credits

+

Credits

-

Authors

+

Authors

    -
  • Eficent
  • +
  • ForgeFlow
  • Serpent Consulting Services Pvt. Ltd.
  • Ursa Information Systems
-

Contributors

+

Contributors

-

Maintainers

+

Maintainers

This module is maintained by the OCA.

Odoo Community Association @@ -464,5 +469,6 @@

Maintainers

+
diff --git a/account_check_printing_report_base/tests/test_check_printing_report.py b/account_check_printing_report_base/tests/test_check_printing_report.py index 8da4f526fa7..026153f33d4 100644 --- a/account_check_printing_report_base/tests/test_check_printing_report.py +++ b/account_check_printing_report_base/tests/test_check_printing_report.py @@ -1,4 +1,4 @@ -# © 2016 Eficent Business and IT Consulting Services S.L. +# Copyright 2016 ForgeFlow, S.L. # Copyright 2018 iterativo. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). diff --git a/account_force_early_discount/README.rst b/account_force_early_discount/README.rst new file mode 100644 index 00000000000..720a45ea30a --- /dev/null +++ b/account_force_early_discount/README.rst @@ -0,0 +1,96 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +==================================== +Account force early payment discount +==================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:c5aec43cf0209dbf5168e55c885f1ff5246b85b7c203b749849819dea88ca850 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |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%2Faccount--payment-lightgray.png?logo=github + :target: https://github.com/OCA/account-payment/tree/18.0/account_force_early_discount + :alt: OCA/account-payment +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-payment-18-0/account-payment-18-0-account_force_early_discount + :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/account-payment&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows to force early payment discounts past the validity +date of the discount set through the payment term. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +The early payment discount will be applied automatically, even past its +validity date, using register payment wizard if the "Force financial +discount" is used on the invoice. + +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 +------- + +* Camptocamp + +Contributors +------------ + +- Akim Juillerat + +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. + +.. |maintainer-grindtildeath| image:: https://github.com/grindtildeath.png?size=40px + :target: https://github.com/grindtildeath + :alt: grindtildeath + +Current `maintainer `__: + +|maintainer-grindtildeath| + +This module is part of the `OCA/account-payment `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_force_early_discount/__init__.py b/account_force_early_discount/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/account_force_early_discount/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/account_force_early_discount/__manifest__.py b/account_force_early_discount/__manifest__.py new file mode 100644 index 00000000000..428ad4fa9c6 --- /dev/null +++ b/account_force_early_discount/__manifest__.py @@ -0,0 +1,19 @@ +# Copyright 2019 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Account force early payment discount", + "summary": "Allow forcing financial discounts for early payments", + "version": "18.0.1.0.0", + "development_status": "Production/Stable", + "category": "Account", + "website": "https://github.com/OCA/account-payment", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["grindtildeath"], + "license": "AGPL-3", + "installable": True, + "depends": ["account"], + "data": [ + "views/account_move.xml", + "wizard/account_payment_register.xml", + ], +} diff --git a/account_force_early_discount/i18n/account_force_early_discount.pot b/account_force_early_discount/i18n/account_force_early_discount.pot new file mode 100644 index 00000000000..a5a8f3483e9 --- /dev/null +++ b/account_force_early_discount/i18n/account_force_early_discount.pot @@ -0,0 +1,66 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_force_early_discount +# +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: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_payment_register__force_early_discount +msgid "Apply Early Payment Discount Past Date" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_bank_statement_line__display_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_move__display_force_early_discount +msgid "Display Force Early Discount" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_bank_statement_line__force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_move__force_early_discount +msgid "Force early payment discount" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model.fields,help:account_force_early_discount.field_account_payment_register__force_early_discount +msgid "" +"Force early payment discount even if the date is past and the flag is not set on the invoices.\n" +"Note that early payment discounts will be applied for invoices havingthe flag set, even if this checkbox is not marked." +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model.fields,help:account_force_early_discount.field_account_bank_statement_line__force_early_discount +#: model:ir.model.fields,help:account_force_early_discount.field_account_move__force_early_discount +msgid "" +"If marked, early payment discount will be applied even if the discount date " +"is passed" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model,name:account_force_early_discount.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model,name:account_force_early_discount.model_account_payment_register +msgid "Pay" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_payment_register__show_force_early_discount +msgid "Show Force Early Discount" +msgstr "" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_payment_register__with_forced_early_discount +msgid "With Forced Early Discount" +msgstr "" diff --git a/account_force_early_discount/i18n/it.po b/account_force_early_discount/i18n/it.po new file mode 100644 index 00000000000..9be3b886306 --- /dev/null +++ b/account_force_early_discount/i18n/it.po @@ -0,0 +1,76 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_force_early_discount +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-12-18 07:48+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: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_payment_register__force_early_discount +msgid "Apply Early Payment Discount Past Date" +msgstr "Applica lo sconto per pagamento anticipato dopo la data" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_bank_statement_line__display_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_move__display_force_early_discount +msgid "Display Force Early Discount" +msgstr "Visualizza forzatura sconto anticipato" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_bank_statement_line__force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_move__force_early_discount +msgid "Force early payment discount" +msgstr "Forza sconto pagamento anticipato" + +#. module: account_force_early_discount +#: model:ir.model.fields,help:account_force_early_discount.field_account_payment_register__force_early_discount +msgid "" +"Force early payment discount even if the date is past and the flag is not set on the invoices.\n" +"Note that early payment discounts will be applied for invoices havingthe flag set, even if this checkbox is not marked." +msgstr "" +"Forza lo sconto per pagamento anticipato anche se la data è passata e il " +"flag non è impostato sulle fatture.\n" +"Si noti che gli sconti per pagamento anticipato verranno applicati alle " +"fatture con il flag impostato, anche se questa casella di controllo non è " +"selezionata." + +#. module: account_force_early_discount +#: model:ir.model.fields,help:account_force_early_discount.field_account_bank_statement_line__force_early_discount +#: model:ir.model.fields,help:account_force_early_discount.field_account_move__force_early_discount +msgid "" +"If marked, early payment discount will be applied even if the discount date " +"is passed" +msgstr "" +"Se contrassegnato, lo sconto per pagamento anticipato verrà applicato anche " +"se la data dello sconto è trascorsa" + +#. module: account_force_early_discount +#: model:ir.model,name:account_force_early_discount.model_account_move +msgid "Journal Entry" +msgstr "Registrazione contabile" + +#. module: account_force_early_discount +#: model:ir.model,name:account_force_early_discount.model_account_payment_register +msgid "Pay" +msgstr "Paga" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_payment_register__show_force_early_discount +msgid "Show Force Early Discount" +msgstr "Visualizza forza sconto anticipato" + +#. module: account_force_early_discount +#: model:ir.model.fields,field_description:account_force_early_discount.field_account_payment_register__with_forced_early_discount +msgid "With Forced Early Discount" +msgstr "Con sconto anticipato forzato" diff --git a/account_force_early_discount/models/__init__.py b/account_force_early_discount/models/__init__.py new file mode 100644 index 00000000000..9c0a4213854 --- /dev/null +++ b/account_force_early_discount/models/__init__.py @@ -0,0 +1 @@ +from . import account_move diff --git a/account_force_early_discount/models/account_move.py b/account_force_early_discount/models/account_move.py new file mode 100644 index 00000000000..402e9596b34 --- /dev/null +++ b/account_force_early_discount/models/account_move.py @@ -0,0 +1,70 @@ +# Copyright 2019-2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class AccountMove(models.Model): + _inherit = "account.move" + + display_force_early_discount = fields.Boolean( + compute="_compute_display_force_early_discount" + ) + force_early_discount = fields.Boolean( + "Force early payment discount", + default=False, + help="If marked, early payment discount will be applied even if the " + "discount date is passed", + ) + + @api.depends( + "invoice_payment_term_id.discount_days", + "invoice_payment_term_id.discount_percentage", + "invoice_payment_term_id.early_discount", + "line_ids.discount_amount_currency", + "line_ids.discount_date", + "state", + ) + def _compute_display_force_early_discount(self): + """Compute discount financial discount fields""" + for rec in self: + display_force = False + if rec.state == "draft": + if ( + rec.invoice_payment_term_id.early_discount + and rec.invoice_payment_term_id.discount_days + and rec.invoice_payment_term_id.discount_percentage + ): + display_force = True + elif rec.state == "posted": + first_payment_term_line = rec._get_first_payment_term_line() + if ( + first_payment_term_line.discount_date + and first_payment_term_line.discount_amount_currency + ): + display_force = True + rec.display_force_early_discount = display_force + + def _get_first_payment_term_line(self): + self.ensure_one() + payment_term_lines = self.line_ids.filtered( + lambda line: line.display_type == "payment_term" + ) + return payment_term_lines.sorted("date_maturity")[:1] + + def _is_eligible_for_early_payment_discount(self, currency, reference_date): + force_move_ids = self.env.context.get("_force_early_discount_move_ids", []) + if self.force_early_discount or self.id in force_move_ids: + payment_terms = self.line_ids.filtered( + lambda line: line.display_type == "payment_term" + ) + return ( + self.currency_id == currency + and self.move_type in self._early_payment_discount_move_types() + and self.invoice_payment_term_id.early_discount + and not ( + payment_terms.sudo().matched_debit_ids + + payment_terms.sudo().matched_credit_ids + ) + ) + return super()._is_eligible_for_early_payment_discount(currency, reference_date) diff --git a/account_force_early_discount/pyproject.toml b/account_force_early_discount/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/account_force_early_discount/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/account_force_early_discount/readme/CONTRIBUTORS.md b/account_force_early_discount/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..e91f48d0a25 --- /dev/null +++ b/account_force_early_discount/readme/CONTRIBUTORS.md @@ -0,0 +1 @@ +- Akim Juillerat \<\> diff --git a/account_force_early_discount/readme/DESCRIPTION.md b/account_force_early_discount/readme/DESCRIPTION.md new file mode 100644 index 00000000000..3bd03b7f5ec --- /dev/null +++ b/account_force_early_discount/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module allows to force early payment discounts past the validity +date of the discount set through the payment term. diff --git a/account_force_early_discount/readme/USAGE.md b/account_force_early_discount/readme/USAGE.md new file mode 100644 index 00000000000..82c81d7328d --- /dev/null +++ b/account_force_early_discount/readme/USAGE.md @@ -0,0 +1,3 @@ +The early payment discount will be applied automatically, even past +its validity date, using register payment wizard if the "Force financial discount" +is used on the invoice. diff --git a/account_force_early_discount/static/description/icon.png b/account_force_early_discount/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/account_force_early_discount/static/description/icon.png differ diff --git a/account_force_early_discount/static/description/index.html b/account_force_early_discount/static/description/index.html new file mode 100644 index 00000000000..0fea91dedac --- /dev/null +++ b/account_force_early_discount/static/description/index.html @@ -0,0 +1,439 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Account force early payment discount

+ +

Production/Stable License: AGPL-3 OCA/account-payment Translate me on Weblate Try me on Runboat

+

This module allows to force early payment discounts past the validity +date of the discount set through the payment term.

+

Table of contents

+ +
+

Usage

+

The early payment discount will be applied automatically, even past its +validity date, using register payment wizard if the “Force financial +discount” is used on the invoice.

+
+
+

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

+
    +
  • Camptocamp
  • +
+
+ +
+

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.

+

Current maintainer:

+

grindtildeath

+

This module is part of the OCA/account-payment project on GitHub.

+

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

+
+
+
+
+ + diff --git a/account_force_early_discount/tests/__init__.py b/account_force_early_discount/tests/__init__.py new file mode 100644 index 00000000000..5ef2703389e --- /dev/null +++ b/account_force_early_discount/tests/__init__.py @@ -0,0 +1 @@ +from . import test_financial_discount_manual_payment diff --git a/account_force_early_discount/tests/common.py b/account_force_early_discount/tests/common.py new file mode 100644 index 00000000000..09bb590af42 --- /dev/null +++ b/account_force_early_discount/tests/common.py @@ -0,0 +1,97 @@ +# Copyright 2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo.tests import Form, TransactionCase + +from odoo.addons.base.tests.common import DISABLED_MAIL_CONTEXT + + +class TestAccountFinancialDiscountCommon(TransactionCase): + @classmethod + def init_invoice( + cls, + partner, + move_type, + payment_term=None, + invoice_date=None, + currency=None, + payment_reference=None, + ): + move_form = Form( + cls.env["account.move"].with_context(default_move_type=move_type) + ) + move_form.partner_id = partner + move_form.invoice_payment_term_id = payment_term + move_form.invoice_date = invoice_date + if currency is not None: + move_form.currency_id = currency + if payment_reference is not None: + move_form.payment_reference = payment_reference + return move_form.save() + + @classmethod + def init_invoice_line(cls, invoice, quantity, unit_price, product=None, tax=None): + with Form(invoice) as move_form: + with move_form.invoice_line_ids.new() as line_form: + if product: + line_form.product_id = product + line_form.name = product and product.name or "test" + line_form.quantity = quantity + line_form.price_unit = unit_price + if tax: + line_form.tax_ids = tax + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env(context=dict(cls.env.context, **DISABLED_MAIL_CONTEXT)) + cls.usd_currency = cls.env.ref("base.USD") + cls.eur_currency = cls.env.ref("base.EUR") + cls.eur_currency.active = True + cls.chf_currency = cls.env.ref("base.CHF") + cls.chf_currency.active = True + + cls.partner = cls.env["res.partner"].create( + {"name": "Peter Muster AG", "supplier_rank": 1} + ) + cls.customer = cls.env["res.partner"].create( + {"name": "Hans Muster GmbH & Co. KG", "customer_rank": 1} + ) + + cls.payment_term = cls.env["account.payment.term"].create( + { + "name": "Skonto", + "early_discount": True, + "discount_days": 10, + "discount_percentage": 2.0, + "line_ids": [ + ( + 0, + 0, + { + "value_amount": 100, + "value": "percent", + "nb_days": 60, + "delay_type": "days_after", + }, + ) + ], + } + ) + + cls.sale_tax = cls.env.company.account_sale_tax_id + cls.purchase_tax = cls.env.company.account_purchase_tax_id + + cls.bank_journal = cls.env["account.journal"].search( + [("company_id", "=", cls.env.company.id), ("type", "=", "bank")], + limit=1, + ) + cls.eur_bank_journal = cls.env["account.journal"].create( + { + "name": "Bank EUR", + "type": "bank", + "code": "BNK2", + "currency_id": cls.eur_currency.id, + } + ) + + cls.payment_thirty_net = cls.env.ref("account.account_payment_term_30days") diff --git a/account_force_early_discount/tests/test_financial_discount_manual_payment.py b/account_force_early_discount/tests/test_financial_discount_manual_payment.py new file mode 100644 index 00000000000..8484b31d4a5 --- /dev/null +++ b/account_force_early_discount/tests/test_financial_discount_manual_payment.py @@ -0,0 +1,389 @@ +# Copyright 2019-2020 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from freezegun import freeze_time + +from odoo.tests import Form + +from .common import TestAccountFinancialDiscountCommon + + +@freeze_time("2019-04-01") +class TestAccountFinancialDiscountManualPayment(TestAccountFinancialDiscountCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.invoice1 = cls.init_invoice( + cls.partner, + "in_invoice", + payment_term=cls.payment_term, + invoice_date="2019-04-01", + ) + cls.init_invoice_line(cls.invoice1, 1.0, 1000.0, tax=cls.purchase_tax) + + cls.invoice2 = cls.init_invoice( + cls.partner, + "in_invoice", + payment_term=cls.payment_term, + invoice_date="2019-02-15", + ) + cls.init_invoice_line(cls.invoice2, 1.0, 1000.0, tax=cls.purchase_tax) + + cls.invoice3 = cls.init_invoice( + cls.partner, + "in_invoice", + payment_term=cls.payment_thirty_net, + invoice_date="2019-04-01", + ) + cls.init_invoice_line(cls.invoice3, 1.0, 1000.0, tax=cls.purchase_tax) + + cls.client_invoice1 = cls.init_invoice( + cls.customer, + "out_invoice", + payment_term=cls.payment_term, + invoice_date="2019-04-01", + ) + cls.init_invoice_line(cls.client_invoice1, 1.0, 1000.0, tax=cls.sale_tax) + + cls.client_invoice2 = cls.init_invoice( + cls.customer, + "out_invoice", + payment_term=cls.payment_term, + invoice_date="2019-02-15", + ) + cls.init_invoice_line(cls.client_invoice2, 1.0, 1000.0, tax=cls.sale_tax) + + cls.client_invoice3 = cls.init_invoice( + cls.customer, + "out_invoice", + payment_term=cls.payment_thirty_net, + invoice_date="2019-04-01", + ) + cls.init_invoice_line(cls.client_invoice3, 1.0, 1000.0, tax=cls.sale_tax) + + cls.amount_without_discount = 1150.0 + cls.amount_discount = 23.0 + cls.amount_with_discount = 1127.0 + + def _assert_payment_line_with_discount_from_invoice(self, invoice): + invoice_payment_line = self._get_payment_lines(invoice) + # The payment move line must have full amount to set invoice as paid + self.assertEqual( + invoice_payment_line.amount_currency, self.amount_without_discount + ) + invoice_payment = invoice_payment_line.mapped("payment_id") + # # The payment must have the amount with discount + self.assertEqual(invoice_payment.amount, self.amount_with_discount) + + def test_single_invoice_payment_with_discount_late(self): + """Test register payment for a vendor bill with late discount""" + self.invoice2.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_model="account.move", + active_ids=self.invoice2.ids, + active_id=self.invoice2.id, + ) + ) + self.assertEqual(payment_wizard_form.show_force_early_discount, True) + self.assertEqual(payment_wizard_form.force_early_discount, False) + self.assertEqual(payment_wizard_form.amount, self.amount_without_discount) + payment_wizard_form.force_early_discount = True + self.assertEqual(payment_wizard_form.amount, self.amount_with_discount) + self.assertEqual(payment_wizard_form.payment_difference_handling, "reconcile") + self.assertEqual(payment_wizard_form.payment_difference, self.amount_discount) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self.assertIn(self.invoice2.payment_state, ("paid", "in_payment")) + + def test_single_invoice_payment_with_discount_late_forced(self): + """Test register payment for a vendor bill with late discount forced""" + self.invoice2.action_post() + self.invoice2.force_early_discount = True + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_model="account.move", + active_ids=self.invoice2.ids, + active_id=self.invoice2.id, + ) + ) + self.assertEqual(payment_wizard_form.show_force_early_discount, True) + self.assertEqual(payment_wizard_form.amount, self.amount_with_discount) + self.assertEqual(payment_wizard_form.payment_difference_handling, "reconcile") + self.assertEqual(payment_wizard_form.payment_difference, self.amount_discount) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self.assertIn(self.invoice2.payment_state, ("paid", "in_payment")) + + def test_single_invoice_payment_without_discount(self): + """Test register payment for a vendor bill without discount""" + self.invoice3.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_model="account.move", + active_ids=self.invoice3.ids, + active_id=self.invoice3.id, + ) + ) + self.assertEqual(payment_wizard_form.show_force_early_discount, False) + self.assertEqual(payment_wizard_form.force_early_discount, False) + self.assertEqual(payment_wizard_form.amount, self.amount_without_discount) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self.assertIn(self.invoice3.payment_state, ("paid", "in_payment")) + + @classmethod + def _get_payment_lines(cls, invoice): + """Returns payment lines match with the invoice""" + # Inspired by account.move._get_reconciled_info_JSON_values + invoice_term_lines = invoice.line_ids.filtered( + lambda line: line.account_type in ("asset_receivable", "liability_payable") + ) + invoice_matched_lines = invoice_term_lines.mapped( + "matched_debit_ids" + ) | invoice_term_lines.mapped("matched_credit_ids") + invoice_counterpart_lines = invoice_matched_lines.mapped( + "debit_move_id" + ) | invoice_matched_lines.mapped("debit_move_id") + return invoice_counterpart_lines.filtered( + lambda line: line not in invoice.line_ids + ) + + def test_multi_invoice_payment_with_discount_late(self): + """Test register payment for multiple vendor bills with late discount""" + invoice4 = self.invoice2.copy({"invoice_date": self.invoice2.invoice_date}) + invoices = self.invoice2 | invoice4 + invoices.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_ids=invoices.ids, active_model="account.move" + ) + ) + self.assertTrue(payment_wizard_form.show_force_early_discount) + self.assertFalse(payment_wizard_form.force_early_discount) + self.assertEqual(payment_wizard_form.journal_id, self.bank_journal) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + invoice2_payment_line = self._get_payment_lines(self.invoice2) + invoice2_payment = invoice2_payment_line.mapped("payment_id") + self.assertEqual(invoice2_payment.amount, self.amount_without_discount) + self.assertIn(self.invoice2.payment_state, ("paid", "in_payment")) + invoice4_payment_lines = self._get_payment_lines(invoice4) + invoice4_payment = invoice4_payment_lines.mapped("payment_id") + self.assertEqual(invoice4_payment.amount, self.amount_without_discount) + self.assertIn(invoice4.payment_state, ("paid", "in_payment")) + + def test_multi_invoice_payment_with_discount_late_forced(self): + """Test register payment for multiple vendor bills with late discount forced + at invoice level""" + invoice4 = self.invoice2.copy({"invoice_date": self.invoice2.invoice_date}) + invoice4.force_early_discount = True + invoices = self.invoice2 | invoice4 + invoices.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_ids=invoices.ids, active_model="account.move" + ) + ) + self.assertTrue(payment_wizard_form.show_force_early_discount) + self.assertFalse(payment_wizard_form.force_early_discount) + self.assertEqual(payment_wizard_form.journal_id, self.bank_journal) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + invoice2_payment_lines = self._get_payment_lines(self.invoice2) + invoice2_payment = invoice2_payment_lines.mapped("payment_id") + self.assertEqual(invoice2_payment.amount, self.amount_without_discount) + self.assertIn(self.invoice2.payment_state, ("paid", "in_payment")) + self._assert_payment_line_with_discount_from_invoice(invoice4) + self.assertIn(invoice4.payment_state, ("paid", "in_payment")) + + def test_multi_invoice_payment_with_discount_late_forced_wizard(self): + """Test register payment grouped for multiple vendor bills with late discount + forced at wizard level""" + invoice4 = self.invoice2.copy({"invoice_date": self.invoice2.invoice_date}) + invoice4.force_early_discount = True + invoices = self.invoice2 | invoice4 + invoices.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_ids=invoices.ids, active_model="account.move" + ) + ) + self.assertTrue(payment_wizard_form.show_force_early_discount) + self.assertFalse(payment_wizard_form.force_early_discount) + payment_wizard_form.force_early_discount = True + self.assertEqual(payment_wizard_form.journal_id, self.bank_journal) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self._assert_payment_line_with_discount_from_invoice(self.invoice2) + self.assertIn(self.invoice2.payment_state, ("paid", "in_payment")) + self._assert_payment_line_with_discount_from_invoice(invoice4) + self.assertIn(invoice4.payment_state, ("paid", "in_payment")) + + def test_multi_invoice_eur_payment_eur_with_discount_late(self): + """Test register payment for multiple vendor bills with force discount""" + invoice1 = self.init_invoice( + self.partner, + "in_invoice", + payment_term=self.payment_term, + invoice_date="2019-04-01", + currency=self.eur_currency, + ) + self.init_invoice_line(invoice1, 1.0, 1000.0, tax=self.purchase_tax) + invoice2 = self.init_invoice( + self.partner, + "in_invoice", + payment_term=self.payment_term, + invoice_date="2019-02-15", + currency=self.eur_currency, + ) + self.init_invoice_line(invoice2, 1.0, 1000.0, tax=self.purchase_tax) + invoices = invoice1 | invoice2 + invoices.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_ids=invoices.ids, active_model="account.move" + ) + ) + self.assertTrue(payment_wizard_form.show_force_early_discount) + self.assertFalse(payment_wizard_form.force_early_discount) + payment_wizard_form.journal_id = self.eur_bank_journal + self.assertFalse(payment_wizard_form.group_payment) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self._assert_payment_line_with_discount_from_invoice(invoice1) + self.assertIn(invoice1.payment_state, ("paid", "in_payment")) + invoice2_payment_line = self._get_payment_lines(invoice2) + invoice2_payment = invoice2_payment_line.mapped("payment_id") + self.assertEqual(invoice2_payment.amount, self.amount_without_discount) + self.assertIn(invoice2.payment_state, ("paid", "in_payment")) + + def test_multi_invoice_eur_payment_eur_with_discount_late_forced(self): + """Test register payment for multiple vendor bills with discount""" + invoice1 = self.init_invoice( + self.partner, + "in_invoice", + payment_term=self.payment_term, + invoice_date="2019-04-01", + currency=self.eur_currency, + ) + self.init_invoice_line(invoice1, 1.0, 1000.0, tax=self.purchase_tax) + invoice2 = self.init_invoice( + self.partner, + "in_invoice", + payment_term=self.payment_term, + invoice_date="2019-02-15", + currency=self.eur_currency, + ) + self.init_invoice_line(invoice2, 1.0, 1000.0, tax=self.purchase_tax) + invoice2.force_early_discount = True + invoices = invoice1 | invoice2 + invoices.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_ids=invoices.ids, active_model="account.move" + ) + ) + self.assertTrue(payment_wizard_form.show_force_early_discount) + self.assertFalse(payment_wizard_form.force_early_discount) + payment_wizard_form.journal_id = self.eur_bank_journal + self.assertFalse(payment_wizard_form.group_payment) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self._assert_payment_line_with_discount_from_invoice(invoice1) + self.assertIn(invoice1.payment_state, ("paid", "in_payment")) + self._assert_payment_line_with_discount_from_invoice(invoice2) + self.assertIn(invoice2.payment_state, ("paid", "in_payment")) + + def test_multi_invoice_eur_payment_eur_with_discount_late_forced_wizard(self): + """Test register payment for multiple vendor bills with discount""" + invoice1 = self.init_invoice( + self.partner, + "in_invoice", + payment_term=self.payment_term, + invoice_date="2019-04-01", + currency=self.eur_currency, + ) + self.init_invoice_line(invoice1, 1.0, 1000.0, tax=self.purchase_tax) + invoice2 = self.init_invoice( + self.partner, + "in_invoice", + payment_term=self.payment_term, + invoice_date="2019-02-15", + currency=self.eur_currency, + ) + self.init_invoice_line(invoice2, 1.0, 1000.0, tax=self.purchase_tax) + invoices = invoice1 | invoice2 + invoices.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_ids=invoices.ids, active_model="account.move" + ) + ) + self.assertTrue(payment_wizard_form.show_force_early_discount) + self.assertFalse(payment_wizard_form.force_early_discount) + payment_wizard_form.force_early_discount = True + payment_wizard_form.journal_id = self.eur_bank_journal + self.assertFalse(payment_wizard_form.group_payment) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self._assert_payment_line_with_discount_from_invoice(invoice1) + self.assertIn(invoice1.payment_state, ("paid", "in_payment")) + self._assert_payment_line_with_discount_from_invoice(invoice2) + self.assertIn(invoice2.payment_state, ("paid", "in_payment")) + + def test_customer_manual_payment_with_discount_late(self): + """Test register payment for a customer invoice with late discount""" + self.client_invoice2.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_model="account.move", + active_ids=self.client_invoice2.ids, + active_id=self.client_invoice2.id, + ) + ) + self.assertEqual(payment_wizard_form.show_force_early_discount, True) + self.assertEqual(payment_wizard_form.force_early_discount, False) + self.assertEqual(payment_wizard_form.amount, self.amount_without_discount) + payment_wizard_form.force_early_discount = True + self.assertEqual(payment_wizard_form.amount, self.amount_with_discount) + self.assertEqual(payment_wizard_form.payment_difference_handling, "reconcile") + self.assertEqual(payment_wizard_form.payment_difference, self.amount_discount) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self.assertIn(self.client_invoice2.payment_state, ("paid", "in_payment")) + + def test_customer_manual_payment_with_discount_late_forced(self): + """Test register payment for a customer invoice with late discount forced""" + self.client_invoice2.action_post() + self.client_invoice2.force_early_discount = True + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_model="account.move", + active_ids=self.client_invoice2.ids, + active_id=self.client_invoice2.id, + ) + ) + self.assertEqual(payment_wizard_form.show_force_early_discount, True) + self.assertEqual(payment_wizard_form.amount, self.amount_with_discount) + self.assertEqual(payment_wizard_form.payment_difference_handling, "reconcile") + self.assertEqual(payment_wizard_form.payment_difference, self.amount_discount) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self.assertIn(self.client_invoice2.payment_state, ("paid", "in_payment")) + + def test_customer_manual_payment_without_discount(self): + """Test register payment for a customer invoice without discount""" + self.client_invoice3.action_post() + payment_wizard_form = Form( + self.env["account.payment.register"].with_context( + active_model="account.move", + active_ids=self.client_invoice3.ids, + active_id=self.client_invoice3.id, + ) + ) + self.assertEqual(payment_wizard_form.show_force_early_discount, False) + self.assertEqual(payment_wizard_form.force_early_discount, False) + self.assertEqual(payment_wizard_form.amount, self.amount_without_discount) + payment_wizard = payment_wizard_form.save() + payment_wizard.action_create_payments() + self.assertIn(self.client_invoice3.payment_state, ("paid", "in_payment")) diff --git a/account_force_early_discount/views/account_move.xml b/account_force_early_discount/views/account_move.xml new file mode 100644 index 00000000000..32078f211cd --- /dev/null +++ b/account_force_early_discount/views/account_move.xml @@ -0,0 +1,19 @@ + + + + account.move.form.inherit + account.move + + + + + + + + diff --git a/account_force_early_discount/wizard/__init__.py b/account_force_early_discount/wizard/__init__.py new file mode 100644 index 00000000000..019698a882d --- /dev/null +++ b/account_force_early_discount/wizard/__init__.py @@ -0,0 +1 @@ +from . import account_payment_register diff --git a/account_force_early_discount/wizard/account_payment_register.py b/account_force_early_discount/wizard/account_payment_register.py new file mode 100644 index 00000000000..0fbdb18dab2 --- /dev/null +++ b/account_force_early_discount/wizard/account_payment_register.py @@ -0,0 +1,110 @@ +# Copyright 2019-2021 Camptocamp SA +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) +from odoo import api, fields, models + + +class AccountPaymentRegister(models.TransientModel): + _inherit = "account.payment.register" + + force_early_discount = fields.Boolean( + string="Apply Early Payment Discount Past Date", + help="Force early payment discount even if the date is past and the flag is" + " not set on the invoices.\n" + "Note that early payment discounts will be applied for invoices having" + "the flag set, even if this checkbox is not marked.", + ) + show_force_early_discount = fields.Boolean( + compute="_compute_show_force_early_discount" + ) + with_forced_early_discount = fields.Boolean( + compute="_compute_with_forced_early_discount" + ) + + @api.depends("force_early_discount", "line_ids", "payment_date") + def _compute_with_forced_early_discount(self): + """Check if an early payment discount is forced on the wizard + + An early payment discount is forced if: + - Early payment discount is available on any invoice and invoice is marked + with force early discount. + - Early payment discount is available on any invoice and wizard is marked + with force early discount.""" + for wizard in self: + if wizard.force_early_discount: + wizard.with_forced_early_discount = True + continue + lines_with_forced_discount_past_date = wizard.line_ids.filtered( + lambda li: li.discount_amount_currency + and li.discount_date < wizard.payment_date # noqa: B023 + and li.move_id.force_early_discount + ) + wizard.with_forced_early_discount = bool( + lines_with_forced_discount_past_date + ) + + @api.depends("line_ids") + def _compute_show_force_early_discount(self): + for wizard in self: + # If at least one invoice has late discounts on its move lines + wizard.show_force_early_discount = wizard.line_ids.filtered( + lambda li: li.discount_amount_currency + and li.discount_date < wizard.payment_date # noqa: B023 + ) + + @api.depends("with_forced_early_discount", "force_early_discount") + def _compute_from_lines(self): + return super()._compute_from_lines() + + @api.depends( + "with_forced_early_discount", + ) + def _compute_amount(self): + return super()._compute_amount() + + def _apply_force_early_payment_discount_ctx(self, batch_result): + if self.force_early_discount: + batch_result["lines"] = batch_result["lines"].with_context( + _force_early_discount_move_ids=batch_result["lines"].move_id.ids + ) + else: + force_discount_lines = batch_result["lines"].filtered( + lambda li: li.move_id.force_early_discount + ) + if force_discount_lines: + batch_result["lines"] = batch_result["lines"].with_context( + _force_early_discount_move_ids=force_discount_lines.move_id.ids + ) + return batch_result + + def _create_payment_vals_from_wizard(self, batch_result): + # Set ctx to be used on account.move._is_eligible_for_early_payment_discount + batch_result = self._apply_force_early_payment_discount_ctx(batch_result) + return super()._create_payment_vals_from_wizard(batch_result) + + def _create_payment_vals_from_batch(self, batch_result): + # Set ctx to be used on account.move._is_eligible_for_early_payment_discount + batch_result = self._apply_force_early_payment_discount_ctx(batch_result) + return super()._create_payment_vals_from_batch(batch_result) + + def _get_total_amounts_to_pay(self, batch_results): + # Set ctx to be used on account.move._is_eligible_for_early_payment_discount + if self.with_forced_early_discount: + force_ids = [] + for batch_result in batch_results: + if self.force_early_discount: + batch_result["lines"] = batch_result["lines"].with_context( + _force_early_discount_move_ids=batch_result["lines"].move_id.ids + ) + force_ids += batch_result["lines"].move_id.ids + else: + force_discount_lines = batch_result["lines"].filtered( + lambda li: li.move_id.force_early_discount + ) + if force_discount_lines: + batch_result["lines"] = batch_result["lines"].with_context( + _force_early_discount_move_ids=force_discount_lines.move_id.ids + ) + force_ids += force_discount_lines.move_id.ids + if force_ids: + self = self.with_context(_force_early_discount_move_ids=force_ids) + return super()._get_total_amounts_to_pay(batch_results) diff --git a/account_force_early_discount/wizard/account_payment_register.xml b/account_force_early_discount/wizard/account_payment_register.xml new file mode 100644 index 00000000000..f9ff6fdad6a --- /dev/null +++ b/account_force_early_discount/wizard/account_payment_register.xml @@ -0,0 +1,16 @@ + + + + account.payment.register.form.inherit + account.payment.register + + + + + + + + diff --git a/account_payment_line/README.rst b/account_payment_line/README.rst new file mode 100644 index 00000000000..f97a51becd6 --- /dev/null +++ b/account_payment_line/README.rst @@ -0,0 +1,103 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +========================= +Payment Counterpart Lines +========================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3b23dbbc9137cb5bd26e40c5e669bc7a942f4f16ada17998de98eb44e114d8b1 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Faccount--payment-lightgray.png?logo=github + :target: https://github.com/OCA/account-payment/tree/18.0/account_payment_line + :alt: OCA/account-payment +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-payment-18-0/account-payment-18-0-account_payment_line + :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/account-payment&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module is an utility module to add lines in payment, allowing users +make more complicated cases when processing payments, split on many +invoices, set up specific write-off and adding some analytic information + +Add tool to proposal of payment distributions, ordering by due date + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +You can use payment distribution suggestion, and if system found moves +pending to reconcile related with partner selected, system will create +all lines trying to pay all invoices until amount remain + +You can add manually lines, if payment don't detect lines specified, +payment works as a normal payment + +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 +------- + +* ForgeFlow S.L. + +Contributors +------------ + +- Christopher Ormaza. +- Do Anh Duy + +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. + +.. |maintainer-ChrisOForgeFlow| image:: https://github.com/ChrisOForgeFlow.png?size=40px + :target: https://github.com/ChrisOForgeFlow + :alt: ChrisOForgeFlow + +Current `maintainer `__: + +|maintainer-ChrisOForgeFlow| + +This module is part of the `OCA/account-payment `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_payment_line/__init__.py b/account_payment_line/__init__.py new file mode 100644 index 00000000000..0ccfa881b02 --- /dev/null +++ b/account_payment_line/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2022 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from . import models diff --git a/account_payment_line/__manifest__.py b/account_payment_line/__manifest__.py new file mode 100644 index 00000000000..35400314550 --- /dev/null +++ b/account_payment_line/__manifest__.py @@ -0,0 +1,20 @@ +# Copyright 2022 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +{ + "name": "Payment Counterpart Lines", + "summary": """Payment Counterpart Lines""", + "author": "ForgeFlow S.L., Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-payment", + "category": "Account", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "depends": ["account_payment"], + "data": [ + "security/ir.model.access.csv", + "views/account_payment_views.xml", + ], + "maintainers": ["ChrisOForgeFlow"], + "installable": True, + "auto_install": False, +} diff --git a/account_payment_line/i18n/account_payment_line.pot b/account_payment_line/i18n/account_payment_line.pot new file mode 100644 index 00000000000..9dea58316aa --- /dev/null +++ b/account_payment_line/i18n/account_payment_line.pot @@ -0,0 +1,300 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_payment_line +# +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: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__account_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__account_id +msgid "Account" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__amount +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__amount +msgid "Amount" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_amount_residual +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_amount_residual +msgid "Amount Residual" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_amount_residual_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_amount_residual_currency +msgid "Amount Residual Currency" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__amount_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__amount_currency +msgid "Amount in Company Currency" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__analytic_account_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__analytic_account_id +msgid "Analytic Account" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__commercial_partner_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__commercial_partner_id +msgid "Commercial Entity" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__company_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__company_id +msgid "Company" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment__line_payment_counterpart_ids +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_inherit_account_payment_form +msgid "Counterpart Lines" +msgstr "" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_payment_counterpart_line +msgid "Counterpart line payment" +msgstr "" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_payment_counterpart_line_abstract +msgid "Counterpart line payment Abstract" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__create_uid +msgid "Created by" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__create_date +msgid "Created on" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__currency_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__currency_id +msgid "Currency" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_date_maturity +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_date_maturity +msgid "Date Maturity" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_inherit_account_payment_form +msgid "Delete Counterpart Lines" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__name +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__name +msgid "Description" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__display_name +msgid "Display Name" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__fully_paid +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__fully_paid +msgid "Fully Paid?" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__id +msgid "ID" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__move_ids +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__move_ids +msgid "Journal Entries Created" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__move_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__move_id +msgid "Journal Entry" +msgstr "" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_id +msgid "Journal Item to Reconcile" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__write_date +msgid "Last Updated on" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__partner_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__partner_id +msgid "Partner" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__payment_id +msgid "Payment" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_move_line__payment_line_id +msgid "Payment line" +msgstr "" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_payment +msgid "Payments" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_inherit_account_payment_form +msgid "Propose Payment Distribution" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Residual" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__residual_after_payment +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__residual_after_payment +msgid "Residual After Payment" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__residual_after_payment_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__residual_after_payment_currency +msgid "Residual After Payment Currency" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Residual Currency" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,help:account_payment_line.field_account_payment_counterpart_line__aml_date_maturity +#: model:ir.model.fields,help:account_payment_line.field_account_payment_counterpart_line_abstract__aml_date_maturity +msgid "" +"This field is used for payable and receivable journal entries. You can put " +"the limit date for the payment of this line." +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Amount" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Amount Company Currency" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Due" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Due Currency" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,help:account_payment_line.field_account_payment__line_payment_counterpart_ids +msgid "" +"Use these lines to add matching lines, for example in a creditcard payment, " +"financing interest or commission is added" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Write-Off amount" +msgstr "" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Write-Off amount currency" +msgstr "" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/account_payment.py:0 +msgid "Write-off" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment__writeoff_account_id +msgid "Write-off Account" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__writeoff_account_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__writeoff_account_id +msgid "Write-off account" +msgstr "" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/account_payment.py:0 +msgid "Write-off account is not set for payment %(name)s" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__writeoff_amount +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__writeoff_amount +msgid "Writeoff Amount" +msgstr "" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__writeoff_amount_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__writeoff_amount_currency +msgid "Writeoff Amount Currency" +msgstr "" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/account_payment.py:0 +msgid "You should set up write-off account on lines or in header to continue" +msgstr "" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/counterpart_line.py:0 +msgid "the amount exceeds the residual amount, please check the invoice %s" +msgstr "" diff --git a/account_payment_line/i18n/it.po b/account_payment_line/i18n/it.po new file mode 100644 index 00000000000..e4f99398cf2 --- /dev/null +++ b/account_payment_line/i18n/it.po @@ -0,0 +1,337 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_payment_line +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-12-19 13:06+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.6.2\n" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/hooks.py:0 +#, python-format +msgid "A payment must always belongs to a bank or cash journal." +msgstr "Un pagamento deve sempre appartenere ad un registro banca o contanti." + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__account_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__account_id +msgid "Account" +msgstr "Conto" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__amount +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__amount +msgid "Amount" +msgstr "Importo" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_amount_residual +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_amount_residual +msgid "Amount Residual" +msgstr "Importo residuo" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_amount_residual_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_amount_residual_currency +msgid "Amount Residual Currency" +msgstr "Valuta importo residuo" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__amount_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__amount_currency +msgid "Amount in Company Currency" +msgstr "Importo in valuta aziendale" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__analytic_account_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__analytic_account_id +msgid "Analytic Account" +msgstr "Conto analitico" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__commercial_partner_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__commercial_partner_id +msgid "Commercial Entity" +msgstr "Entità commerciale" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__company_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__company_id +msgid "Company" +msgstr "Azienda" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment__line_payment_counterpart_ids +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_inherit_account_payment_form +msgid "Counterpart Lines" +msgstr "Righe contropartita" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_payment_counterpart_line +msgid "Counterpart line payment" +msgstr "Pagamento riga contropartita" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_payment_counterpart_line_abstract +msgid "Counterpart line payment Abstract" +msgstr "Sintesi pagamento riga contropartita" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__currency_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__currency_id +msgid "Currency" +msgstr "Valuta" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_date_maturity +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_date_maturity +msgid "Date Maturity" +msgstr "Data scadenza" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_inherit_account_payment_form +msgid "Delete Counterpart Lines" +msgstr "Cancella righe contropartita" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__name +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__name +msgid "Description" +msgstr "Descrizione" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__fully_paid +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__fully_paid +msgid "Fully Paid?" +msgstr "Completamente pagata?" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__id +msgid "ID" +msgstr "ID" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__move_ids +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__move_ids +msgid "Journal Entries Created" +msgstr "Registrazioni contabili create" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__move_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__move_id +msgid "Journal Entry" +msgstr "Registrazione contabile" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/hooks.py:0 +#, python-format +msgid "" +"Journal Entry %s is not valid. In order to proceed, the journal items must " +"share the same currency." +msgstr "" +"La registrazione contabile %s non è valida. Per procedere, i movimenti " +"contabili devono condividere la stessa valuta." + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_move_line +msgid "Journal Item" +msgstr "Movimento contabile" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__aml_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__aml_id +msgid "Journal Item to Reconcile" +msgstr "Movimento contabile da riconciliare" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__partner_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__partner_id +msgid "Partner" +msgstr "Partner" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__payment_id +msgid "Payment" +msgstr "Pagamento" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_move_line__payment_line_id +msgid "Payment line" +msgstr "Riga di pagamento" + +#. module: account_payment_line +#: model:ir.model,name:account_payment_line.model_account_payment +msgid "Payments" +msgstr "Pagamenti" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_inherit_account_payment_form +msgid "Propose Payment Distribution" +msgstr "Proponi distribuzione pagamento" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Residual" +msgstr "Residuo" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__residual_after_payment +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__residual_after_payment +msgid "Residual After Payment" +msgstr "Residuo dopo pagamento" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__residual_after_payment_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__residual_after_payment_currency +msgid "Residual After Payment Currency" +msgstr "Valuta residuo dopo pagamento" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Residual Currency" +msgstr "Valuta residuo" + +#. module: account_payment_line +#: model:ir.model.fields,help:account_payment_line.field_account_payment_counterpart_line__aml_date_maturity +#: model:ir.model.fields,help:account_payment_line.field_account_payment_counterpart_line_abstract__aml_date_maturity +msgid "" +"This field is used for payable and receivable journal entries. You can put " +"the limit date for the payment of this line." +msgstr "" +"Questo campo viene usato per pagare e ricevere registrazioni contabili. Si " +"può inserire la data limite per il pagamento di questa riga." + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Amount" +msgstr "Importo totale" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Amount Company Currency" +msgstr "Importo totale in valuta azienda" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Due" +msgstr "Totale dovuto" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Total Due Currency" +msgstr "Valuta totale dovuto" + +#. module: account_payment_line +#: model:ir.model.fields,help:account_payment_line.field_account_payment__line_payment_counterpart_ids +msgid "" +"Use these lines to add matching lines, for example in a creditcard payment, " +"financing interest or commission is added" +msgstr "" +"Utilizzare queste righe per aggiungere righe corrispondenti, ad esempio in " +"un pagamento con carta di credito, vengono aggiunti interessi o commissioni " +"di finanziamento" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Write-Off amount" +msgstr "Importo storno" + +#. module: account_payment_line +#: model_terms:ir.ui.view,arch_db:account_payment_line.view_account_payment_line_tree +msgid "Write-Off amount currency" +msgstr "Valuta importo storno" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/account_payment.py:0 +#, python-format +msgid "Write-off" +msgstr "Storno" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment__writeoff_account_id +msgid "Write-off Account" +msgstr "Conto storno" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__writeoff_account_id +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__writeoff_account_id +msgid "Write-off account" +msgstr "Conto storno" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/account_payment.py:0 +#, python-format +msgid "Write-off account is not set for payment %(name)s" +msgstr "Il conto storno non è impostato per il pagamento %(name)s" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__writeoff_amount +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__writeoff_amount +msgid "Writeoff Amount" +msgstr "Importo storno" + +#. module: account_payment_line +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line__writeoff_amount_currency +#: model:ir.model.fields,field_description:account_payment_line.field_account_payment_counterpart_line_abstract__writeoff_amount_currency +msgid "Writeoff Amount Currency" +msgstr "Valuta importo storno" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/account_payment.py:0 +#, python-format +msgid "You should set up write-off account on lines or in header to continue" +msgstr "" +"Si dovrebbe impostare un conto di storno sulle righe o nell'intestazione per " +"continuare" + +#. module: account_payment_line +#. odoo-python +#: code:addons/account_payment_line/models/counterpart_line.py:0 +#, python-format +msgid "the amount exceeds the residual amount, please check the invoice %s" +msgstr "l'importo eccede l'importo residuo, controllare la fattura %s" diff --git a/account_payment_line/models/__init__.py b/account_payment_line/models/__init__.py new file mode 100644 index 00000000000..a41b4214589 --- /dev/null +++ b/account_payment_line/models/__init__.py @@ -0,0 +1,3 @@ +from . import counterpart_line +from . import account_payment +from . import account_move diff --git a/account_payment_line/models/account_move.py b/account_payment_line/models/account_move.py new file mode 100644 index 00000000000..dbfb5428c46 --- /dev/null +++ b/account_payment_line/models/account_move.py @@ -0,0 +1,12 @@ +# Copyright 2022 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import fields, models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + payment_line_id = fields.Many2one( + "account.payment.counterpart.line", string="Payment line", ondelete="set null" + ) diff --git a/account_payment_line/models/account_payment.py b/account_payment_line/models/account_payment.py new file mode 100644 index 00000000000..e57ddca8e92 --- /dev/null +++ b/account_payment_line/models/account_payment.py @@ -0,0 +1,300 @@ +# Copyright 2022 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +from odoo import Command, fields, models +from odoo.exceptions import ValidationError + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + line_payment_counterpart_ids = fields.One2many( + "account.payment.counterpart.line", + "payment_id", + string="Counterpart Lines", + help="Use these lines to add matching lines, for example in a credit" + "card payment, financing interest or commission is added", + ) + writeoff_account_id = fields.Many2one( + "account.account", + string="Write-off Account", + domain="[('deprecated', '=', False), ('company_ids', '=', company_id)]", + ) + + def _process_post_reconcile(self): + for rec in self.filtered("line_payment_counterpart_ids"): + for line in rec.line_payment_counterpart_ids.filtered("aml_id"): + to_reconcile = (line.aml_id + line.move_ids).filtered( + lambda x: not x.reconciled and x.account_id.reconcile + ) + if to_reconcile: + to_reconcile.reconcile() + return True + + def _get_moves_domain(self): + domain = [ + ("amount_residual", "!=", 0.0), + ("state", "=", "posted"), + ("company_id", "=", self.company_id.id), + ( + "commercial_partner_id", + "=", + self.partner_id.commercial_partner_id.id, + ), + ] + move_type_map = { + ("supplier", "outbound"): [ + ("move_type", "in", ("in_invoice", "in_receipt")) + ], + ("supplier", "inbound"): [("move_type", "=", "in_refund")], + ("customer", "outbound"): [("move_type", "=", "out_refund")], + ("customer", "inbound"): [ + ("move_type", "in", ("out_invoice", "out_receipt")) + ], + } + domain.extend(move_type_map.get((self.partner_type, self.payment_type), [])) + return domain + + def _filter_amls(self, amls): + return amls.filtered( + lambda x: x.partner_id.commercial_partner_id.id + == self.partner_id.commercial_partner_id.id + and x.amount_residual != 0 + and x.account_id.account_type in ("asset_receivable", "liability_payable") + ) + + def _hook_create_new_line(self, invoice, aml, amount_to_apply): + line_model = self.env["account.payment.counterpart.line"] + self.ensure_one() + return line_model.create( + { + "payment_id": self.id, + "name": "/", + "move_id": invoice.id, + "aml_id": aml.id, + "account_id": aml.account_id.id, + "partner_id": self.partner_id.commercial_partner_id.id, + "amount": amount_to_apply, + } + ) + + def action_propose_payment_distribution(self): + for rec in self: + domain = rec._get_moves_domain() + pending_invoices = rec.env["account.move"].search( + domain, order="invoice_date_due ASC" + ) + pending_amount = rec.amount + rec.line_payment_counterpart_ids.unlink() + for invoice in pending_invoices: + for aml in self._filter_amls(invoice.line_ids): + amount_to_apply = 0 + amount_residual = rec.company_id.currency_id._convert( + aml.amount_residual, + rec.currency_id, + rec.company_id, + date=rec.date, + ) + if pending_amount >= 0: + amount_to_apply = min(abs(amount_residual), pending_amount) + pending_amount -= abs(amount_residual) + if rec.payment_type == "outbound": + amount_to_apply *= -1 + rec._hook_create_new_line(invoice, aml, amount_to_apply) + + def action_delete_counterpart_lines(self): + if self.line_payment_counterpart_ids and self.state == "draft": + self.line_payment_counterpart_ids = [Command.clear()] + + def _prepare_move_line_default_vals( + self, write_off_line_vals=None, force_balance=None + ): + new_write_off_line_vals = self._prepare_move_line_counterpart_vals( + write_off_line_vals + ) + if write_off_line_vals: + new_write_off_line_vals += write_off_line_vals + vals_list = super()._prepare_move_line_default_vals( + write_off_line_vals=new_write_off_line_vals, force_balance=force_balance + ) + # filter line with both debit and credit equal 0 + filter_vals_list = [ + vals + for vals in vals_list + if not (vals["debit"] == 0.0 and vals["credit"] == 0.0) + ] + return filter_vals_list if filter_vals_list else vals_list + + def _prepare_move_line_counterpart_vals(self, write_off_line_vals=None): + """return list of dictionary + * amount: The amount to be added to the counterpart amount. + * name: The label to set on the line. + * account_id: The account on which create the counterpart line. + """ + self.ensure_one() + write_off_line_vals_list = write_off_line_vals or [] + write_off_amount_currency = sum( + x["amount_currency"] for x in write_off_line_vals_list + ) + write_off_balance = sum(x["balance"] for x in write_off_line_vals_list) + is_outbound = self.payment_type == "outbound" + if is_outbound: + write_off_amount_currency *= -1 + new_aml_lines = [] + currency_id = self.currency_id + company_id = self.company_id + payment_date = self.date + for line in self.line_payment_counterpart_ids.filtered( + lambda x: not currency_id.is_zero(x.amount) + ): + line_balance = line.amount if is_outbound else line.amount * -1 + line_balance_currency = ( + line.amount_currency if is_outbound else line.amount_currency * -1 + ) + aml_value = line_balance_currency + write_off_balance + aml_value_currency = line_balance + write_off_amount_currency + if line.fully_paid and not currency_id.is_zero(line.writeoff_amount): + write_off_account = ( + line.writeoff_account_id.id or self.writeoff_account_id.id + ) + if not write_off_account: + raise ValidationError( + self.env._( + "Write-off account is not set for payment %(name)s", + name=self.display_name, + ) + ) + # Fully Paid line + amount_currency_fully_paid = abs(line.aml_amount_residual_currency) * ( + line.aml_amount_residual > 0.0 and -1 or 1 + ) + new_aml_lines.append( + { + "name": line.display_name, + "debit": line.aml_amount_residual < 0.0 + and abs(line.aml_amount_residual) + or 0.0, + "credit": line.aml_amount_residual > 0.0 + and abs(line.aml_amount_residual) + or 0.0, + "amount_currency": amount_currency_fully_paid, + "balance": currency_id._convert( + amount_currency_fully_paid, + company_id.currency_id, + company_id, + payment_date, + ), + "date_maturity": payment_date, + "partner_id": line.partner_id.commercial_partner_id.id, + "account_id": line.account_id.id, + "currency_id": line.payment_id.currency_id.id, + "payment_id": self.id, + "payment_line_id": line.id, + } + ) + # write-off line + amount_currency_write_off = abs(line.writeoff_amount_currency) * ( + line.writeoff_amount < 0.0 and -1 or 1 + ) + new_aml_lines.append( + { + "name": self.env._("Write-off"), + "debit": line.writeoff_amount > 0.0 + and line.writeoff_amount + or 0.0, + "credit": line.writeoff_amount < 0.0 + and -line.writeoff_amount + or 0.0, + "amount_currency": amount_currency_write_off, + "balance": currency_id._convert( + amount_currency_write_off, + company_id.currency_id, + company_id, + payment_date, + ), + "date_maturity": payment_date, + "partner_id": line.partner_id.commercial_partner_id.id, + "account_id": write_off_account, + "currency_id": line.payment_id.currency_id.id, + "payment_id": self.id, + "payment_line_id": line.id, + } + ) + else: + aml_value *= is_outbound and -1 or 1 + amount_currency = abs(aml_value_currency) * ( + aml_value < 0.0 and -1 or 1 + ) + new_aml_lines.append( + { + "name": line.display_name, + "debit": aml_value > 0.0 and aml_value or 0.0, + "credit": aml_value < 0.0 and -aml_value or 0.0, + "amount_currency": amount_currency, + "balance": currency_id._convert( + amount_currency, + company_id.currency_id, + company_id, + payment_date, + ), + "date_maturity": payment_date, + "partner_id": line.partner_id.commercial_partner_id.id, + "account_id": line.account_id.id, + "currency_id": line.payment_id.currency_id.id, + "payment_id": self.id, + "payment_line_id": line.id, + } + ) + return new_aml_lines + + def _check_writeoff_lines(self): + for rec in self: + writeoff_lines = rec.line_payment_counterpart_ids.filtered( + lambda x, currency=rec.currency_id: x.fully_paid + and not currency.is_zero(x.writeoff_amount) + ) + if not rec.writeoff_account_id and not all( + line.writeoff_account_id for line in writeoff_lines + ): + raise ValidationError( + self.env._( + "You should set up write-off account on lines " + "or in header to continue" + ) + ) + + def action_post(self): + self._check_writeoff_lines() + for rec in self.filtered("line_payment_counterpart_ids"): + if rec.move_id.line_ids: + rec.move_id.line_ids.unlink() + rec.move_id.line_ids = [ + (0, 0, line_vals) for line_vals in rec._prepare_move_line_default_vals() + ] + res = super().action_post() + self._process_post_reconcile() + return res + + def action_draft(self): + res = super().action_draft() + for rec in self.filtered("line_payment_counterpart_ids"): + # CHECK ME: force to recreate lines + # if document back to draft state, + # because we can change counterpart lines, + # but change will not be propagated properly + rec.move_id.line_ids.unlink() + return res + + +class AccountPaymentCounterLine(models.Model): + _name = "account.payment.counterpart.line" + _inherit = "account.payment.counterpart.line.abstract" + _description = "Counterpart line payment" + + payment_id = fields.Many2one( + "account.payment", string="Payment", ondelete="cascade" + ) + + def _get_onchange_fields(self): + res = super()._get_onchange_fields() + return res + ("payment_id.currency_id", "payment_id.date") diff --git a/account_payment_line/models/counterpart_line.py b/account_payment_line/models/counterpart_line.py new file mode 100644 index 00000000000..26df8ffb611 --- /dev/null +++ b/account_payment_line/models/counterpart_line.py @@ -0,0 +1,188 @@ +# Copyright 2022 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) +from odoo import api, fields, models +from odoo.exceptions import ValidationError + +dict_payment_type = dict( + inbound=["out_invoice", "in_refund", "out_receipt"], + outbound=["in_invoice", "out_refund", "in_receipt"], +) + + +class AccountPaymentCounterLinesAbstract(models.AbstractModel): + _name = "account.payment.counterpart.line.abstract" + _description = "Counterpart line payment Abstract" + + company_id = fields.Many2one( + comodel_name="res.company", compute="_compute_company_fields" + ) + name = fields.Char(string="Description", required=True, default="/") + account_id = fields.Many2one( + "account.account", + string="Account", + required=True, + ondelete="restrict", + check_company=True, + ) + analytic_account_id = fields.Many2one( + comodel_name="account.analytic.account", + string="Analytic Account", + ondelete="restrict", + check_company=True, + ) + currency_id = fields.Many2one( + comodel_name="res.currency", + string="Currency", + compute="_compute_company_fields", + ) + + fully_paid = fields.Boolean(string="Fully Paid?") + writeoff_account_id = fields.Many2one( + comodel_name="account.account", + string="Write-off account", + domain="[('deprecated', '=', False), ('company_ids', '=', company_id)]", + ) + writeoff_amount = fields.Monetary( + required=False, + compute="_compute_amounts", + currency_field="currency_id", + ) + writeoff_amount_currency = fields.Monetary( + required=False, + compute="_compute_amounts", + currency_field="currency_id", + ) + + @api.depends_context("company") + @api.depends("company_id") + def _compute_company_fields(self): + company = self.env.company + for rec in self: + rec.company_id = company.id + rec.currency_id = company.currency_id.id + + amount = fields.Monetary(required=True) + amount_currency = fields.Monetary( + string="Amount in Company Currency", + compute="_compute_amounts", + currency_field="currency_id", + ) + aml_amount_residual = fields.Monetary( + string="Amount Residual", + compute="_compute_amounts", + currency_field="currency_id", + ) + residual_after_payment = fields.Monetary( + compute="_compute_amounts", + currency_field="currency_id", + ) + aml_amount_residual_currency = fields.Monetary( + string="Amount Residual Currency", + compute="_compute_amounts", + currency_field="currency_id", + ) + residual_after_payment_currency = fields.Monetary( + compute="_compute_amounts", + currency_field="currency_id", + ) + + def _get_onchange_fields(self): + return "aml_id.amount_residual", "amount", "fully_paid" + + @api.depends(lambda x: x._get_onchange_fields()) + def _compute_amounts(self): + for rec in self: + payment = rec.payment_id + payment_date = ( + hasattr(payment, "payment_date") + and payment.payment_date + or payment.date + ) + rec.amount_currency = payment.currency_id._convert( + rec.amount, + payment.company_id.currency_id, + payment.company_id, + date=payment_date, + ) + rec.aml_amount_residual = rec.aml_id.amount_residual + min_max = payment.payment_type == "outbound" and min or max + rec.residual_after_payment = ( + not rec.fully_paid + and min_max(rec.aml_id.amount_residual - rec.amount, 0) + or 0.0 + ) + rec.writeoff_amount = ( + rec.fully_paid and (rec.aml_id.amount_residual - rec.amount) or 0.0 + ) + rec.aml_amount_residual_currency = rec.aml_id.amount_residual_currency + rec.residual_after_payment_currency = ( + not rec.fully_paid + and min_max( + rec.aml_id.amount_residual_currency - rec.amount_currency, 0 + ) + or 0.0 + ) + rec.writeoff_amount_currency = ( + rec.fully_paid + and (rec.aml_id.amount_residual_currency - rec.amount_currency) + or 0.0 + ) + + partner_id = fields.Many2one("res.partner", string="Partner", ondelete="restrict") + commercial_partner_id = fields.Many2one(related="partner_id.commercial_partner_id") + move_id = fields.Many2one( + "account.move", string="Journal Entry", ondelete="set null" + ) + move_ids = fields.One2many( + "account.move.line", + "payment_line_id", + string="Journal Entries Created", + ) + aml_id = fields.Many2one( + "account.move.line", string="Journal Item to Reconcile", ondelete="set null" + ) + aml_date_maturity = fields.Date( + string="Date Maturity", required=False, related="aml_id.date_maturity" + ) + + @api.onchange("move_id", "aml_id") + def _onchange_move_id(self): + aml_model = self.env["account.move.line"] + for rec in self: + dict_payment_type.get(rec.payment_id.payment_type, []) + if rec.move_id and not rec.aml_id: + domain = [ + ("move_id", "=", rec.move_id.id), + ("amount_residual", "!=", 0.0), + ] + lines_ordered = aml_model.search( + domain, order="date_maturity ASC", limit=1 + ) + if lines_ordered: + rec.aml_id = lines_ordered.id + if rec.aml_id: + rec.move_id = rec.aml_id.move_id.id + rec.account_id = rec.aml_id.account_id.id + rec.amount = rec.aml_id.amount_residual + rec.partner_id = rec.aml_id.partner_id.id + if rec.move_id.move_type == "entry": + if rec.payment_id.payment_type == "outbound": + rec.amount = -rec.aml_id.amount_residual + else: + rec.amount = rec.aml_id.amount_residual + + @api.constrains("amount") + def constrains_amount_residual(self): + for rec in self: + if ( + rec.aml_id + and 0 < rec.aml_amount_residual_currency < rec.amount_currency + ): + move_name = rec.aml_id.move_id.name or rec.aml_id.name + raise ValidationError( + self.env._( + "the amount exceeds the residual amount, " + "please check the invoice %s", + move_name, + ) + ) diff --git a/account_payment_line/pyproject.toml b/account_payment_line/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/account_payment_line/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/account_payment_line/readme/CONTRIBUTORS.md b/account_payment_line/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..e2daf8bf739 --- /dev/null +++ b/account_payment_line/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- Christopher Ormaza. \<\> +- Do Anh Duy \<\> diff --git a/account_payment_line/readme/DESCRIPTION.md b/account_payment_line/readme/DESCRIPTION.md new file mode 100644 index 00000000000..233fe3f9d17 --- /dev/null +++ b/account_payment_line/readme/DESCRIPTION.md @@ -0,0 +1,5 @@ +This module is an utility module to add lines in payment, allowing users +make more complicated cases when processing payments, split on many +invoices, set up specific write-off and adding some analytic information + +Add tool to proposal of payment distributions, ordering by due date diff --git a/account_payment_line/readme/USAGE.md b/account_payment_line/readme/USAGE.md new file mode 100644 index 00000000000..4d98ebcb838 --- /dev/null +++ b/account_payment_line/readme/USAGE.md @@ -0,0 +1,6 @@ +You can use payment distribution suggestion, and if system found moves +pending to reconcile related with partner selected, system will create +all lines trying to pay all invoices until amount remain + +You can add manually lines, if payment don't detect lines specified, +payment works as a normal payment diff --git a/account_payment_line/security/ir.model.access.csv b/account_payment_line/security/ir.model.access.csv new file mode 100644 index 00000000000..5e27e28cceb --- /dev/null +++ b/account_payment_line/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_account_payment_counterpart_line_all,access_account_payment_counterpart_line_all,model_account_payment_counterpart_line,base.group_user,1,0,0,0 +access_account_payment_counterpart_line_group_account_invoice,access_account_payment_counterpart_line_group_account_invoice,model_account_payment_counterpart_line,account.group_account_invoice,1,1,1,1 +access_account_payment_counterpart_line_group_account_user,access_account_payment_counterpart_line_group_account_user,model_account_payment_counterpart_line,account.group_account_user,1,1,1,1 +access_account_payment_counterpart_line_group_account_manager,access_account_payment_counterpart_line_group_account_manager,model_account_payment_counterpart_line,account.group_account_manager,1,1,1,1 diff --git a/account_payment_line/static/description/icon.png b/account_payment_line/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/account_payment_line/static/description/icon.png differ diff --git a/account_payment_line/static/description/index.html b/account_payment_line/static/description/index.html new file mode 100644 index 00000000000..8c4a58655b0 --- /dev/null +++ b/account_payment_line/static/description/index.html @@ -0,0 +1,444 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Payment Counterpart Lines

+ +

Beta License: AGPL-3 OCA/account-payment Translate me on Weblate Try me on Runboat

+

This module is an utility module to add lines in payment, allowing users +make more complicated cases when processing payments, split on many +invoices, set up specific write-off and adding some analytic information

+

Add tool to proposal of payment distributions, ordering by due date

+

Table of contents

+ +
+

Usage

+

You can use payment distribution suggestion, and if system found moves +pending to reconcile related with partner selected, system will create +all lines trying to pay all invoices until amount remain

+

You can add manually lines, if payment don’t detect lines specified, +payment works as a normal payment

+
+
+

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

+
    +
  • ForgeFlow S.L.
  • +
+
+
+

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.

+

Current maintainer:

+

ChrisOForgeFlow

+

This module is part of the OCA/account-payment project on GitHub.

+

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

+
+
+
+
+ + diff --git a/account_payment_line/tests/__init__.py b/account_payment_line/tests/__init__.py new file mode 100644 index 00000000000..7a4851544fa --- /dev/null +++ b/account_payment_line/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from . import test_account_payment_line diff --git a/account_payment_line/tests/test_account_payment_line.py b/account_payment_line/tests/test_account_payment_line.py new file mode 100644 index 00000000000..c81fe40411f --- /dev/null +++ b/account_payment_line/tests/test_account_payment_line.py @@ -0,0 +1,933 @@ +# Copyright 2022 ForgeFlow, S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) + +import json + +from odoo import Command, fields +from odoo.exceptions import ValidationError +from odoo.tests import Form, tagged + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon + + +@tagged("post_install", "-at_install") +class TestAccountPaymentLines(AccountTestInvoicingCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.test_product = cls.env["product.product"].create( + { + "name": "test_product", + "type": "service", + } + ) + cls.customer = cls.env["res.partner"].create({"name": "test_customer"}) + cls.customer2 = cls.env["res.partner"].create({"name": "test_customer"}) + cls.supplier = cls.env["res.partner"].create({"name": "test_vendor"}) + cls.supplier2 = cls.env["res.partner"].create({"name": "test_vendor"}) + cls.currency_2x = cls.env["res.currency"].create( + { + "name": "2X", # Foreign currency, 2 time + "symbol": "X", + "rate_ids": [ + Command.create( + { + "name": fields.Date.context_today(cls.env.user), + "rate": cls.env.company.currency_id.rate * 2, + }, + ) + ], + } + ) + cls.payment_terms_split = cls.env["account.payment.term"].create( + { + "name": "50% Advance End of Following Month", + "note": "Payment terms: 30% Advance End of Following Month", + "sequence": 500, + "line_ids": [ + Command.create( + { + "value": "percent", + "value_amount": 50.0, + "nb_days": 0, + }, + ), + Command.create( + { + "value": "percent", + "value_amount": 50.0, + "nb_days": 31, + }, + ), + ], + } + ) + + def setUp(self): + super().setUp() + self.bank_journal = self.company_data["default_journal_bank"] + self.account_receivable = self.company_data["default_account_receivable"] + self.account_payable = self.company_data["default_account_payable"] + self.account_expense = self.company_data["default_account_expense"] + + def _create_invoice( + self, move_type, partner, amount, currency=False, payment_term=False + ): + move_form = Form( + self.env["account.move"].with_context( + default_move_type=move_type, + ) + ) + if not currency: + currency = self.env.company.currency_id + move_form.invoice_date = fields.Date.context_today(self.env.user) + move_form.partner_id = partner + move_form.currency_id = currency + if payment_term: + move_form.invoice_payment_term_id = payment_term + with move_form.invoice_line_ids.new() as line_form: + line_form.product_id = self.test_product + line_form.price_unit = amount + line_form.tax_ids.clear() + move = move_form.save() + move.action_post() + return move + + def _create_refund(self, invoice): + ctx = {"active_model": "account.move", "active_ids": [invoice.id]} + move_reversal = ( + self.env["account.move.reversal"] + .with_context(**ctx) + .create( + { + "date": fields.Date.context_today(self.env.user), + "reason": "no reason", + "journal_id": invoice.journal_id.id, + } + ) + ) + reversal = move_reversal.refund_moves() + reverse_move = self.env["account.move"].browse(reversal["res_id"]) + return reverse_move + + def _create_payment( + self, + main_partner, + total_amount, + payment_type, + partner_type, + lines=False, + currency=False, + post=False, + suggest_payment_distribution=False, + writeoff_account=False, + ): + payment_form = Form( + self.env["account.payment"].with_context( + default_journal_id=self.bank_journal.id + ) + ) + payment_form.partner_id = main_partner + payment_form.payment_type = payment_type + payment_form.amount = total_amount + if writeoff_account: + payment_form.writeoff_account_id = writeoff_account + if not currency: + currency = self.env.company.currency_id + payment_form.currency_id = currency + if not lines: + lines = [] + for line in lines: + with payment_form.line_payment_counterpart_ids.new() as line_form: + if line.get("move_id", False): + line_form.move_id = line.get("move_id", False) + if line.get("account_id", False): + line_form.account_id = line.get("account_id", False) + if line.get("amount", False): + line_form.amount = line.get("amount", False) + if line.get("fully_paid", False): + line_form.fully_paid = line.get("fully_paid", False) + if line.get("writeoff_account_id", False): + line_form.writeoff_account_id = line.get( + "writeoff_account_id", False + ) + payment = payment_form.save() + if suggest_payment_distribution: + payment.action_propose_payment_distribution() + if post: + payment.action_post() + return payment + + def test_01_customer_payment(self): + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + new_payment = self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [{"move_id": new_invoice}], + post=True, + ) + self.assertEqual(new_payment.state, "paid") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual(new_payment.reconciled_invoice_ids, new_invoice) + + new_invoice2 = self._create_invoice("out_invoice", self.customer, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + new_payment2 = self._create_payment( + self.customer, + 50.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice2, + "amount": 50.0, + } + ], + post=True, + ) + self.assertEqual(new_payment2.state, "in_process") + self.assertTrue(new_payment2.is_reconciled) + self.assertEqual(new_payment2.reconciled_invoice_ids, new_invoice2) + self.assertEqual(new_invoice2.amount_residual, 50.0) + + new_payment3 = self._create_payment( + self.customer, + 50.0, + "inbound", + "customer", + [{"move_id": new_invoice2}], + post=True, + ) + self.assertEqual(new_payment3.state, "paid") + self.assertTrue(new_payment3.is_reconciled) + self.assertEqual(new_payment3.reconciled_invoice_ids, new_invoice2) + self.assertEqual(new_invoice2.amount_residual, 0.0) + + new_payment2.action_draft() + new_payment2.action_cancel() + + self.assertEqual(new_payment2.state, "canceled") + self.assertEqual(new_payment3.state, "in_process") + self.assertTrue(new_payment3.is_reconciled) + self.assertEqual(new_payment3.reconciled_invoice_ids, new_invoice2) + self.assertEqual(new_invoice2.amount_residual, 50.0) + + def test_02_customer_refund_payment(self): + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + new_invoice2 = self._create_invoice("out_invoice", self.customer, 100.0) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + new_payment = self._create_payment( + self.customer, + 200.0, + "inbound", + "customer", + [ + {"move_id": new_invoice}, + {"move_id": new_invoice2}, + ], + post=True, + ) + self.assertEqual(new_payment.state, "paid") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual(new_payment.reconciled_invoice_ids, new_invoice + new_invoice2) + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertIn(new_invoice2.payment_state, ["paid", "in_payment"]) + + new_refund = self._create_refund(new_invoice) + new_refund.action_post() + self.assertEqual(new_refund.payment_state, "not_paid") + self.assertEqual(new_refund.amount_total, 100.0) + new_payment_refund = self._create_payment( + self.customer, + 100.0, + "outbound", + "customer", + [{"move_id": new_refund}], + post=True, + ) + self.assertEqual(new_payment_refund.state, "paid") + self.assertTrue(new_payment_refund.is_reconciled) + self.assertEqual(new_payment_refund.reconciled_invoice_ids, new_refund) + self.assertIn(new_refund.payment_state, ["paid", "in_payment"]) + + def test_03_supplier_payment(self): + new_invoice = self._create_invoice("in_invoice", self.supplier, 100.0) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + new_payment = self._create_payment( + self.supplier, + 100.0, + "outbound", + "supplier", + [{"move_id": new_invoice}], + post=True, + ) + self.assertEqual(new_payment.state, "in_process") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual(new_payment.reconciled_bill_ids, new_invoice) + + new_invoice2 = self._create_invoice("in_invoice", self.supplier, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + new_payment2 = self._create_payment( + self.supplier, + 50.0, + "outbound", + "supplier", + [ + { + "move_id": new_invoice2, + "amount": -50.0, + } + ], + post=True, + ) + self.assertEqual(new_payment2.state, "in_process") + self.assertTrue(new_payment2.is_reconciled) + self.assertEqual(new_payment2.reconciled_bill_ids, new_invoice2) + self.assertEqual(new_invoice2.amount_residual, 50.0) + + new_payment3 = self._create_payment( + self.supplier, + 50.0, + "outbound", + "supplier", + [{"move_id": new_invoice2}], + post=True, + ) + self.assertEqual(new_payment3.state, "in_process") + self.assertTrue(new_payment3.is_reconciled) + self.assertEqual(new_payment3.reconciled_bill_ids, new_invoice2) + self.assertEqual(new_invoice2.amount_residual, 0.0) + + new_payment2.action_draft() + new_payment2.action_cancel() + + self.assertEqual(new_payment2.state, "canceled") + self.assertEqual(new_payment3.state, "in_process") + self.assertTrue(new_payment3.is_reconciled) + self.assertEqual(new_payment3.reconciled_bill_ids, new_invoice2) + self.assertEqual(new_invoice2.amount_residual, 50.0) + + def test_04_supplier_refund_payment(self): + new_invoice = self._create_invoice("in_invoice", self.supplier, 100.0) + new_invoice2 = self._create_invoice("in_invoice", self.supplier, 100.0) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + new_payment = self._create_payment( + self.supplier, + 200.0, + "outbound", + "supplier", + [ + {"move_id": new_invoice}, + {"move_id": new_invoice2}, + ], + post=True, + ) + self.assertEqual(new_payment.state, "in_process") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual(new_payment.reconciled_bill_ids, new_invoice + new_invoice2) + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertIn(new_invoice2.payment_state, ["paid", "in_payment"]) + + new_refund = self._create_refund(new_invoice) + new_refund.action_post() + self.assertEqual(new_refund.payment_state, "not_paid") + self.assertEqual(new_refund.amount_total, 100.0) + new_payment_refund = self._create_payment( + self.supplier, + 100.0, + "inbound", + "supplier", + [{"move_id": new_refund}], + post=True, + ) + self.assertEqual(new_payment_refund.state, "in_process") + self.assertTrue(new_payment_refund.is_reconciled) + self.assertEqual(new_payment_refund.reconciled_bill_ids, new_refund) + self.assertIn(new_refund.payment_state, ["paid", "in_payment"]) + + def test_05_partial_payments(self): + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + new_invoice2 = self._create_invoice("out_invoice", self.customer, 100.0) + new_invoice3 = self._create_invoice( + "out_invoice", self.customer, 100.0, payment_term=self.payment_terms_split + ) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + self.assertEqual(new_invoice3.payment_state, "not_paid") + self.assertEqual(new_invoice3.amount_total, 100.0) + self.assertEqual( + len( + new_invoice3.line_ids.filtered( + lambda x: x.partner_id.id == new_invoice3.partner_id.id + and x.account_id.account_type == "asset_receivable" + ) + ), + 2, + ) + new_payment = self._create_payment( + self.customer, + 150.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice, + "amount": 50.0, + }, + { + "move_id": new_invoice2, + "amount": 50.0, + }, + { + "move_id": new_invoice3, + "amount": 50.0, + }, + ], + post=True, + ) + self.assertEqual(new_invoice.payment_state, "partial") + self.assertEqual(new_invoice.amount_residual, 50.0) + self.assertEqual(new_invoice2.payment_state, "partial") + self.assertEqual(new_invoice2.amount_residual, 50.0) + self.assertEqual(new_invoice3.payment_state, "partial") + self.assertEqual(new_invoice3.amount_residual, 50.0) + self.assertEqual(new_payment.state, "in_process") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual( + new_payment.reconciled_invoice_ids, + new_invoice + new_invoice2 + new_invoice3, + ) + + new_payment2 = self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice, + "amount": 50.0, + }, + { + "move_id": new_invoice2, + "amount": 50.0, + }, + ], + post=True, + ) + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice.amount_residual, 0.0) + self.assertIn(new_invoice2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice2.amount_residual, 0.0) + self.assertEqual(new_invoice3.payment_state, "partial") + self.assertEqual(new_invoice3.amount_residual, 50.0) + self.assertEqual(new_payment2.state, "paid") + self.assertTrue(new_payment2.is_reconciled) + self.assertEqual( + new_payment2.reconciled_invoice_ids, + new_invoice + new_invoice2, + ) + new_payment3 = self._create_payment( + self.customer, + 50.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice3, + "amount": 50.0, + }, + ], + post=True, + ) + self.assertIn(new_invoice3.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice3.amount_residual, 0.0) + self.assertEqual(new_payment3.state, "paid") + self.assertTrue(new_payment3.is_reconciled) + self.assertEqual( + new_payment3.reconciled_invoice_ids, + new_invoice3, + ) + + def test_06_payments_without_invoices(self): + new_payment = self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [ + { + "account_id": self.account_expense, + "amount": 50.0, + }, + { + "account_id": self.customer.property_account_receivable_id, + "amount": 50.0, + }, + ], + post=True, + ) + self.assertEqual(new_payment.state, "in_process") + self.assertFalse(new_payment.is_reconciled) + self.assertFalse(bool(new_payment.reconciled_invoice_ids)) + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + payments = json.loads( + json.dumps(new_invoice.invoice_outstanding_credits_debits_widget) + ) + self.assertEqual(sum(p.get("amount") for p in payments.get("content")), 50) + new_invoice.js_assign_outstanding_line(payments.get("content", [])[0].get("id")) + self.assertEqual(new_invoice.payment_state, "partial") + self.assertEqual(new_invoice.amount_residual, 50.0) + self.assertEqual(new_payment.state, "in_process") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual( + new_payment.reconciled_invoice_ids, + new_invoice, + ) + + def test_07_payment_multi_currency(self): + new_invoice = self._create_invoice( + "out_invoice", self.customer, 100.0, currency=self.currency_2x + ) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + new_payment = self._create_payment( + self.customer, + 50.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice, + "amount": 50.0, + }, + ], + post=True, + ) + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice.amount_residual, 0.0) + self.assertEqual(new_payment.state, "paid") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual( + new_payment.reconciled_invoice_ids, + new_invoice, + ) + + new_invoice2 = self._create_invoice( + "out_invoice", self.customer, 100.0, currency=self.currency_2x + ) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + new_payment2 = self._create_payment( + self.customer, + 100.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice2, + "amount": 100.0, + }, + ], + post=True, + currency=self.currency_2x, + ) + self.assertIn(new_invoice2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice2.amount_residual, 0.0) + self.assertEqual(new_payment2.state, "paid") + self.assertTrue(new_payment2.is_reconciled) + self.assertEqual( + new_payment2.reconciled_invoice_ids, + new_invoice2, + ) + + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + new_payment = self._create_payment( + self.customer, + 200.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice, + "amount": 200.0, + }, + ], + post=True, + currency=self.currency_2x, + ) + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice.amount_residual, 0.0) + self.assertEqual(new_payment.state, "paid") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual( + new_payment.reconciled_invoice_ids, + new_invoice, + ) + + def test_08_payment_term_split(self): + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + new_invoice2 = self._create_invoice("out_invoice", self.customer, 100.0) + new_invoice3 = self._create_invoice( + "out_invoice", self.customer, 100.0, payment_term=self.payment_terms_split + ) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + self.assertEqual(new_invoice3.payment_state, "not_paid") + self.assertEqual(new_invoice3.amount_total, 100.0) + new_payment = self._create_payment( + self.customer, + 300.0, + "inbound", + "customer", + suggest_payment_distribution=True, + ) + self.assertEqual(len(new_payment.line_payment_counterpart_ids), 4) + self.assertEqual( + len( + new_payment.line_payment_counterpart_ids.filtered( + lambda x: x.amount == 50.0 + ) + ), + 2, + ) + self.assertEqual( + len( + new_payment.line_payment_counterpart_ids.filtered( + lambda x: x.amount == 100.0 + ) + ), + 2, + ) + new_payment.action_post() + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice.amount_residual, 0.0) + self.assertIn(new_invoice2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice2.amount_residual, 0.0) + self.assertIn(new_invoice3.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice3.amount_residual, 0.0) + self.assertEqual(new_payment.state, "paid") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual( + new_payment.reconciled_invoice_ids, + new_invoice + new_invoice2 + new_invoice3, + ) + + def test_09_offset_payment(self): + new_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + self.assertEqual(new_invoice.payment_state, "not_paid") + self.assertEqual(new_invoice.amount_total, 100.0) + new_payment = self._create_payment( + self.customer, + 50.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice, + "amount": 50.0, + "fully_paid": True, + }, + ], + writeoff_account=self.account_expense, + post=True, + ) + self.assertIn(new_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice.amount_residual, 0.0) + self.assertEqual(new_payment.state, "paid") + self.assertTrue(new_payment.is_reconciled) + self.assertEqual( + new_payment.reconciled_invoice_ids, + new_invoice, + ) + + new_invoice2 = self._create_invoice("out_invoice", self.customer, 100.0) + new_invoice3 = self._create_invoice("out_invoice", self.customer, 100.0) + self.assertEqual(new_invoice2.payment_state, "not_paid") + self.assertEqual(new_invoice2.amount_total, 100.0) + self.assertEqual(new_invoice3.payment_state, "not_paid") + self.assertEqual(new_invoice3.amount_total, 100.0) + new_payment2 = self._create_payment( + self.customer, + 150.0, + "inbound", + "customer", + [ + { + "move_id": new_invoice2, + "amount": 50.0, + "writeoff_account_id": self.account_expense, + "fully_paid": True, + }, + { + "move_id": new_invoice3, + "amount": 100.0, + }, + ], + ) + writeoff_line = new_payment2.line_payment_counterpart_ids.filtered( + lambda x: x.move_id == new_invoice2 + ) + self.assertEqual(writeoff_line.writeoff_amount, 50.0) + self.assertEqual(writeoff_line.residual_after_payment, 0.0) + new_payment2.action_post() + self.assertIn(new_invoice2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice2.amount_residual, 0.0) + self.assertIn(new_invoice3.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_invoice3.amount_residual, 0.0) + self.assertEqual(new_payment2.state, "paid") + self.assertTrue(new_payment2.is_reconciled) + self.assertEqual( + new_payment2.reconciled_invoice_ids, + new_invoice2 + new_invoice3, + ) + + def test_10_payment_distribution_proposition(self): + new_out_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + new_out_refund = self._create_invoice("out_refund", self.customer, 100.0) + new_in_invoice = self._create_invoice("in_invoice", self.customer, 100.0) + new_in_refund = self._create_invoice("in_refund", self.customer, 100.0) + new_out_invoice2 = self._create_invoice("out_invoice", self.customer2, 100.0) + new_out_refund2 = self._create_invoice("out_refund", self.customer2, 100.0) + new_in_invoice2 = self._create_invoice("in_invoice", self.customer2, 100.0) + new_in_refund2 = self._create_invoice("in_refund", self.customer2, 100.0) + + payment_form = Form( + self.env["account.payment"].with_context( + default_journal_id=self.bank_journal.id + ) + ) + payment_form.partner_id = self.customer + payment_form.payment_type = "inbound" + payment_form.amount = 50.0 + payment = payment_form.save() + payment.action_propose_payment_distribution() + self.assertEqual(len(payment.line_payment_counterpart_ids), 1) + self.assertEqual( + sum(payment.line_payment_counterpart_ids.mapped("amount")), 50.0 + ) + self.assertEqual( + payment.line_payment_counterpart_ids.mapped("move_id"), new_out_invoice + ) + + payment.action_delete_counterpart_lines() + self.assertEqual(len(payment.line_payment_counterpart_ids), 0) + + payment.action_propose_payment_distribution() + payment.action_post() + self.assertEqual(new_out_invoice.payment_state, "partial") + self.assertEqual(new_out_invoice.amount_residual, 50.0) + self.assertEqual(payment.state, "in_process") + self.assertTrue(payment.is_reconciled) + self.assertEqual( + payment.reconciled_invoice_ids, + new_out_invoice, + ) + + new_payment2 = self._create_payment( + self.customer, + 50.0, + "outbound", + "customer", + [ + { + "move_id": new_out_invoice, + "amount": 50.0, + }, + { + "move_id": new_out_refund, + "amount": -100.0, + }, + ], + post=True, + ) + self.assertIn(new_out_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_out_invoice.amount_residual, 0.0) + self.assertIn(new_out_refund.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_out_refund.amount_residual, 0.0) + self.assertEqual(new_payment2.state, "paid") + self.assertTrue(new_payment2.is_reconciled) + self.assertEqual( + new_payment2.reconciled_invoice_ids, + new_out_invoice + new_out_refund, + ) + + payment_form = Form( + self.env["account.payment"].with_context( + default_journal_id=self.bank_journal.id + ) + ) + payment_form.partner_id = self.customer2 + payment_form.payment_type = "outbound" + payment_form.amount = 100.0 + payment = payment_form.save() + payment.action_propose_payment_distribution() + self.assertEqual(len(payment.line_payment_counterpart_ids), 1) + self.assertEqual( + sum(payment.line_payment_counterpart_ids.mapped("amount")), -100.0 + ) + self.assertEqual( + payment.line_payment_counterpart_ids.mapped("move_id"), new_out_refund2 + ) + + payment.action_delete_counterpart_lines() + self.assertEqual(len(payment.line_payment_counterpart_ids), 0) + payment.write( + { + "payment_type": "outbound", + "partner_type": "supplier", + } + ) + payment.action_propose_payment_distribution() + self.assertEqual(len(payment.line_payment_counterpart_ids), 1) + self.assertEqual( + sum(payment.line_payment_counterpart_ids.mapped("amount")), -100.0 + ) + self.assertEqual( + payment.line_payment_counterpart_ids.mapped("move_id"), new_in_invoice2 + ) + + payment.action_delete_counterpart_lines() + self.assertEqual(len(payment.line_payment_counterpart_ids), 0) + payment.write( + { + "payment_type": "inbound", + "partner_type": "supplier", + } + ) + payment.action_propose_payment_distribution() + self.assertEqual(len(payment.line_payment_counterpart_ids), 1) + self.assertEqual( + sum(payment.line_payment_counterpart_ids.mapped("amount")), 100.0 + ) + self.assertEqual( + payment.line_payment_counterpart_ids.mapped("move_id"), new_in_refund2 + ) + payment.unlink() + + new_payment3 = self._create_payment( + self.customer, + 0.0, + "outbound", + "supplier", + [ + { + "move_id": new_in_refund, + "amount": 100.0, + }, + { + "move_id": new_in_invoice, + "amount": -100.0, + }, + ], + post=True, + ) + self.assertIn(new_in_refund.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_in_refund.amount_residual, 0.0) + self.assertIn(new_in_invoice.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_in_invoice.amount_residual, 0.0) + self.assertEqual(new_payment3.state, "paid") + self.assertTrue(new_payment3.is_reconciled) + self.assertEqual( + new_payment3.reconciled_bill_ids, + new_in_refund + new_in_invoice, + ) + + new_payment4 = self._create_payment( + self.customer2, + 0.0, + "inbound", + "customer", + [ + { + "move_id": new_out_refund2, + "amount": -100.0, + }, + { + "move_id": new_out_invoice2, + "amount": 100.0, + }, + { + "move_id": new_in_refund2, + "amount": 100.0, + }, + { + "move_id": new_in_invoice2, + "amount": -100.0, + }, + ], + post=True, + ) + self.assertIn(new_out_refund2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_out_refund2.amount_residual, 0.0) + self.assertIn(new_out_invoice2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_out_invoice2.amount_residual, 0.0) + self.assertIn(new_in_refund2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_in_refund2.amount_residual, 0.0) + self.assertIn(new_in_invoice2.payment_state, ["paid", "in_payment"]) + self.assertEqual(new_in_invoice2.amount_residual, 0.0) + self.assertEqual(new_payment4.state, "paid") + self.assertTrue(new_payment4.is_reconciled) + self.assertEqual( + new_payment4.reconciled_invoice_ids, + new_out_invoice2 + new_out_refund2, + ) + self.assertEqual( + new_payment4.reconciled_bill_ids, + new_in_refund2 + new_in_invoice2, + ) + + def test_11_exceptions(self): + new_out_invoice = self._create_invoice("out_invoice", self.customer, 100.0) + + # Should select a writeoff_account + with self.assertRaises(ValidationError): + self._create_payment( + self.customer, + 50.0, + "inbound", + "customer", + [ + { + "move_id": new_out_invoice, + "amount": 50.0, + "fully_paid": True, + }, + ], + post=True, + ) + + # Should input lower or equal amount than invoice selected in line + with self.assertRaises(ValidationError): + self._create_payment( + self.customer, + 150.0, + "inbound", + "customer", + [ + { + "move_id": new_out_invoice, + "amount": 150.0, + }, + ], + post=True, + ) diff --git a/account_payment_line/views/account_payment_views.xml b/account_payment_line/views/account_payment_views.xml new file mode 100644 index 00000000000..9a62a81d1d3 --- /dev/null +++ b/account_payment_line/views/account_payment_views.xml @@ -0,0 +1,137 @@ + + + + + view.account.payment.line.list + account.payment.counterpart.line + + + + + + + + + + + + + + + + + + + + + + + + + + view.inherit.account.payment.form + account.payment + + + + + + + + + (ev) => this.popoverPartialOutstanding(ev, line.id) + + + + diff --git a/account_payment_widget_amount/tests/__init__.py b/account_payment_widget_amount/tests/__init__.py new file mode 100644 index 00000000000..977fdd85eb7 --- /dev/null +++ b/account_payment_widget_amount/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_account_payment_widget_amount diff --git a/account_payment_widget_amount/tests/test_account_payment_widget_amount.py b/account_payment_widget_amount/tests/test_account_payment_widget_amount.py new file mode 100644 index 00000000000..34939e9a007 --- /dev/null +++ b/account_payment_widget_amount/tests/test_account_payment_widget_amount.py @@ -0,0 +1,430 @@ +# Copyright 2017-2021 ForgeFlow S.L. +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +import time + +from odoo.tests.common import TransactionCase + + +class TestAccountPaymentWidgetAmount(TransactionCase): + def setUp(self): + super().setUp() + # Models + self.partner = self.env["res.partner"].create({"name": "Test"}) + self.account_account_model = self.env["account.account"] + self.account_payment_model = self.env["account.payment"] + self.account_journal_model = self.env["account.journal"] + self.account_move_model = self.env["account.move"] + # Records + self.company = self.env.ref("base.main_company") + + self.account_bank = self.account_account_model.create( + { + "name": "Test Bank", + "code": "TEST.BANK", + "account_type": "asset_cash", + "reconcile": False, + "company_ids": [(6, 0, [self.company.id])], + } + ) + self.account_receivable = self.account_account_model.create( + { + "name": "Test Receivable", + "code": "TEST.AR", + "account_type": "asset_receivable", + "reconcile": True, + "company_ids": [(6, 0, [self.company.id])], + } + ) + self.account_payable = self.account_account_model.create( + { + "name": "Test Payable", + "code": "TEST.AP", + "account_type": "liability_payable", + "reconcile": True, + "company_ids": [(6, 0, [self.company.id])], + } + ) + self.partner.property_account_receivable_id = self.account_receivable + self.partner.property_account_payable_id = self.account_payable + self.account_income = self.account_account_model.create( + { + "name": "Test Income", + "code": "TEST.IN", + "account_type": "income", + "reconcile": False, + "company_ids": [(6, 0, [self.company.id])], + } + ) + self.account_expense = self.account_account_model.create( + { + "name": "Test Expense", + "code": "TEST.EX", + "account_type": "expense", + "reconcile": False, + "company_ids": [(6, 0, [self.company.id])], + } + ) + self.bank_journal = self.account_journal_model.create( + { + "name": "Test Bank", + "code": "TBK", + "type": "bank", + "company_id": self.company.id, + } + ) + self.sale_journal = self.account_journal_model.search( + [("type", "=", "sale"), ("company_id", "=", self.company.id)] + )[0] + self.purchase_journal = self.account_journal_model.search( + [("type", "=", "purchase"), ("company_id", "=", self.company.id)] + )[0] + # Create a new currency. It is 2x the valuation + # of the company currency. + self.new_usd = self.env["res.currency"].create( + { + "name": "us2", + "symbol": "$²", + "rate_ids": [(0, 0, {"rate": 2, "name": time.strftime("%Y-%m-%d")})], + } + ) + + def test_01(self): + """Tests that I can create an invoice in company currency, + register a payment in company currency, and then reconcile part + of the payment to the invoice. + I expect: + - The residual amount of the invoice is reduced by the amount assigned. + - The residual amount of the payment is reduced by the amount assigned. + """ + invoice = self.account_move_model.create( + { + "name": "Test Customer Invoice", + "journal_id": self.sale_journal.id, + "partner_id": self.partner.id, + "company_id": self.company.id, + "currency_id": self.company.currency_id.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "Line 1", + "price_unit": 200.0, + "account_id": self.account_income.id, + "quantity": 1, + "tax_ids": False, + }, + ) + ], + } + ) + # Open invoice + invoice.action_post() + # Create a payment + payment = self.account_payment_model.create( + { + "payment_type": "inbound", + "payment_method_id": self.env.ref( + "account.account_payment_method_manual_in" + ).id, + "partner_type": "customer", + "partner_id": self.partner.id, + "amount": 1000.0, + "currency_id": self.company.currency_id.id, + "date": time.strftime("%Y-%m-%d"), + "journal_id": self.bank_journal.id, + "company_id": self.company.id, + } + ) + payment.action_post() + payment_ml = payment.move_id.line_ids.filtered( + lambda line: line.account_id == self.account_receivable + ) + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 100.0) + self.assertFalse(payment_ml.reconciled) + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 0.0) + self.assertIn(invoice.payment_state, ("paid", "in_payment")) + self.assertFalse(payment_ml.reconciled) + + def test_02(self): + """Tests that I can create an invoice in foreign currency, + register a payment in company currency, and then reconcile part + of the payment to the invoice. + I expect: + - The residual amount of the invoice is reduced by the amount assigned. + - The residual amount of the payment is reduced by the amount assigned. + """ + self.company.currency_id.rate_ids = False + # The invoice is for 200 in the new currency, which translates in 100 + # in company currency. + invoice = self.account_move_model.create( + { + "move_type": "out_invoice", + "name": "Test Customer Invoice", + "journal_id": self.sale_journal.id, + "partner_id": self.partner.id, + "company_id": self.company.id, + "currency_id": self.new_usd.id, + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "Line 1", + "price_unit": 200.0, + "account_id": self.account_income.id, + "quantity": 1, + "tax_ids": False, + }, + ) + ], + } + ) + + # Open invoice + invoice.action_post() + # Create a payment + payment = self.account_payment_model.create( + { + "payment_type": "inbound", + "payment_method_id": self.env.ref( + "account.account_payment_method_manual_in" + ).id, + "partner_type": "customer", + "partner_id": self.partner.id, + "amount": 1000.0, + "currency_id": self.company.currency_id.id, + "date": time.strftime("%Y-%m-%d"), + "journal_id": self.bank_journal.id, + "company_id": self.company.id, + } + ) + payment.action_post() + payment_ml = payment.move_id.line_ids.filtered( + lambda line: line.account_id == self.account_receivable + ) + # We pay 100 in the currency of the invoice. Which means that in + # company currency we are paying 50. + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 100.0) + self.assertEqual(invoice.payment_state, "partial") + self.assertFalse(payment_ml.reconciled) + self.assertEqual(payment_ml.amount_residual, -950.0) + + def test_03(self): + """Tests that I can create an refund invoice in foreign currency, + register an outgoing payment in company currency, and then + reconcile part of the payment to the invoice. + I expect: + - The residual amount of the invoice is reduced by the amount assigned. + - The residual amount of the payment is reduced by the amount assigned. + """ + self.company.currency_id.rate_ids = False + # The invoice is for 200 in the new currency, which translates in 100 + # in company currency. + invoice = self.account_move_model.create( + { + "name": "Test Customer Invoice", + "move_type": "out_refund", + "journal_id": self.sale_journal.id, + "partner_id": self.partner.id, + "company_id": self.company.id, + "currency_id": self.new_usd.id, + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "Line 1", + "price_unit": 200.0, + "account_id": self.account_income.id, + "quantity": 1, + "tax_ids": False, + }, + ) + ], + } + ) + + # Open invoice + invoice.action_post() + # Create a payment + payment = self.account_payment_model.create( + { + "payment_type": "outbound", + "payment_method_id": self.env.ref( + "account.account_payment_method_manual_out" + ).id, + "partner_type": "customer", + "partner_id": self.partner.id, + "amount": 1000.0, + "currency_id": self.company.currency_id.id, + "date": time.strftime("%Y-%m-%d"), + "journal_id": self.bank_journal.id, + "company_id": self.company.id, + } + ) + payment.action_post() + payment_ml = payment.move_id.line_ids.filtered( + lambda line: line.account_id == self.account_receivable + ) + # We collect 100 in the currency of the refund. Which means that in + # company currency we are reconciling 50. + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 100.0) + self.assertEqual(invoice.payment_state, "partial") + self.assertFalse(payment_ml.reconciled) + self.assertEqual(payment_ml.amount_residual, 950.0) + + def test_04(self): + """Tests that I can create an invoice in company currency, + register a payment in foreign currency, and then reconcile part + of the payment to the invoice. + I expect: + - The residual amount of the invoice is reduced by the amount assigned. + - The residual amount of the payment is reduced by the amount assigned. + """ + self.company.currency_id.rate_ids = False + invoice = self.account_move_model.create( + { + "move_type": "out_invoice", + "name": "Test Customer Invoice", + "journal_id": self.sale_journal.id, + "partner_id": self.partner.id, + "company_id": self.company.id, + "currency_id": self.company.currency_id.id, + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "Line 1", + "price_unit": 200.0, + "account_id": self.account_income.id, + "quantity": 1, + "tax_ids": False, + }, + ) + ], + } + ) + + # Open invoice + invoice.action_post() + # Create a payment for 1000 of foreign currency, which translates + # to 500 in company currency. + payment = self.account_payment_model.create( + { + "payment_type": "inbound", + "payment_method_id": self.env.ref( + "account.account_payment_method_manual_in" + ).id, + "partner_type": "customer", + "partner_id": self.partner.id, + "amount": 1000.0, + "currency_id": self.new_usd.id, + "date": time.strftime("%Y-%m-%d"), + "journal_id": self.bank_journal.id, + "company_id": self.company.id, + } + ) + payment.action_post() + payment_ml = payment.move_id.line_ids.filtered( + lambda line: line.account_id == self.account_receivable + ) + # We pay 100 in the currency of the invoice, which is the + # company currency + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 100.0) + self.assertEqual(invoice.payment_state, "partial") + self.assertFalse(payment_ml.reconciled) + self.assertEqual(payment_ml.amount_residual, -400.0) + self.assertEqual(payment_ml.amount_residual_currency, -800.0) + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 0.0) + self.assertIn(invoice.payment_state, ("paid", "in_payment")) + self.assertEqual(payment_ml.amount_residual, -300.0) + self.assertEqual(payment_ml.amount_residual_currency, -600.0) + + def test_05(self): + """Tests that I can create a vendor bill in company currency, + register a payment in company currency, and then reconcile part + of the payment to the bill. + I expect: + - The residual amount of the invoice is reduced by the amount assigned. + - The residual amount of the payment is reduced by the amount assigned. + """ + invoice = self.account_move_model.create( + { + "name": "Test Vendor Bill", + "move_type": "in_invoice", + "journal_id": self.purchase_journal.id, + "partner_id": self.partner.id, + "company_id": self.company.id, + "invoice_date": time.strftime("%Y-%m-%d"), + "currency_id": self.company.currency_id.id, + "invoice_line_ids": [ + ( + 0, + 0, + { + "name": "Line 1", + "price_unit": 200.0, + "account_id": self.account_expense.id, + "quantity": 1, + "tax_ids": False, + }, + ) + ], + } + ) + + # Open invoice + invoice.action_post() + # Create a payment + payment = self.account_payment_model.create( + { + "payment_type": "outbound", + "payment_method_id": self.env.ref( + "account.account_payment_method_manual_out" + ).id, + "partner_type": "supplier", + "partner_id": self.partner.id, + "amount": 1000.0, + "currency_id": self.company.currency_id.id, + "date": time.strftime("%Y-%m-%d"), + "journal_id": self.bank_journal.id, + "company_id": self.company.id, + } + ) + payment.action_post() + payment_ml = payment.move_id.line_ids.filtered( + lambda line: line.account_id == self.account_payable + ) + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 100.0) + self.assertFalse(payment_ml.reconciled) + invoice.with_context(paid_amount=100.0).js_assign_outstanding_line( + payment_ml.id + ) + self.assertEqual(invoice.amount_residual, 0.0) + self.assertIn(invoice.payment_state, ("paid", "in_payment")) + self.assertFalse(payment_ml.reconciled) diff --git a/partner_aging/README.rst b/partner_aging/README.rst new file mode 100644 index 00000000000..bdde3ff5fb8 --- /dev/null +++ b/partner_aging/README.rst @@ -0,0 +1,123 @@ +.. image:: https://odoo-community.org/readme-banner-image + :target: https://odoo-community.org/get-involved?utm_source=readme + :alt: Odoo Community Association + +===================================== +Interactive Partner Aging at any date +===================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:81728cb9589df17089f55bb5b5f10e7dbe1cc84b28ed6a75b231f08b86c416cb + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |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%2Faccount--payment-lightgray.png?logo=github + :target: https://github.com/OCA/account-payment/tree/18.0/partner_aging + :alt: OCA/account-payment +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/account-payment-18-0/account-payment-18-0-partner_aging + :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/account-payment&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module creates a new customer / supplier aging report as of a given +date. + +The default Aged Partner balance report is related to a specific date +and a static PDF that is based on the difference between credits and +debits, not based on documents such as Invoices/Bills and Payments. + +This module provides an interactive view with details of the invoice. + +This module does not consider unapplied credits. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +1. Go to Accounting +2. Click on Sales > Customer Aging or Vendors > Supplier Aging +3. Change the date if necessary + +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 +------- + +* Open Source Integrators + +Contributors +------------ + +- Bhavesh Odedra +- Balaji Kannan +- Sandeep Mangukiya +- Sudarshan Kadalazhi +- Murtuza Saleh +- Maxime Chambreuil +- Nikul Chaudhary +- Ammar Offcewala +- Urvisha Desai +- Miquel Alzanillas + +Other credits +------------- + +The development of this module has been financially supported by: + +- Open Source Integrators +- Serpent Consulting Services Pvt. Ltd. + +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. + +.. |maintainer-Urvisha-OSI| image:: https://github.com/Urvisha-OSI.png?size=40px + :target: https://github.com/Urvisha-OSI + :alt: Urvisha-OSI + +Current `maintainer `__: + +|maintainer-Urvisha-OSI| + +This module is part of the `OCA/account-payment `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/partner_aging/__init__.py b/partner_aging/__init__.py new file mode 100644 index 00000000000..ffe5d86224f --- /dev/null +++ b/partner_aging/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2022 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import wizard diff --git a/partner_aging/__manifest__.py b/partner_aging/__manifest__.py new file mode 100644 index 00000000000..c2d69e3b1b3 --- /dev/null +++ b/partner_aging/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2012 - 2022 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +{ + "name": "Interactive Partner Aging at any date", + "summary": "Aging as a view - invoices and credits", + "version": "18.0.1.0.0", + "license": "AGPL-3", + "author": "Open Source Integrators, Odoo Community Association (OCA)", + "category": "Accounting & Finance", + "website": "https://github.com/OCA/account-payment", + "depends": ["account"], + "data": [ + "security/ir.model.access.csv", + "wizard/res_partner_aging_customer.xml", + "wizard/res_partner_aging_supplier.xml", + ], + "installable": True, + "application": True, + "development_status": "Production/Stable", + "maintainers": ["Urvisha-OSI"], +} diff --git a/partner_aging/i18n/es.po b/partner_aging/i18n/es.po new file mode 100644 index 00000000000..eec61bc806f --- /dev/null +++ b/partner_aging/i18n/es.po @@ -0,0 +1,271 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_aging +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-09-21 16:38+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\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 4.17\n" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_121togr +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_121togr +msgid "+121" +msgstr "+121" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_01to30 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_01to30 +msgid "1/30" +msgstr "1/30" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_31to60 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_31to60 +msgid "31/60" +msgstr "31/60" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_61to90 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_61to90 +msgid "61/90" +msgstr "61/90" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_91to120 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_91to120 +msgid "91/120" +msgstr "91/120" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__age_date +msgid "Aging Date" +msgstr "Fecha de antigüedad" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__avg_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__avg_days_overdue +msgid "Avg Days Overdue" +msgstr "Promedio de días vencidos" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Cancel" +msgstr "Cancelar" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Choose date for Supplier Aging" +msgstr "Seleccionar una fecha de antigüedad de proveedores" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Choose date for partner Aging" +msgstr "Seleccionar una fecha de antigüedad de socios" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Current" +msgstr "Actual" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +msgid "Customer" +msgstr "Cliente" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_partner_aging_date +#: model:ir.ui.menu,name:partner_aging.menu_customer_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Customer Aging" +msgstr "Antigüedad de clientes" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_customer_aging_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Customer Aging Date wise" +msgstr "Antigüedad relativa de clientes" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date +msgid "Date" +msgstr "Fecha" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__max_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__max_days_overdue +msgid "Days Outstanding" +msgstr "Días pendientes" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Due Date" +msgstr "Fecha de vencimiento" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Group By" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Group By..." +msgstr "Agrupar por..." + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__id +msgid "ID" +msgstr "ID (identificación)" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_id +msgid "Invoice" +msgstr "Factura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__inv_date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__inv_date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Invoice Date" +msgstr "Fecha factura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_uid +msgid "Last Updated by" +msgstr "Última Actualzacíón por" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_date +msgid "Last Updated on" +msgstr "Última Actualización el" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__not_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__not_due +msgid "Not Due Yet" +msgstr "Aún no vencida" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Open Document" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_ref +msgid "Our Invoice" +msgstr "Nuestra factura" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Overdue" +msgstr "Con retraso" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__partner_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__partner_id +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Partner" +msgstr "Socio" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_customer +msgid "Res Partner Aging Customer" +msgstr "Res Socio Cliente de edad avanzada" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_date +msgid "Res Partner Aging Date" +msgstr "Fecha de antigüedad Socio Res" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_supplier +msgid "Res Partner Aging Supplier" +msgstr "Antigüedad de proveedores" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__salesman +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__salesman +msgid "Sales Rep" +msgstr "Representante de ventas" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Supplier" +msgstr "Proveedor" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_date +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_tree +#: model:ir.ui.menu,name:partner_aging.menu_supplier_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Supplier Aging" +msgstr "Antigüedad de proveedor" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Supplier Aging Date wise" +msgstr "Antigüedad relativa de proveedor" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_ref +msgid "Their Invoice" +msgstr "Su factura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__total +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__total +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Total" +msgstr "Total/es" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "or" +msgstr "o" + +#~ msgid "Last Modified on" +#~ msgstr "Última Modificación el" diff --git a/partner_aging/i18n/fr.po b/partner_aging/i18n/fr.po new file mode 100644 index 00000000000..e725730b9c8 --- /dev/null +++ b/partner_aging/i18n/fr.po @@ -0,0 +1,271 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_aging +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-06-10 14:25+0000\n" +"Last-Translator: EvaS595 \n" +"Language-Team: none\n" +"Language: fr\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: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_121togr +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_121togr +msgid "+121" +msgstr "+121" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_01to30 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_01to30 +msgid "1/30" +msgstr "1/30" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_31to60 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_31to60 +msgid "31/60" +msgstr "31/60" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_61to90 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_61to90 +msgid "61/90" +msgstr "61/90" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_91to120 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_91to120 +msgid "91/120" +msgstr "91/120" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__age_date +msgid "Aging Date" +msgstr "Date d’échéance" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__avg_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__avg_days_overdue +msgid "Avg Days Overdue" +msgstr "Durée moyenne de retard" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Cancel" +msgstr "Annuler" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Choose date for Supplier Aging" +msgstr "Choisir une date pour la balance âgée des fournisseurs" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Choose date for partner Aging" +msgstr "Choisir une date pour la balance âgée des clients" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_uid +msgid "Created by" +msgstr "Créé par" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_date +msgid "Created on" +msgstr "Créé le" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Current" +msgstr "Actuel" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +msgid "Customer" +msgstr "Client" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_partner_aging_date +#: model:ir.ui.menu,name:partner_aging.menu_customer_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Customer Aging" +msgstr "Balance âgée des clients" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_customer_aging_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Customer Aging Date wise" +msgstr "Balance âgée des clients par échéances" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date +msgid "Date" +msgstr "Date" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__max_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__max_days_overdue +msgid "Days Outstanding" +msgstr "Jours exceptionnels" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__display_name +msgid "Display Name" +msgstr "Nom d'affichage" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Due Date" +msgstr "Date d'échéance" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Group By" +msgstr "Regrouper par" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Group By..." +msgstr "Regrouper par..." + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__id +msgid "ID" +msgstr "ID" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_id +msgid "Invoice" +msgstr "Facture" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__inv_date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__inv_date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Invoice Date" +msgstr "Date de la facture" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_uid +msgid "Last Updated by" +msgstr "Dernière modification par" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_date +msgid "Last Updated on" +msgstr "Mis à jour le" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__not_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__not_due +msgid "Not Due Yet" +msgstr "Pas encore dû" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Open Document" +msgstr "Ouvrir le document" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_ref +msgid "Our Invoice" +msgstr "Nos factures" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Overdue" +msgstr "Retard" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__partner_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__partner_id +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Partner" +msgstr "Partenaire" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_customer +msgid "Res Partner Aging Customer" +msgstr "Res Partner Balance Agée Client" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_date +msgid "Res Partner Aging Date" +msgstr "Res Partner Balance Agée Date" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_supplier +msgid "Res Partner Aging Supplier" +msgstr "Res Partner Balance Agée Fournisseur" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__salesman +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__salesman +msgid "Sales Rep" +msgstr "Commercial" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Supplier" +msgstr "Fournisseur" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_date +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_tree +#: model:ir.ui.menu,name:partner_aging.menu_supplier_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Supplier Aging" +msgstr "Balance âgée des fournisseurs" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Supplier Aging Date wise" +msgstr "Balance âgée des fournisseurs par échéances" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_ref +msgid "Their Invoice" +msgstr "Leurs factures" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__total +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__total +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Total" +msgstr "Total" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "or" +msgstr "ou" + +#~ msgid "Last Modified on" +#~ msgstr "Dernière modification le" diff --git a/partner_aging/i18n/it.po b/partner_aging/i18n/it.po new file mode 100644 index 00000000000..c892d18ff53 --- /dev/null +++ b/partner_aging/i18n/it.po @@ -0,0 +1,268 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_aging +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-01-11 13:06+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.6.2\n" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_121togr +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_121togr +msgid "+121" +msgstr "+121" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_01to30 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_01to30 +msgid "1/30" +msgstr "1/30" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_31to60 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_31to60 +msgid "31/60" +msgstr "31/60" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_61to90 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_61to90 +msgid "61/90" +msgstr "61/90" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_91to120 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_91to120 +msgid "91/120" +msgstr "91/120" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__age_date +msgid "Aging Date" +msgstr "Data anzianità" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__avg_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__avg_days_overdue +msgid "Avg Days Overdue" +msgstr "Giorni medi scadenza" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Cancel" +msgstr "Annulla" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Choose date for Supplier Aging" +msgstr "Scegliere la data per anzianità fornitore" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Choose date for partner Aging" +msgstr "Scegliere la data per anzianità partner" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_uid +msgid "Created by" +msgstr "Creato da" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_date +msgid "Created on" +msgstr "Creato il" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Current" +msgstr "Attuale" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +msgid "Customer" +msgstr "Cliente" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_partner_aging_date +#: model:ir.ui.menu,name:partner_aging.menu_customer_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Customer Aging" +msgstr "Anzianità cliente" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_customer_aging_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Customer Aging Date wise" +msgstr "In merito alla data anzianità cliente" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date +msgid "Date" +msgstr "Data" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__max_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__max_days_overdue +msgid "Days Outstanding" +msgstr "Giorni ritardo" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Due Date" +msgstr "Scadenza" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Group By" +msgstr "Raggruppa per" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Group By..." +msgstr "Raggruppa per..." + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__id +msgid "ID" +msgstr "ID" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_id +msgid "Invoice" +msgstr "Fattura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__inv_date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__inv_date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Invoice Date" +msgstr "Data fattura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_uid +msgid "Last Updated by" +msgstr "Ultimo aggiornamento di" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_date +msgid "Last Updated on" +msgstr "Ultimo aggiornamento il" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__not_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__not_due +msgid "Not Due Yet" +msgstr "Non ancora dovuto" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Open Document" +msgstr "Apri documento" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_ref +msgid "Our Invoice" +msgstr "Nostra fattura" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Overdue" +msgstr "Scaduta" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__partner_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__partner_id +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Partner" +msgstr "Partner" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_customer +msgid "Res Partner Aging Customer" +msgstr "Anzianità cliente res partner" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_date +msgid "Res Partner Aging Date" +msgstr "Data anzianità res partner" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_supplier +msgid "Res Partner Aging Supplier" +msgstr "Anzianità fornitore res partner" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__salesman +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__salesman +msgid "Sales Rep" +msgstr "Resoconto vendite" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Supplier" +msgstr "Fornitore" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_date +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_tree +#: model:ir.ui.menu,name:partner_aging.menu_supplier_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Supplier Aging" +msgstr "Anzianità fornitore" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Supplier Aging Date wise" +msgstr "In meroto alla data anzianità fornitore" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_ref +msgid "Their Invoice" +msgstr "Loro fattura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__total +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__total +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Total" +msgstr "Totale" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "or" +msgstr "o" diff --git a/partner_aging/i18n/partner_aging.pot b/partner_aging/i18n/partner_aging.pot new file mode 100644 index 00000000000..9d280ba0df4 --- /dev/null +++ b/partner_aging/i18n/partner_aging.pot @@ -0,0 +1,265 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_aging +# +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: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_121togr +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_121togr +msgid "+121" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_01to30 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_01to30 +msgid "1/30" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_31to60 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_31to60 +msgid "31/60" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_61to90 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_61to90 +msgid "61/90" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_91to120 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_91to120 +msgid "91/120" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__age_date +msgid "Aging Date" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__avg_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__avg_days_overdue +msgid "Avg Days Overdue" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Cancel" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Choose date for Supplier Aging" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Choose date for partner Aging" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_uid +msgid "Created by" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_date +msgid "Created on" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_list +msgid "Current" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Customer" +msgstr "" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_partner_aging_date +#: model:ir.ui.menu,name:partner_aging.menu_customer_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Customer Aging" +msgstr "" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_customer_aging_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Customer Aging Date wise" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date +msgid "Date" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__max_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__max_days_overdue +msgid "Days Outstanding" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__display_name +msgid "Display Name" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_list +msgid "Due Date" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Group By" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Group By..." +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__id +msgid "ID" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_id +msgid "Invoice" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__inv_date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__inv_date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_list +msgid "Invoice Date" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_date +msgid "Last Updated on" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__not_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__not_due +msgid "Not Due Yet" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_list +msgid "Open Document" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_ref +msgid "Our Invoice" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Overdue" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__partner_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__partner_id +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Partner" +msgstr "" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_customer +msgid "Res Partner Aging Customer" +msgstr "" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_date +msgid "Res Partner Aging Date" +msgstr "" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_supplier +msgid "Res Partner Aging Supplier" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__salesman +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__salesman +msgid "Sales Rep" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Supplier" +msgstr "" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_date +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_list +#: model:ir.ui.menu,name:partner_aging.menu_supplier_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Supplier Aging" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Supplier Aging Date wise" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_ref +msgid "Their Invoice" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__total +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__total +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_list +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Total" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "or" +msgstr "" diff --git a/partner_aging/i18n/pt.po b/partner_aging/i18n/pt.po new file mode 100644 index 00000000000..3b4d497a8e0 --- /dev/null +++ b/partner_aging/i18n/pt.po @@ -0,0 +1,270 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_aging +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2019-08-18 01:44+0000\n" +"Last-Translator: Pedro Castro Silva \n" +"Language-Team: none\n" +"Language: pt\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" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_121togr +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_121togr +msgid "+121" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_01to30 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_01to30 +msgid "1/30" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_31to60 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_31to60 +msgid "31/60" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_61to90 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_61to90 +msgid "61/90" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__days_due_91to120 +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__days_due_91to120 +msgid "91/120" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__age_date +msgid "Aging Date" +msgstr "Data de Antiguidade" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__avg_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__avg_days_overdue +msgid "Avg Days Overdue" +msgstr "Med. Dias Vencidos" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Cancel" +msgstr "Cancelar" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Choose date for Supplier Aging" +msgstr "Escolha data para Antiguidade de Fornecedores" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Choose date for partner Aging" +msgstr "Escolha data para Antiguidade de Parceiros" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_uid +msgid "Created by" +msgstr "Criado por" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__create_date +msgid "Created on" +msgstr "Criado em" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Current" +msgstr "Corrente" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +msgid "Customer" +msgstr "Cliente" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_partner_aging_date +#: model:ir.ui.menu,name:partner_aging.menu_customer_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_partner_aging_date +msgid "Customer Aging" +msgstr "Antiguidade de Clientes" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_customer_aging_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Customer Aging Date wise" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date +msgid "Date" +msgstr "Data" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__max_days_overdue +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__max_days_overdue +msgid "Days Outstanding" +msgstr "Dias Por Liquidar" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__display_name +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__display_name +msgid "Display Name" +msgstr "Nome a Exibir" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Due Date" +msgstr "Data de Vencimento" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +msgid "Group By" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Group By..." +msgstr "Agrupar Por..." + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__id +msgid "ID" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_id +msgid "Invoice" +msgstr "Fatura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__inv_date_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__inv_date_due +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Invoice Date" +msgstr "Data da Fatura" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_uid +msgid "Last Updated by" +msgstr "Última Atualização por" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_date__write_date +msgid "Last Updated on" +msgstr "Última Atualização em" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__not_due +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__not_due +msgid "Not Due Yet" +msgstr "Vincendo" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Open Document" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__invoice_ref +msgid "Our Invoice" +msgstr "Nossa Fatura" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Overdue" +msgstr "Vencido" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__partner_id +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__partner_id +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Partner" +msgstr "Parceiro" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_customer +msgid "Res Partner Aging Customer" +msgstr "" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_date +msgid "Res Partner Aging Date" +msgstr "" + +#. module: partner_aging +#: model:ir.model,name:partner_aging.model_res_partner_aging_supplier +msgid "Res Partner Aging Supplier" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__salesman +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__salesman +msgid "Sales Rep" +msgstr "Repr. Vendas" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Supplier" +msgstr "Fornecedor" + +#. module: partner_aging +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_date +#: model:ir.actions.act_window,name:partner_aging.action_supplier_aging_tree +#: model:ir.ui.menu,name:partner_aging.menu_supplier_aging_wizard +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "Supplier Aging" +msgstr "Antiguidade de Fornecedores" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +msgid "Supplier Aging Date wise" +msgstr "" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__invoice_ref +msgid "Their Invoice" +msgstr "Fatura Deles" + +#. module: partner_aging +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_customer__total +#: model:ir.model.fields,field_description:partner_aging.field_res_partner_aging_supplier__total +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_customer_aging_ad_tree +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_search +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_ad_tree +msgid "Total" +msgstr "" + +#. module: partner_aging +#: model_terms:ir.ui.view,arch_db:partner_aging.view_supplier_aging_date +msgid "or" +msgstr "ou" + +#~ msgid "Last Modified on" +#~ msgstr "Última Modific. em" diff --git a/partner_aging/pyproject.toml b/partner_aging/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/partner_aging/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/partner_aging/readme/CONTRIBUTORS.md b/partner_aging/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..aaaad75956e --- /dev/null +++ b/partner_aging/readme/CONTRIBUTORS.md @@ -0,0 +1,10 @@ +- Bhavesh Odedra \<\> +- Balaji Kannan \<\> +- Sandeep Mangukiya \<\> +- Sudarshan Kadalazhi \<\> +- Murtuza Saleh \<\> +- Maxime Chambreuil \<\> +- Nikul Chaudhary \<\> +- Ammar Offcewala \<\> +- Urvisha Desai \<\> +- Miquel Alzanillas \<\> diff --git a/partner_aging/readme/CREDITS.md b/partner_aging/readme/CREDITS.md new file mode 100644 index 00000000000..081bc339d3b --- /dev/null +++ b/partner_aging/readme/CREDITS.md @@ -0,0 +1,4 @@ +The development of this module has been financially supported by: + +- Open Source Integrators \<\> +- Serpent Consulting Services Pvt. Ltd. \<\> diff --git a/partner_aging/readme/DESCRIPTION.md b/partner_aging/readme/DESCRIPTION.md new file mode 100644 index 00000000000..834d192139a --- /dev/null +++ b/partner_aging/readme/DESCRIPTION.md @@ -0,0 +1,10 @@ +This module creates a new customer / supplier aging report as of a given +date. + +The default Aged Partner balance report is related to a specific date +and a static PDF that is based on the difference between credits and +debits, not based on documents such as Invoices/Bills and Payments. + +This module provides an interactive view with details of the invoice. + +This module does not consider unapplied credits. diff --git a/partner_aging/readme/USAGE.md b/partner_aging/readme/USAGE.md new file mode 100644 index 00000000000..95220d3358d --- /dev/null +++ b/partner_aging/readme/USAGE.md @@ -0,0 +1,5 @@ +To use this module, you need to: + +1. Go to Accounting +2. Click on Sales \> Customer Aging or Vendors \> Supplier Aging +3. Change the date if necessary diff --git a/partner_aging/security/ir.model.access.csv b/partner_aging/security/ir.model.access.csv new file mode 100644 index 00000000000..a6528788f8d --- /dev/null +++ b/partner_aging/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,group_id/id,name,model_id/id,perm_create,perm_read,perm_write,perm_unlink +res_partner_aging_customer,account.group_account_invoice,AR can READ,partner_aging.model_res_partner_aging_customer,0,1,0,0 +res_partner_aging_supplier,account.group_account_invoice,AP can READ,partner_aging.model_res_partner_aging_supplier,0,1,0,0 +res_partner_aging_date,base.group_user,Partner Aging Date,partner_aging.model_res_partner_aging_date,1,1,1,1 diff --git a/partner_aging/static/description/icon.png b/partner_aging/static/description/icon.png new file mode 100644 index 00000000000..7849454dcb9 Binary files /dev/null and b/partner_aging/static/description/icon.png differ diff --git a/partner_aging/static/description/index.html b/partner_aging/static/description/index.html new file mode 100644 index 00000000000..9d1debb2574 --- /dev/null +++ b/partner_aging/static/description/index.html @@ -0,0 +1,465 @@ + + + + + +README.rst + + + +
+ + + +Odoo Community Association + +
+

Interactive Partner Aging at any date

+ +

Production/Stable License: AGPL-3 OCA/account-payment Translate me on Weblate Try me on Runboat

+

This module creates a new customer / supplier aging report as of a given +date.

+

The default Aged Partner balance report is related to a specific date +and a static PDF that is based on the difference between credits and +debits, not based on documents such as Invoices/Bills and Payments.

+

This module provides an interactive view with details of the invoice.

+

This module does not consider unapplied credits.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to Accounting
  2. +
  3. Click on Sales > Customer Aging or Vendors > Supplier Aging
  4. +
  5. Change the date if necessary
  6. +
+
+
+

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

+
    +
  • Open Source Integrators
  • +
+
+ +
+

Other credits

+

The development of this module has been financially supported by:

+ +
+
+

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.

+

Current maintainer:

+

Urvisha-OSI

+

This module is part of the OCA/account-payment project on GitHub.

+

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

+
+
+
+
+ + diff --git a/partner_aging/tests/__init__.py b/partner_aging/tests/__init__.py new file mode 100644 index 00000000000..f1dd981e040 --- /dev/null +++ b/partner_aging/tests/__init__.py @@ -0,0 +1,4 @@ +# Copyright 2012 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import test_partner_aging diff --git a/partner_aging/tests/test_partner_aging.py b/partner_aging/tests/test_partner_aging.py new file mode 100644 index 00000000000..b37647b347f --- /dev/null +++ b/partner_aging/tests/test_partner_aging.py @@ -0,0 +1,186 @@ +# Copyright 2012 Open Source Integrators +# Copyright 2021 Tecnativa - Víctor Martínez +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime, timedelta + +from odoo import fields +from odoo.tests import Form, common +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon + + +@common.tagged("-at_install", "post_install") +class TestPartnerAging(AccountTestInvoicingCommon): + def setUp(self): + super().setUp() + self.partner_aging_date_model = self.env["res.partner.aging.date"] + self.partner_aging_supplier_model = self.env["res.partner.aging.supplier"] + self.partner_aging_customer_model = self.env["res.partner.aging.customer"] + self.current_date = fields.Date.today() + self.account_invoice_obj = self.env["account.move"] + self.account_move_line_obj = self.env["account.move.line"] + self.account_account_obj = self.env["account.account"] + self.account_journal_obj = self.env["account.journal"] + + self.partner_12 = self.env.ref("base.res_partner_12") + self.partner_2 = self.env.ref("base.res_partner_2") + self.partner_10 = self.env.ref("base.res_partner_10") + self.partner_3 = self.env.ref("base.res_partner_3") + self.partner_18 = self.env.ref("base.res_partner_18") + + self.journal_sale = self.account_journal_obj.create( + {"name": "sale_0", "code": "SALE0", "type": "sale"} + ) + self.journal_purchase = self.account_journal_obj.create( + {"name": "purchase_0", "code": "PRCHASE0", "type": "purchase"} + ) + + self.account_id = self.account_account_obj.create( + { + "code": "RA1000", + "name": "Test Receivable Account", + "account_type": "asset_receivable", + "reconcile": True, + } + ) + + self.income_account_id = self.account_account_obj.create( + { + "code": "RA1001", + "name": "Test Income Account", + "account_type": "income", + } + ) + + self.expense_account_id = self.account_account_obj.create( + { + "code": "RA1002", + "name": "Test Expense Account", + "account_type": "expense", + } + ) + + self.env.ref("product.product_product_4").categ_id.write( + { + "property_account_income_categ_id": self.income_account_id.id, + "property_account_expense_categ_id": self.expense_account_id.id, + } + ) + + invoice_data_list = [ + # Customer Invoice Data + [ + "out_invoice", + self.get_date(30), + self.partner_12.id, + self.journal_sale.id, + ], + ["out_invoice", self.get_date(60), self.partner_2.id, self.journal_sale.id], + [ + "out_invoice", + self.get_date(90), + self.partner_18.id, + self.journal_sale.id, + ], + [ + "out_invoice", + self.get_date(119), + self.partner_3.id, + self.journal_sale.id, + ], + [ + "out_invoice", + self.get_date(124), + self.partner_10.id, + self.journal_sale.id, + ], + # Supplier Invoice Data + [ + "in_invoice", + self.get_date(30), + self.partner_12.id, + self.journal_purchase.id, + ], + [ + "in_invoice", + self.get_date(60), + self.partner_2.id, + self.journal_purchase.id, + ], + [ + "in_invoice", + self.get_date(90), + self.partner_18.id, + self.journal_purchase.id, + ], + [ + "in_invoice", + self.get_date(119), + self.partner_3.id, + self.journal_purchase.id, + ], + [ + "in_invoice", + self.get_date(124), + self.partner_10.id, + self.journal_purchase.id, + ], + ] + for invoice in invoice_data_list: + self._create_invoice_with_reference(invoice) + + def _create_invoice_with_reference(self, invoice_data): + invoice = self.account_invoice_obj.with_context( + default_journal_id=invoice_data[3], test_no_refuse_ref=True + ).create( + { + "ref": "reference", + "move_type": invoice_data[0], + "invoice_date": invoice_data[1], + "invoice_date_due": invoice_data[1], + "partner_id": invoice_data[2], + } + ) + invoice_form = Form(invoice) + with invoice_form.invoice_line_ids.new() as line_form: + line_form.product_id = self.env.ref("product.product_product_4") + line_form.quantity = 1 + line_form.price_unit = 0 + line_form.name = "product that cost 100" + line_form.account_id = ( + invoice_data[0] == "out_invoice" + and self.income_account_id + or self.expense_account_id + ) + invoice = invoice_form.save() + invoice.action_post() + return invoice + + def get_date(self, set_days): + return (datetime.now() - timedelta(days=-set_days)).strftime( + DEFAULT_SERVER_DATE_FORMAT + ) + + def test_partner_aging_customer(self): + partner_aging_date = self.partner_aging_date_model.create( + {"age_date": self.current_date} + ) + res = partner_aging_date.open_customer_aging() + self.assertEqual(res["context"]["age_date"], self.current_date) + partner_aging_customer_rec = self.partner_aging_customer_model.search( + [("invoice_id", "!=", False)], limit=1 + ) + partner_aging_customer_rec.open_document() + + def test_partner_aging_supplier(self): + partner_aging_date = self.partner_aging_date_model.create( + {"age_date": self.current_date} + ) + res = partner_aging_date.open_supplier_aging() + self.assertEqual(res["context"]["age_date"], self.current_date) + partner_aging_supplier_rec = self.partner_aging_supplier_model.search( + [("invoice_id", "!=", False)], limit=1 + ) + partner_aging_supplier_rec.open_document() diff --git a/partner_aging/wizard/__init__.py b/partner_aging/wizard/__init__.py new file mode 100644 index 00000000000..4a316350cc7 --- /dev/null +++ b/partner_aging/wizard/__init__.py @@ -0,0 +1,7 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import ( + res_partner_aging_customer, + res_partner_aging_date, + res_partner_aging_supplier, +) diff --git a/partner_aging/wizard/res_partner_aging_customer.py b/partner_aging/wizard/res_partner_aging_customer.py new file mode 100644 index 00000000000..2b9c78e4172 --- /dev/null +++ b/partner_aging/wizard/res_partner_aging_customer.py @@ -0,0 +1,343 @@ +# Copyright 2022 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models, tools + + +class ResPartnerAgingCustomer(models.Model): + _name = "res.partner.aging.customer" + _description = "Res Partner Aging Customer" + _auto = False + _order = "partner_id" + + partner_id = fields.Many2one("res.partner", "Partner", readonly=True) + avg_days_overdue = fields.Integer(readonly=True) + date = fields.Date(readonly=True) + date_due = fields.Date("Due Date", readonly=True) + inv_date_due = fields.Date("Invoice Date", readonly=True) + total = fields.Float(readonly=True) + not_due = fields.Float("Not Due Yet", readonly=True) + days_due_01to30 = fields.Float("1/30", readonly=True) + days_due_31to60 = fields.Float("31/60", readonly=True) + days_due_61to90 = fields.Float("61/90", readonly=True) + days_due_91to120 = fields.Float("91/120", readonly=True) + days_due_121togr = fields.Float("+121", readonly=True) + max_days_overdue = fields.Integer("Days Outstanding", readonly=True) + invoice_ref = fields.Char("Our Invoice", size=25, readonly=True) + invoice_id = fields.Many2one("account.move", "Invoice", readonly=True) + salesman = fields.Many2one("res.users", "Sales Rep", readonly=True) + + def execute_aging_query(self, age_date=False): + if not age_date: + age_date = fields.Date.context_today(self) + + query = f""" + SELECT aml.id, aml.partner_id as partner_id, + ai.invoice_user_id as salesman, aml.date as date, aml.date as + date_due, ai.name as invoice_ref, + days_due AS avg_days_overdue, + CASE WHEN (days_due BETWEEN 1 and 30) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_01to30, + + CASE WHEN (days_due BETWEEN 31 and 60) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_31to60, + + CASE WHEN (days_due BETWEEN 61 and 90) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_61to90, + + CASE WHEN (days_due BETWEEN 91 and 120) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and + apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_91to120, + + CASE WHEN (days_due >= 121) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_121togr, + + CASE when days_due < 0 THEN 0 ELSE days_due END as + "max_days_overdue", + CASE WHEN (days_due < 1) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS not_due, + + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END AS total, + ai.id as invoice_id, + ai.invoice_date_due as inv_date_due + FROM account_move_line aml + LEFT JOIN account_account ac on ac.id = aml.account_id + INNER JOIN + (SELECT lt.id, + CASE WHEN inv.invoice_date_due is null then 0 + WHEN inv.id is not null THEN '{age_date}' - inv.invoice_date_due + ELSE current_date - lt.date END AS days_due + FROM account_move_line lt LEFT JOIN account_move inv + on lt.move_id = inv.id) DaysDue + ON DaysDue.id = aml.id + LEFT JOIN account_move as ai ON ai.id = aml.move_id + WHERE ac.account_type = 'asset_receivable' + and aml.date + <= '{age_date}' AND ai.state = 'posted' AND + (ai.payment_state != 'paid' OR + aml.full_reconcile_id IS NULL) AND + ai.move_type IN ('out_invoice', 'out_refund') + and ai.partner_id IS NOT NULL + GROUP BY aml.partner_id, + aml.id, ai.name, days_due, ai.invoice_user_id, ai.id UNION + SELECT aml.id, aml.partner_id as partner_id, + ai.invoice_user_id as + salesman, aml.date as date, aml.date as date_due, + ai.name as invoice_ref,days_due AS avg_days_overdue, + + CASE WHEN (days_due BETWEEN 1 and 30) THEN + + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_01to30, + + CASE WHEN (days_due BETWEEN 31 and 60) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) + and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_31to60, + + CASE WHEN (days_due BETWEEN 61 and 90) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_61to90, + + CASE WHEN (days_due BETWEEN 91 and 120) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or apr.debit_move_id=aml.id) + and apr.create_date <= '{age_date}')) WHEN (aml.full_reconcile_id + is NULL and aml.amount_residual>0) THEN + aml.debit-(select coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or apr.debit_move_id=aml.id) + and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_91to120, + + CASE WHEN (days_due >= 121) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from + account_partial_reconcile apr where + (apr.credit_move_id =aml.id or apr.debit_move_id=aml.id) + and apr.create_date <= '{age_date}')) WHEN (aml.full_reconcile_id + is NULL and aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS days_due_121togr, + + CASE when days_due < 0 THEN + 0 ELSE days_due END as "max_days_overdue", + + CASE WHEN (days_due < 1) THEN + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END ELSE 0 END AS not_due, + + CASE WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual<0) THEN -(aml.credit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}')) + WHEN (aml.full_reconcile_id is NULL and + aml.amount_residual>0) THEN aml.debit-(select + coalesce(sum(apr.amount),0) from account_partial_reconcile + apr where (apr.credit_move_id =aml.id or + apr.debit_move_id=aml.id) and apr.create_date <= '{age_date}') + WHEN (aml.full_reconcile_id is NOT NULL) THEN + aml.amount_residual END AS total, + ai.id as invoice_id, + ai.invoice_date_due as inv_date_due + FROM account_move_line aml + LEFT JOIN account_account ac on ac.id = aml.account_id + INNER JOIN + ( + SELECT lt.id, + CASE WHEN inv.invoice_date_due is null then 0 + WHEN inv.id is not null THEN '{age_date}' - inv.invoice_date_due + ELSE current_date - lt.date END AS days_due + FROM account_move_line lt LEFT JOIN account_move + inv on lt.move_id = inv.id + ) DaysDue + ON DaysDue.id = aml.id + LEFT JOIN account_move as ai ON ai.id = aml.move_id + WHERE ac.account_type = 'asset_receivable' + AND aml.date <= '{age_date}' + AND aml.partner_id IS NULL + AND ai.partner_id IS not NULL + AND aml.full_reconcile_id is NULL + GROUP BY aml.partner_id, aml.id, ai.name, days_due, + ai.invoice_user_id, ai.id UNION + select aml.id, + aml.partner_id as partner_id, + aml.create_uid as salesman, + aml.date as date, + aml.date as date_due, + ' ' as invoice_ref, + 0 as avg_days_overdue, + 0 as days_due_01to30, + 0 as days_due_31to60, + 0 as days_due_61to90, + 0 as days_due_91to120, + 0 as days_due_121togr, + 0 as max_days_overdue, + 0 as not_due, + CASE WHEN (aml.credit - (select sum(debit) + from account_move_line l where + l.full_reconcile_id = aml.full_reconcile_id and + l.date<='{age_date}')) > 0 then -(aml.credit - (select + sum(debit) from account_move_line l where + l.full_reconcile_id = aml.full_reconcile_id and + l.date<='{age_date}')) ELSE 0 END AS total, + null as invoice_id, aml.date as inv_date_due + from account_move_line aml + LEFT JOIN account_account ac on ac.id = aml.account_id + where aml.date <= '{age_date}' + and aml.partner_id IS NOT NULL + and aml.full_reconcile_id IS NOT NULL + and ac.account_type = 'asset_receivable' + and aml.credit > 0 + """ + + tools.drop_view_if_exists(self.env.cr, self._table) + # pylint: disable=sql-injection + q = f"""CREATE OR REPLACE VIEW {self._table} AS ({query})""" + self.env.cr.execute(q) + + def open_document(self): + """ + @description Open form view of Customer Invoice + """ + xmlid = "account.action_move_out_invoice_type" + action = self.env["ir.actions.act_window"]._for_xml_id(xmlid) + action["views"] = [(self.env.ref("account.view_move_form").id, "form")] + action["res_id"] = self.invoice_id.id + return action + + def init(self): + self.execute_aging_query() + return super().init() diff --git a/partner_aging/wizard/res_partner_aging_customer.xml b/partner_aging/wizard/res_partner_aging_customer.xml new file mode 100644 index 00000000000..160fd9fdaaa --- /dev/null +++ b/partner_aging/wizard/res_partner_aging_customer.xml @@ -0,0 +1,121 @@ + + + + + customer.aging.ad.search + res.partner.aging.customer + search + + + + + + + + + + + + + + + + + customer.aging.ad.list + res.partner.aging.customer + list + + + + + + + + +