diff --git a/account_cash_discount_write_off/README.rst b/account_cash_discount_write_off/README.rst new file mode 100644 index 000000000000..2e31b67a49eb --- /dev/null +++ b/account_cash_discount_write_off/README.rst @@ -0,0 +1,94 @@ +=============================== +Account Cash Discount Write Off +=============================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:3ae5bbdcc58337eb64bdf7024a5e2ab36e9e6f14bea534d1ee9fb55cc48dd2bd + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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 + :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/15.0/account_cash_discount_write_off + :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-15-0/account-payment-15-0-account_cash_discount_write_off + :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=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +Create an automatic write-off for payment with discount on the payment order +confirmation. If the cash discount amount is computed based on the total +amount, the created write-off will also contains tax adjustments. This +adjustments are computed based on the discount percent configured on the +related invoice. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +#. Create a supplier invoice +#. In the "Other Info" tab, you can configure a discount in percent and a discount delay in days +#. Validate the invoice +#. Create a payment order and add the previously created invoice using the search based on the cash discount due date +#. Validate your payment order till the last state +#. The invoice is now marked as "Paid". + +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 +~~~~~~~ + +* ACSONE SA/NV + +Contributors +~~~~~~~~~~~~ + +* Benjamin Willig +* Christelle De Coninck (ACSONE) +* Helly kapatel + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/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_cash_discount_write_off/__init__.py b/account_cash_discount_write_off/__init__.py new file mode 100644 index 000000000000..0650744f6bc6 --- /dev/null +++ b/account_cash_discount_write_off/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_cash_discount_write_off/__manifest__.py b/account_cash_discount_write_off/__manifest__.py new file mode 100644 index 000000000000..9f9e7d7d81e9 --- /dev/null +++ b/account_cash_discount_write_off/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Account Cash Discount Write Off", + "summary": """ + Create an automatic writeoff for payment with discount on the payment + order confirmation""", + "version": "15.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/account-payment", + "depends": ["account_cash_discount_payment"], + "data": ["views/res_company.xml"], + "demo": [], +} diff --git a/account_cash_discount_write_off/i18n/account_cash_discount_write_off.pot b/account_cash_discount_write_off/i18n/account_cash_discount_write_off.pot new file mode 100644 index 000000000000..49f2e174587c --- /dev/null +++ b/account_cash_discount_write_off/i18n/account_cash_discount_write_off.pot @@ -0,0 +1,84 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_cash_discount_write_off +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.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_cash_discount_write_off +#: code:addons/account_cash_discount_write_off/models/account_payment_line.py:0 +#, python-format +msgid "Cash Discount Write-Off" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company__default_cash_discount_writeoff_account_id +msgid "Cash Discount Write-Off Account" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company__default_cash_discount_writeoff_journal_id +msgid "Cash Discount Write-Off Journal" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model,name:account_cash_discount_write_off.model_res_company +msgid "Companies" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_account_payment_line__display_name +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_account_payment_order__display_name +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company__display_name +msgid "Display Name" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_account_payment_line__id +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_account_payment_order__id +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company__id +msgid "ID" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_account_payment_line____last_update +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_account_payment_order____last_update +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model,name:account_cash_discount_write_off.model_account_payment_line +msgid "Payment Lines" +msgstr "" + +#. module: account_cash_discount_write_off +#: model:ir.model,name:account_cash_discount_write_off.model_account_payment_order +msgid "Payment Order" +msgstr "" + +#. module: account_cash_discount_write_off +#: model_terms:ir.ui.view,arch_db:account_cash_discount_write_off.res_company_form_view +msgid "Write-Off Account" +msgstr "" + +#. module: account_cash_discount_write_off +#: model_terms:ir.ui.view,arch_db:account_cash_discount_write_off.res_company_form_view +msgid "Write-Off Journal" +msgstr "" + +#. module: account_cash_discount_write_off +#: code:addons/account_cash_discount_write_off/models/account_payment_line.py:0 +#, python-format +msgid "" +"You have to fill in journal and account for cash discount write-off on the " +"company." +msgstr "" diff --git a/account_cash_discount_write_off/i18n/fr.po b/account_cash_discount_write_off/i18n/fr.po new file mode 100644 index 000000000000..92ea0e4bb25d --- /dev/null +++ b/account_cash_discount_write_off/i18n/fr.po @@ -0,0 +1,63 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_cash_discount_write_off +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.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_cash_discount_write_off +#: code:addons/account_cash_discount_write_off/models/account_payment_line.py:71 +#, python-format +msgid "Cash Discount Write-Off" +msgstr "Escompte (écart)" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company_default_cash_discount_writeoff_account_id +msgid "Cash Discount Write-Off Account" +msgstr "Compte d'écart de l'escompte" + +#. module: account_cash_discount_write_off +#: model:ir.model.fields,field_description:account_cash_discount_write_off.field_res_company_default_cash_discount_writeoff_journal_id +msgid "Cash Discount Write-Off Journal" +msgstr "Journal d'écart de l'escompte" + +#. module: account_cash_discount_write_off +#: model:ir.model,name:account_cash_discount_write_off.model_res_company +msgid "Companies" +msgstr "Sociétés" + +#. module: account_cash_discount_write_off +#: model:ir.model,name:account_cash_discount_write_off.model_account_payment_line +msgid "Payment Lines" +msgstr "Lignes de paiement" + +#. module: account_cash_discount_write_off +#: model:ir.model,name:account_cash_discount_write_off.model_account_payment_order +msgid "Payment Order" +msgstr "Ordre de paiement" + +#. module: account_cash_discount_write_off +#: model_terms:ir.ui.view,arch_db:account_cash_discount_write_off.res_company_form_view +msgid "Write-Off Account" +msgstr "Compte d'écart" + +#. module: account_cash_discount_write_off +#: model_terms:ir.ui.view,arch_db:account_cash_discount_write_off.res_company_form_view +msgid "Write-Off Journal" +msgstr "Journal d'écart" + +#. module: account_cash_discount_write_off +#: code:addons/account_cash_discount_write_off/models/account_payment_line.py:67 +#, python-format +msgid "You have to fill in journal and account for cash discount write-off on the company." +msgstr "" +"Vous devez remplir un journal et un compte d'écart pour les escomptes sur la " +"société." diff --git a/account_cash_discount_write_off/models/__init__.py b/account_cash_discount_write_off/models/__init__.py new file mode 100644 index 000000000000..f07ea21575c1 --- /dev/null +++ b/account_cash_discount_write_off/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_payment_line +from . import account_payment_order +from . import res_company diff --git a/account_cash_discount_write_off/models/account_payment_line.py b/account_cash_discount_write_off/models/account_payment_line.py new file mode 100644 index 000000000000..36a2cd1ec7ab --- /dev/null +++ b/account_cash_discount_write_off/models/account_payment_line.py @@ -0,0 +1,154 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, models +from odoo.exceptions import UserError +from odoo.tools import float_compare, float_is_zero, float_round + + +class PaymentLine(models.Model): + + _inherit = "account.payment.line" + + def _check_cash_discount_write_off_creation(self): + self.ensure_one() + create_write_off = ( + self.pay_with_discount + and self.move_line_id + and self.move_line_id.move_id + and self.move_line_id.move_id.has_discount + ) + if not create_write_off: + return False + + invoice = self.move_line_id.move_id + refunds_amount_total = invoice._get_refunds_amount_total()["total"] + + # Invoice residual has already changed so we need to compute + # the residual with discount before the first reconciliation + amount_to_pay = ( + invoice.amount_total + - refunds_amount_total + - invoice.discount_amount + + invoice.refunds_discount_amount + ) + return ( + float_compare( + self.amount_currency, + amount_to_pay, + precision_rounding=self.currency_id.rounding, + ) + == 0 + ) + + def get_cash_discount_writeoff_move_values(self): + self.ensure_one() + move_line = self.move_line_id + partner = move_line.partner_id + invoice = move_line.move_id + company = invoice.company_id + tax_adjustment = company.cash_discount_use_tax_adjustment + rounding = self.currency_id.rounding + + woff_account = False + woff_journal = False + + if invoice: + company = invoice.company_id + woff_account = company.default_cash_discount_writeoff_account_id + woff_journal = company.default_cash_discount_writeoff_journal_id + + if not woff_account or not woff_journal: + raise UserError( + _( + "You have to fill in journal and account for cash discount " + "write-off on the company." + ) + ) + + move_line_name = _("Cash Discount Write-Off") + supplier_account_amount = ( + invoice.discount_amount - invoice.refunds_discount_amount + ) + discount_amount_credit = supplier_account_amount + + lines_values = [ + { + "partner_id": partner.id, + "name": move_line_name, + "debit": supplier_account_amount, + "account_id": move_line.account_id.id, + } + ] + + if tax_adjustment: + refund_moves = ( + invoice._get_payment_move_lines() + .filtered(lambda line: line.move_id.move_type == "in_refund") + .mapped("move_id") + ) + target_move_ids = refund_moves.ids + [move_line.move_id.id] + + tax_move_lines = self.env["account.move.line"].search( + [ + ("move_id", "in", target_move_ids), + "|", + ("tax_line_id", "!=", False), + ("tax_ids", "!=", False), + ] + ) + + for tax_move_line in tax_move_lines: + tax_invoice = tax_move_line.move_id + amount = float_round( + abs(tax_move_line.balance) * tax_invoice.discount_percent / 100.0, + precision_rounding=rounding, + ) + if tax_move_line.credit > 0: + discount_amount_credit += amount + elif tax_move_line.debit > 0: + discount_amount_credit -= amount + + if tax_move_line.tax_line_id: + account = tax_move_line.account_id + else: + account = woff_account + lines_values.append( + { + "partner_id": partner.id, + "name": move_line_name, + "debit": tax_move_line.credit > 0 and amount or 0.0, + "credit": tax_move_line.debit > 0 and amount or 0.0, + "account_id": account.id, + "tax_repartition_line_id": ( + tax_move_line.tax_repartition_line_id.id + ), + "tax_ids": [(6, 0, tax_move_line.tax_ids.ids)], + "tax_tag_ids": [(6, 0, tax_move_line.tax_tag_ids.ids)], + } + ) + + amount_left = not float_is_zero( + discount_amount_credit, precision_rounding=rounding + ) + if amount_left: + writeoff_amount = float_round( + abs(discount_amount_credit), + precision_rounding=rounding, + ) + lines_values.append( + { + "partner_id": partner.id, + "name": move_line_name, + "credit": (writeoff_amount if discount_amount_credit > 0 else 0.0), + "debit": (writeoff_amount if discount_amount_credit < 0 else 0.0), + "account_id": woff_account.id, + } + ) + + move_values = { + "journal_id": woff_journal.id, + "partner_id": partner.id, + "line_ids": [(0, 0, values) for values in lines_values], + } + return move_values diff --git a/account_cash_discount_write_off/models/account_payment_order.py b/account_cash_discount_write_off/models/account_payment_order.py new file mode 100644 index 000000000000..ddc4dfe35c88 --- /dev/null +++ b/account_cash_discount_write_off/models/account_payment_order.py @@ -0,0 +1,41 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountPaymentOrder(models.Model): + + _inherit = "account.payment.order" + + def generated2uploaded(self): + # Generate the discount first + for order in self: + order._create_cash_discount_write_off() + + res = super().generated2uploaded() + return res + + def _create_cash_discount_write_off(self): + self.ensure_one() + for payment_line in self.payment_line_ids: + if payment_line._check_cash_discount_write_off_creation(): + self._create_payment_line_discount_write_off(payment_line) + + def _create_payment_line_discount_write_off(self, payment_line): + self.ensure_one() + move = self._create_payment_line_discount_write_off_move(payment_line) + move_lines = payment_line.move_line_id | move.line_ids + lines_to_reconcile = move_lines.filtered( + lambda line: not line.reconciled + and line.account_id == payment_line.move_line_id.account_id + ) + lines_to_reconcile.reconcile() + + def _create_payment_line_discount_write_off_move(self, payment_line): + self.ensure_one() + move_values = payment_line.get_cash_discount_writeoff_move_values() + move = self.env["account.move"].create(move_values) + if self.payment_mode_id.post_move: + move.action_post() + return move diff --git a/account_cash_discount_write_off/models/res_company.py b/account_cash_discount_write_off/models/res_company.py new file mode 100644 index 000000000000..8ca566c03a6a --- /dev/null +++ b/account_cash_discount_write_off/models/res_company.py @@ -0,0 +1,16 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + + _inherit = "res.company" + + default_cash_discount_writeoff_account_id = fields.Many2one( + comodel_name="account.account", string="Cash Discount Write-Off Account" + ) + default_cash_discount_writeoff_journal_id = fields.Many2one( + comodel_name="account.journal", string="Cash Discount Write-Off Journal" + ) diff --git a/account_cash_discount_write_off/readme/CONFIGURATION.rst b/account_cash_discount_write_off/readme/CONFIGURATION.rst new file mode 100644 index 000000000000..4b36a637786b --- /dev/null +++ b/account_cash_discount_write_off/readme/CONFIGURATION.rst @@ -0,0 +1,5 @@ +To configure this module, you need to: + +#. Go to your companies +#. Set the cash discount write off account +#. Set the cash discount write off journal diff --git a/account_cash_discount_write_off/readme/CONTRIBUTORS.rst b/account_cash_discount_write_off/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..804addf92882 --- /dev/null +++ b/account_cash_discount_write_off/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Benjamin Willig +* Christelle De Coninck (ACSONE) +* Helly kapatel diff --git a/account_cash_discount_write_off/readme/DESCRIPTION.rst b/account_cash_discount_write_off/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..9ea67509095d --- /dev/null +++ b/account_cash_discount_write_off/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +Create an automatic write-off for payment with discount on the payment order +confirmation. If the cash discount amount is computed based on the total +amount, the created write-off will also contains tax adjustments. This +adjustments are computed based on the discount percent configured on the +related invoice. diff --git a/account_cash_discount_write_off/readme/USAGE.rst b/account_cash_discount_write_off/readme/USAGE.rst new file mode 100644 index 000000000000..902c3d3d1b01 --- /dev/null +++ b/account_cash_discount_write_off/readme/USAGE.rst @@ -0,0 +1,8 @@ +To use this module, you need to: + +#. Create a supplier invoice +#. In the "Other Info" tab, you can configure a discount in percent and a discount delay in days +#. Validate the invoice +#. Create a payment order and add the previously created invoice using the search based on the cash discount due date +#. Validate your payment order till the last state +#. The invoice is now marked as "Paid". diff --git a/account_cash_discount_write_off/static/description/icon.png b/account_cash_discount_write_off/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/account_cash_discount_write_off/static/description/icon.png differ diff --git a/account_cash_discount_write_off/static/description/index.html b/account_cash_discount_write_off/static/description/index.html new file mode 100644 index 000000000000..d37a84ebd46f --- /dev/null +++ b/account_cash_discount_write_off/static/description/index.html @@ -0,0 +1,442 @@ + + + + + +Account Cash Discount Write Off + + + +
+

Account Cash Discount Write Off

+ + +

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

+

Create an automatic write-off for payment with discount on the payment order +confirmation. If the cash discount amount is computed based on the total +amount, the created write-off will also contains tax adjustments. This +adjustments are computed based on the discount percent configured on the +related invoice.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. Create a supplier invoice
  2. +
  3. In the “Other Info” tab, you can configure a discount in percent and a discount delay in days
  4. +
  5. Validate the invoice
  6. +
  7. Create a payment order and add the previously created invoice using the search based on the cash discount due date
  8. +
  9. Validate your payment order till the last state
  10. +
  11. The invoice is now marked as “Paid”.
  12. +
+
+
+

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

+
    +
  • ACSONE SA/NV
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+ +Odoo Community Association + +

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

+

This module is part of the OCA/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_cash_discount_write_off/tests/__init__.py b/account_cash_discount_write_off/tests/__init__.py new file mode 100644 index 000000000000..1946b7036593 --- /dev/null +++ b/account_cash_discount_write_off/tests/__init__.py @@ -0,0 +1 @@ +from . import test_account_cash_discount_write_off diff --git a/account_cash_discount_write_off/tests/test_account_cash_discount_write_off.py b/account_cash_discount_write_off/tests/test_account_cash_discount_write_off.py new file mode 100644 index 000000000000..8e0e54d3f1ad --- /dev/null +++ b/account_cash_discount_write_off/tests/test_account_cash_discount_write_off.py @@ -0,0 +1,264 @@ +# Copyright 2018 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo.exceptions import UserError +from odoo.fields import Date +from odoo.tests.common import Form + +from odoo.addons.account_cash_discount_payment.tests.common import ( + TestAccountCashDiscountPaymentCommon, +) + + +class TestAccountCashDiscountWriteOff(TestAccountCashDiscountPaymentCommon): + @classmethod + def setUpClass(cls): + super(TestAccountCashDiscountWriteOff, cls).setUpClass() + cls.cash_discount_writeoff_account = cls.Account.create( + { + "name": "Cash Discount Write-Off", + "code": "CD-W", + "user_type_id": cls.inc_account_type.id, + } + ) + + cls.cash_discount_writeoff_journal = cls.Journal.create( + {"name": "Cash Discount Write-Off", "code": "CD-W", "type": "general"} + ) + + def test_cash_discount_with_write_off(self): + payment_mode = self.payment_mode_out + payment_mode.post_move = True + discount_due_date = Date.today() + + invoice = self.create_supplier_invoice( + discount_due_date, payment_mode, 1000, 10, [] + ) + invoice.action_post() + + payment_order = self.PaymentOrder.create( + { + "payment_mode_id": payment_mode.id, + "payment_type": "outbound", + "journal_id": self.bank_ing_journal.id, + } + ) + + payment_line_wizard = self.PaymentLineCreate.with_context( + active_model=payment_order._name, + active_id=payment_order.id, + ).create( + { + "cash_discount_date": discount_due_date, + "date_type": "discount_due_date", + "journal_ids": [(6, 0, [self.purchase_journal.id])], + } + ) + + payment_line_wizard.populate() + payment_line_wizard.create_payment_lines() + + payment_order.draft2open() + + payment_line = payment_order.payment_line_ids[0] + self.assertTrue(payment_line.pay_with_discount) + self.assertTrue(payment_line._check_cash_discount_write_off_creation()) + old_amount = payment_line.amount_currency + payment_line.amount_currency = 10 + self.assertFalse(payment_line._check_cash_discount_write_off_creation()) + payment_line.amount_currency = old_amount + + payment_order.open2generated() + + with self.assertRaises(UserError), self.env.cr.savepoint(): + payment_order.generated2uploaded() + + woff_account = self.cash_discount_writeoff_account + woff_journal = self.cash_discount_writeoff_journal + self.company.write( + { + "default_cash_discount_writeoff_account_id": woff_account.id, + "default_cash_discount_writeoff_journal_id": woff_journal.id, + "cash_discount_base_amount_type": "total", + } + ) + + payment_order.generated2uploaded() + + payment_move_lines = invoice._get_payment_move_lines() + write_off_line = self.MoveLine.search( + [ + ("id", "in", payment_move_lines.ids), + ("name", "=", "Cash Discount Write-Off"), + ] + ) + + self.assertEqual(len(write_off_line), 1) + self.assertEqual(write_off_line.debit, 100) + + write_off_base_line = self.MoveLine.search( + [ + ("id", "!=", write_off_line.id), + ("move_id", "=", write_off_line.move_id.id), + ] + ) + self.assertEqual(len(write_off_base_line), 1) + self.assertEqual( + write_off_base_line.account_id, self.cash_discount_writeoff_account + ) + self.assertEqual(invoice.payment_state, "paid") + + def test_cash_discount_with_write_off_with_taxes(self): + woff_account = self.cash_discount_writeoff_account + woff_journal = self.cash_discount_writeoff_journal + self.company.write( + { + "default_cash_discount_writeoff_account_id": woff_account.id, + "default_cash_discount_writeoff_journal_id": woff_journal.id, + "cash_discount_base_amount_type": "total", + } + ) + + self.assertEqual(self.company.cash_discount_base_amount_type, "total") + + payment_mode = self.payment_mode_out + payment_mode.post_move = True + discount_due_date = Date.today() + + invoice = self.create_supplier_invoice( + discount_due_date, payment_mode, 1000, 10, [self.tax_10_p, self.tax_15_p] + ) + invoice.action_post() + + payment_order = self.PaymentOrder.create( + { + "payment_mode_id": payment_mode.id, + "payment_type": "outbound", + "journal_id": self.bank_ing_journal.id, + } + ) + + payment_line_wizard = self.PaymentLineCreate.with_context( + active_model=payment_order._name, + active_id=payment_order.id, + ).create( + { + "cash_discount_date": discount_due_date, + "date_type": "discount_due_date", + "journal_ids": [(6, 0, [self.purchase_journal.id])], + } + ) + + payment_line_wizard.populate() + payment_line_wizard.create_payment_lines() + + payment_order.draft2open() + payment_order.open2generated() + payment_order.generated2uploaded() + + self.assertEqual(invoice.payment_state, "paid") + + discount_writeoff_move_lines = self.MoveLine.search( + [("journal_id", "=", self.cash_discount_writeoff_journal.id)] + ) + self.assertEqual(len(discount_writeoff_move_lines), 4) + + tax_10_move_line = self.MoveLine.search( + [ + ("id", "in", discount_writeoff_move_lines.ids), + ("tax_line_id", "=", self.tax_10_p.id), + ] + ) + self.assertEqual(len(tax_10_move_line), 1) + self.assertEqual(tax_10_move_line.credit, 10) + + tax_15_move_line = self.MoveLine.search( + [ + ("id", "in", discount_writeoff_move_lines.ids), + ("tax_line_id", "=", self.tax_15_p.id), + ] + ) + self.assertEqual(len(tax_15_move_line), 1) + self.assertEqual(tax_15_move_line.credit, 15) + + def test_cash_discount_with_refund(self): + woff_account = self.cash_discount_writeoff_account + woff_journal = self.cash_discount_writeoff_journal + self.company.write( + { + "default_cash_discount_writeoff_account_id": woff_account.id, + "default_cash_discount_writeoff_journal_id": woff_journal.id, + "cash_discount_base_amount_type": "total", + } + ) + + payment_mode = self.payment_mode_out + payment_mode.post_move = True + discount_due_date = Date.today() + + invoice = self.create_supplier_invoice( + discount_due_date, payment_mode, 100, 2, [self.tax_17_p] + ) + invoice.action_post() + self.assertAlmostEqual(invoice.amount_residual, 117) + self.assertAlmostEqual(invoice.residual_with_discount, 114.66) + + move_reversal = self.AccountMoveReversal.with_context( + active_model=invoice._name, active_ids=invoice.ids + ).create( + { + "reason": "no reason", + "refund_method": "refund", + "journal_id": self.purchase_journal.id, + } + ) + reversal = move_reversal.reverse_moves() + + refund = self.env["account.move"].browse(reversal["res_id"]) + + refund_form = Form(refund) + with refund_form.invoice_line_ids.edit(0) as refund_line: + refund_line.price_unit = 10 + refund_form.save() + + refund.write({"discount_due_date": discount_due_date, "discount_percent": 2}) + refund.action_post() + credit_aml_id = self.AccountMoveLine.search( + [("move_id", "=", refund.id), ("debit", ">", 0)], limit=1 + ) + + invoice.js_assign_outstanding_line(credit_aml_id.id) + + self.assertAlmostEqual(invoice.amount_residual, 105.3) + self.assertAlmostEqual(invoice.residual_with_discount, 103.19) + + payment_order = self.PaymentOrder.create( + { + "payment_mode_id": payment_mode.id, + "payment_type": "outbound", + "journal_id": self.bank_ing_journal.id, + } + ) + + payment_line_wizard = self.PaymentLineCreate.with_context( + active_model=payment_order._name, + active_id=payment_order.id, + ).create( + { + "cash_discount_date": discount_due_date, + "date_type": "discount_due_date", + "journal_ids": [(6, 0, [self.purchase_journal.id])], + } + ) + + payment_line_wizard.populate() + payment_line_wizard.create_payment_lines() + + payment_line = payment_order.payment_line_ids[0] + self.assertAlmostEqual(payment_line.amount_currency, 103.19) + + payment_order.draft2open() + payment_order.open2generated() + payment_order.generated2uploaded() + + self.assertEqual(invoice.payment_state, "paid") diff --git a/account_cash_discount_write_off/views/res_company.xml b/account_cash_discount_write_off/views/res_company.xml new file mode 100644 index 000000000000..e2d1bb5338a9 --- /dev/null +++ b/account_cash_discount_write_off/views/res_company.xml @@ -0,0 +1,25 @@ + + + + + res.company.form (in account_cash_discount_write_off) + res.company + + + + + + + + + diff --git a/setup/account_cash_discount_write_off/odoo/addons/account_cash_discount_write_off b/setup/account_cash_discount_write_off/odoo/addons/account_cash_discount_write_off new file mode 120000 index 000000000000..ab9c3988885f --- /dev/null +++ b/setup/account_cash_discount_write_off/odoo/addons/account_cash_discount_write_off @@ -0,0 +1 @@ +../../../../account_cash_discount_write_off \ No newline at end of file diff --git a/setup/account_cash_discount_write_off/setup.py b/setup/account_cash_discount_write_off/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/account_cash_discount_write_off/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000000..e07d19c3adbc --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +odoo-addon-account_cash_discount_base @ git+https://github.com/OCA/account-payment.git@refs/pull/597/head#subdirectory=setup/account_cash_discount_base +odoo-addon-account_cash_discount_payment @ git+https://github.com/OCA/account-payment.git@refs/pull/761/head#subdirectory=setup/account_cash_discount_payment