diff --git a/account_banking_ach_discount/README.rst b/account_banking_ach_discount/README.rst new file mode 100644 index 00000000..a63e6077 --- /dev/null +++ b/account_banking_ach_discount/README.rst @@ -0,0 +1,130 @@ +============================== +Discount on ACH batch payments +============================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fl10n--usa-lightgray.png?logo=github + :target: https://github.com/OCA/l10n-usa/tree/14.0/account_banking_ach_discount + :alt: OCA/l10n-usa +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/l10n-usa-14-0/l10n-usa-14-0-account_banking_ach_discount + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/203/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module will add support for discount in ACH and batch ACH workflow. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +Payment Terms +~~~~~~~~~~~~~ + +* Go to *Accounting > Configuration > Payment Terms* +* Create or select a payment term +* Activate the discounts options +* On a line, set the discount percentage and number of days + +Payment Modes +~~~~~~~~~~~~~ + +* Go to *Accounting > Configuration > Payment Modes* +* Create or select a payment mode +* Link it to an ACH payment method + +Vendors +~~~~~~~ + +* Go *Contacts* or *Accounting > Vendors > Vendors* +* Create or select a vendor +* On the Sales and Purchase tab, set the supplier payment mode +* On the Accounting tab, set their bank information (account number, bank, routing number) + +Usage +===== + +* Go to *Accounting > Customers > Invoices* or *Accounting > Vendors > Bills* +* Select or create various records in the state posted with ACH and discounts +* In the Action menu, click on Batch Payments +* Review the payment information provided by default +* Click on Make Payments +* Review the payment order, confirm it and generate the ACH file +* Go to your bank's website to upload the file +* Come back to Odoo and confirm the upload to the bank was successful + +Changelog +========= + +12.0.1.0.0 +~~~~~~~~~~ + +- ACH Payment, Automatic Discount and Batch Payment Partial Pay Integration + +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 smashing 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 +~~~~~~~~~~~~ + +* Open Source Integrators + + * Bhavesh Odedra + * Maxime Chambreuil + +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-bodedra| image:: https://github.com/bodedra.png?size=40px + :target: https://github.com/bodedra + :alt: bodedra + +Current `maintainer `__: + +|maintainer-bodedra| + +This module is part of the `OCA/l10n-usa `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_banking_ach_discount/__init__.py b/account_banking_ach_discount/__init__.py new file mode 100644 index 00000000..a5b63e4a --- /dev/null +++ b/account_banking_ach_discount/__init__.py @@ -0,0 +1,5 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import models +from . import wizard diff --git a/account_banking_ach_discount/__manifest__.py b/account_banking_ach_discount/__manifest__.py new file mode 100644 index 00000000..4de311b3 --- /dev/null +++ b/account_banking_ach_discount/__manifest__.py @@ -0,0 +1,24 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Discount on ACH batch payments", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "Open Source Integrators, Odoo Community Association (OCA)", + "category": "Accounting", + "maintainer": "Odoo Community Association (OCA)", + "website": "https://github.com/OCA/l10n-usa", + "development_status": "Beta", + "maintainers": ["bodedra"], + "depends": [ + "account_payment_term_discount", + "account_payment_batch_process", + "account_payment_order", + "account_banking_ach_credit_transfer", + "account_banking_ach_direct_debit", + ], + "data": [ + "views/account_payment_view.xml", + ], +} diff --git a/account_banking_ach_discount/i18n/account_banking_ach_discount.pot b/account_banking_ach_discount/i18n/account_banking_ach_discount.pot new file mode 100644 index 00000000..7eca063e --- /dev/null +++ b/account_banking_ach_discount/i18n/account_banking_ach_discount.pot @@ -0,0 +1,148 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_banking_ach_discount +# +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_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__writeoff_account_id +msgid "Account" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__payment_difference_handling +msgid "Action" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_bank_payment_line +msgid "Bank Payment Lines" +msgstr "" + +#. module: account_banking_ach_discount +#: model_terms:ir.ui.view,arch_db:account_banking_ach_discount.account_payment_line_discount_amount_form +#: model_terms:ir.ui.view,arch_db:account_banking_ach_discount.account_payment_line_discount_amount_tree +msgid "Discount Account" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__discount_amount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_bank_payment_line__discount_amount +msgid "Discount Amount" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_move__display_name +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_move_line__display_name +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment__display_name +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__display_name +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_order__display_name +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_register__display_name +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_bank_payment_line__display_name +msgid "Display Name" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_move__id +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_move_line__id +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment__id +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__id +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_order__id +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_register__id +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_bank_payment_line__id +msgid "ID" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_account_move +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__move_id +msgid "Journal Entry" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_account_move_line +msgid "Journal Item" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields.selection,name:account_banking_ach_discount.selection__account_payment_line__payment_difference_handling__open +msgid "Keep open" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_move____last_update +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_move_line____last_update +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment____last_update +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line____last_update +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_order____last_update +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_register____last_update +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_bank_payment_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields.selection,name:account_banking_ach_discount.selection__account_payment_line__payment_difference_handling__reconcile +msgid "Mark invoice as fully paid" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__note +msgid "Note" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__payment_difference +msgid "Payment Difference" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_account_payment_line +msgid "Payment Lines" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_account_payment_order +msgid "Payment Order" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_account_payment +msgid "Payments" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__reason_code +msgid "Reason Code" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model,name:account_banking_ach_discount.model_account_payment_register +msgid "Register Payment" +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,help:account_banking_ach_discount.field_account_payment_line__move_id +msgid "The move of this entry line." +msgstr "" + +#. module: account_banking_ach_discount +#: code:addons/account_banking_ach_discount/models/account_payment.py:0 +#, python-format +msgid "" +"This method should only be called to process a single invoice's payment." +msgstr "" + +#. module: account_banking_ach_discount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_account_payment_line__total_amount +#: model:ir.model.fields,field_description:account_banking_ach_discount.field_bank_payment_line__total_amount +msgid "Total Amount" +msgstr "" diff --git a/account_banking_ach_discount/models/__init__.py b/account_banking_ach_discount/models/__init__.py new file mode 100644 index 00000000..e4adbac1 --- /dev/null +++ b/account_banking_ach_discount/models/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import account_payment +from . import account_move_line +from . import bank_payment_line +from . import account_payment_order +from . import account_move diff --git a/account_banking_ach_discount/models/account_move.py b/account_banking_ach_discount/models/account_move.py new file mode 100644 index 00000000..bdd4cecb --- /dev/null +++ b/account_banking_ach_discount/models/account_move.py @@ -0,0 +1,104 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models + + +class AccountMove(models.Model): + _inherit = "account.move" + + def _get_reconciled_info_JSON_values(self): + res = super(AccountMove, self)._get_reconciled_info_JSON_values() + inv_number = self.ref + if res: + flag = False + for item in res: + payment_lines = set() + for line in self.line_ids: + payment_lines.update( + line.mapped("matched_credit_ids.credit_move_id.id") + ) + payment_lines.update( + line.mapped("matched_debit_ids.debit_move_id.id") + ) + payment_move_line_ids = ( + self.env["account.move.line"] + .browse(list(payment_lines)) + .sorted() + ) + + for mvl in payment_move_line_ids: + # get bank payment line + if mvl.move_id and mvl.id == item["payment_id"]: + for pay_li in mvl.bank_payment_line_id.payment_line_ids: + # Get related payment line ref + if pay_li.communication == inv_number: + item["amount"] = pay_li.amount_currency + # for non-ach payment + # Deduct the discount only for the related payment. + # Discount is applied on the last payment (i.e. fully reconciled). + if ( + not mvl.bank_payment_line_id + and mvl.move_id.id == self.id + and item["account_payment_id"] == mvl.payment_id.id + ): + if mvl.full_reconcile_id and not flag: + item["amount"] = item["amount"] - self.discount_taken + flag = True + + return res + + def _prepare_discount_move_line(self, vals): + valid = False + for invoice in self: + if ( + invoice.invoice_payment_term_id + and invoice.invoice_payment_term_id.is_discount + and invoice.invoice_payment_term_id.line_ids + ): + discount_information = ( + invoice.invoice_payment_term_id._check_payment_term_discount( + invoice, invoice.date_invoice + ) + ) + discount_amt = discount_information[0] + discount_account_id = discount_information[1] + if discount_amt > 0.0: + vals.update( + { + "account_id": discount_account_id, + "move_id": invoice.id, + "bank_payment_line_id": False, + "name": "Early Pay Discount", + } + ) + if invoice.type == "out_invoice": + vals.update({"credit": 0.0, "debit": discount_amt}) + valid = True + elif invoice.type == "in_invoice": + vals.update({"credit": discount_amt, "debit": 0.0}) + valid = True + if valid: + return vals + else: + return {} + + def _prepare_writeoff_move_line(self, payment_line, vals): + for invoice in self: + note = "" + if payment_line.reason_code: + note = payment_line.reason_code.display_name + ": " + if payment_line.note: + note += payment_line.note + vals.update( + { + "account_id": payment_line.writeoff_account_id.id, + "bank_payment_line_id": False, + "name": note, + "move_id": invoice.id, + } + ) + if invoice.move_type == "out_invoice": + vals.update({"credit": 0.0, "debit": payment_line.payment_difference}) + elif invoice.move_type == "in_invoice": + vals.update({"credit": payment_line.payment_difference, "debit": 0.0}) + return vals diff --git a/account_banking_ach_discount/models/account_move_line.py b/account_banking_ach_discount/models/account_move_line.py new file mode 100644 index 00000000..5d1b5f87 --- /dev/null +++ b/account_banking_ach_discount/models/account_move_line.py @@ -0,0 +1,43 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models + + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + def _prepare_payment_line_vals(self, payment_order): + vals = super(AccountMoveLine, self)._prepare_payment_line_vals(payment_order) + invoice = self.move_id + amount_currency = vals.get("amount_currency") + # No discount for open invoices + if ( + ( + "payment_line_state" in self._context + and self._context.get("payment_line_state") != "open" + ) + or self._context.get("is_new_order") + or self._context.get("is_update_order") + ): + if ( + invoice + and invoice.invoice_payment_term_id + and invoice.invoice_payment_term_id.is_discount + and invoice.invoice_payment_term_id.line_ids + ): + discount_information = ( + invoice.invoice_payment_term_id._check_payment_term_discount( + invoice, + self._context.get("payment_date") or invoice.invoice_date, + ) + ) + discount_amt = discount_information[0] + vals.update( + { + "discount_amount": discount_amt, + "amount_currency": amount_currency - discount_amt, + "writeoff_account_id": discount_information[1], + } + ) + return vals diff --git a/account_banking_ach_discount/models/account_payment.py b/account_banking_ach_discount/models/account_payment.py new file mode 100644 index 00000000..4a47850b --- /dev/null +++ b/account_banking_ach_discount/models/account_payment.py @@ -0,0 +1,98 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class AccountPayment(models.Model): + _inherit = "account.payment" + + def action_validate_invoice_payment(self): + # Check if Invoices have ACH IN/OUT payment method, to avoid any + # conflict + valid = False + if self.filtered(lambda p: p.payment_method_id.code in ("ACH-In", "ACH-Out")): + valid = True + if any(len(record.invoice_ids) != 1 for record in self): + # For multiple invoices, there is account.register.payments wizard + raise UserError( + _( + "This method should only be called to process a " + "single invoice's payment." + ) + ) + if valid: + for payment in self: + payment_method = payment.payment_method_id + if payment_method: + if payment_method.code in ("ACH-In", "ACH-Out"): + # Update invoice with Payment mode + if not payment.reconciled_invoice_ids.payment_mode_id: + payment_mode_id = self.env["account.payment.mode"].search( + [ + ("payment_type", "=", payment.payment_type), + ("payment_method_id", "=", payment_method.id), + ("payment_order_ok", "=", True), + ], + limit=1, + ) + if payment_mode_id: + payment.reconciled_invoice_ids.write( + {"payment_mode_id": payment_mode_id.id} + ) + payment.reconciled_invoice_ids.move_id.line_ids.write( + {"payment_mode_id": payment_mode_id.id} + ) + action = ( + payment.reconciled_invoice_ids.create_account_payment_line() + ) + payment.unlink() + return action + res = super(AccountPayment, self).action_validate_invoice_payment() + return res + + +class AccountPaymentLine(models.Model): + _inherit = "account.payment.line" + + discount_amount = fields.Monetary(currency_field="currency_id") + total_amount = fields.Monetary( + compute="_compute_total_amount", currency_field="currency_id" + ) + payment_difference_handling = fields.Selection( + [("open", "Keep open"), ("reconcile", "Mark invoice as fully paid")], + default="reconcile", + string="Action", + copy=False, + ) + writeoff_account_id = fields.Many2one( + "account.account", + string="Account", + domain=[("deprecated", "!=", True)], + copy=False, + ) + reason_code = fields.Many2one("payment.adjustment.reason") + note = fields.Text() + payment_difference = fields.Float() + move_id = fields.Many2one( + "account.move", related="move_line_id.move_id", store=True + ) + + @api.onchange("discount_amount") + def _onchange_discount_amount(self): + if self.discount_amount: + self.amount_currency = self.amount_currency - self.discount_amount + + @api.depends("amount_currency", "discount_amount") + def _compute_total_amount(self): + for line in self: + line.total_amount = line.amount_currency + line.payment_difference + + @api.model + def same_fields_payment_line_and_bank_payment_line(self): + res = super( + AccountPaymentLine, self + ).same_fields_payment_line_and_bank_payment_line() + res.append("discount_amount") + return res diff --git a/account_banking_ach_discount/models/account_payment_order.py b/account_banking_ach_discount/models/account_payment_order.py new file mode 100644 index 00000000..b9edf1af --- /dev/null +++ b/account_banking_ach_discount/models/account_payment_order.py @@ -0,0 +1,102 @@ +# Copyright (C) 2019 Open Source Integrators +# 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 _prepare_move(self, bank_lines=None): + values = super()._prepare_move(bank_lines) + bank_payment_line_pool = self.env["bank.payment.line"] + line_ids = [] + for vals in values.get("line_ids"): + # get the debit line for adjusting A/P entries + if "bank_payment_line_id" in vals[2] and vals[2]["bank_payment_line_id"]: + bank_payment_id = vals[2].get("bank_payment_line_id") + bank_payment = bank_payment_line_pool.browse(bank_payment_id) + for line in bank_payment.payment_line_ids: + + temp_vals = vals[2].copy() + amount = line.amount_currency + discount = line.discount_amount + payment_difference = line.payment_difference + writeoff = 0.0 + invoice_close = False + if payment_difference: + writeoff = ( + payment_difference and payment_difference - discount or 0.0 + ) + invoice_close = line.payment_difference_handling != "open" + use_debit = line.move_id.move_type in ( + "in_invoice", + "out_refund", + ) + + temp_vals["move_id"] = line.move_id.id + if use_debit: + temp_vals["debit"] = amount + discount + else: + temp_vals["credit"] = amount + discount + + line_ids.append((0, 0, temp_vals)) + + if discount > 0: + if payment_difference: + pay_term = line.move_id.invoice_payment_term_id + discount_information = ( + pay_term._check_payment_term_discount( + line.move_id, line.date + ) + ) + discount_vals = temp_vals.copy() + discount_vals["account_id"] = discount_information[1] + discount_vals["name"] = "Early Pay Discount" + if use_debit: + discount_vals["debit"] = 0.0 + discount_vals["credit"] = discount_information[0] + else: + discount_vals["credit"] = 0.0 + discount_vals["debit"] = discount_information[0] + discount_vals["bank_payment_line_id"] = False + if discount_vals: + line_ids.append((0, 0, discount_vals)) + # Discount Taken Update + line.move_id.discount_taken = discount + else: + # Case: If user Manually enters discount amount + discount_vals = temp_vals.copy() + discount_vals["account_id"] = ( + line.writeoff_account_id + and line.writeoff_account_id.id + or False + ) + discount_vals["name"] = "Early Pay Discount" + if use_debit: + discount_vals["debit"] = 0.0 + discount_vals["credit"] = discount + else: + discount_vals["credit"] = 0.0 + discount_vals["debit"] = discount + discount_vals["bank_payment_line_id"] = False + if discount_vals: + line_ids.append((0, 0, discount_vals)) + # Discount Taken Update + line.move_id.discount_taken = discount + + if invoice_close and round(writeoff, 2): + if use_debit: + temp_vals["debit"] = amount + discount + round(writeoff, 2) + else: + temp_vals["credit"] = amount + discount + round(writeoff, 2) + writeoff_vals = line.move_id._prepare_writeoff_move_line( + line, temp_vals.copy() + ) + writeoff_vals["bank_payment_line_id"] = False + if writeoff_vals: + line_ids.append((0, 0, writeoff_vals)) + # payment order line + else: + line_ids.append(vals) + values["line_ids"] = line_ids + return values diff --git a/account_banking_ach_discount/models/bank_payment_line.py b/account_banking_ach_discount/models/bank_payment_line.py new file mode 100644 index 00000000..9ee03c19 --- /dev/null +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -0,0 +1,26 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class BankPaymentLine(models.Model): + _inherit = "bank.payment.line" + + discount_amount = fields.Monetary( + compute="_compute_discount_amount", currency_field="currency_id" + ) + total_amount = fields.Monetary( + compute="_compute_total_amount", currency_field="currency_id" + ) + + @api.depends("amount_currency", "discount_amount") + def _compute_total_amount(self): + for line in self: + line.total_amount = line.amount_currency + line.discount_amount + + @api.depends("payment_line_ids", "payment_line_ids.discount_amount") + def _compute_discount_amount(self): + for bline in self: + discount_amount = sum(bline.mapped("payment_line_ids.discount_amount")) + bline.discount_amount = discount_amount diff --git a/account_banking_ach_discount/readme/CONFIGURE.rst b/account_banking_ach_discount/readme/CONFIGURE.rst new file mode 100644 index 00000000..ac6179e5 --- /dev/null +++ b/account_banking_ach_discount/readme/CONFIGURE.rst @@ -0,0 +1,22 @@ +Payment Terms +~~~~~~~~~~~~~ + +* Go to *Accounting > Configuration > Payment Terms* +* Create or select a payment term +* Activate the discounts options +* On a line, set the discount percentage and number of days + +Payment Modes +~~~~~~~~~~~~~ + +* Go to *Accounting > Configuration > Payment Modes* +* Create or select a payment mode +* Link it to an ACH payment method + +Vendors +~~~~~~~ + +* Go *Contacts* or *Accounting > Vendors > Vendors* +* Create or select a vendor +* On the Sales and Purchase tab, set the supplier payment mode +* On the Accounting tab, set their bank information (account number, bank, routing number) diff --git a/account_banking_ach_discount/readme/CONTRIBUTORS.rst b/account_banking_ach_discount/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..4a3b34a1 --- /dev/null +++ b/account_banking_ach_discount/readme/CONTRIBUTORS.rst @@ -0,0 +1,4 @@ +* Open Source Integrators + + * Bhavesh Odedra + * Maxime Chambreuil diff --git a/account_banking_ach_discount/readme/DESCRIPTION.rst b/account_banking_ach_discount/readme/DESCRIPTION.rst new file mode 100644 index 00000000..c4609831 --- /dev/null +++ b/account_banking_ach_discount/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This module will add support for discount in ACH and batch ACH workflow. diff --git a/account_banking_ach_discount/readme/HISTORY.rst b/account_banking_ach_discount/readme/HISTORY.rst new file mode 100644 index 00000000..3e246dc4 --- /dev/null +++ b/account_banking_ach_discount/readme/HISTORY.rst @@ -0,0 +1,4 @@ +12.0.1.0.0 +~~~~~~~~~~ + +- ACH Payment, Automatic Discount and Batch Payment Partial Pay Integration diff --git a/account_banking_ach_discount/readme/USAGE.rst b/account_banking_ach_discount/readme/USAGE.rst new file mode 100644 index 00000000..05146861 --- /dev/null +++ b/account_banking_ach_discount/readme/USAGE.rst @@ -0,0 +1,8 @@ +* Go to *Accounting > Customers > Invoices* or *Accounting > Vendors > Bills* +* Select or create various records in the state posted with ACH and discounts +* In the Action menu, click on Batch Payments +* Review the payment information provided by default +* Click on Make Payments +* Review the payment order, confirm it and generate the ACH file +* Go to your bank's website to upload the file +* Come back to Odoo and confirm the upload to the bank was successful diff --git a/account_banking_ach_discount/static/description/icon.png b/account_banking_ach_discount/static/description/icon.png new file mode 100644 index 00000000..84791119 Binary files /dev/null and b/account_banking_ach_discount/static/description/icon.png differ diff --git a/account_banking_ach_discount/static/description/index.html b/account_banking_ach_discount/static/description/index.html new file mode 100644 index 00000000..eefb0179 --- /dev/null +++ b/account_banking_ach_discount/static/description/index.html @@ -0,0 +1,487 @@ + + + + + + +Discount on ACH batch payments + + + +
+

Discount on ACH batch payments

+ + +

Beta License: AGPL-3 OCA/l10n-usa Translate me on Weblate Try me on Runbot

+

This module will add support for discount in ACH and batch ACH workflow.

+

Table of contents

+ +
+

Configuration

+
+

Payment Terms

+
    +
  • Go to Accounting > Configuration > Payment Terms
  • +
  • Create or select a payment term
  • +
  • Activate the discounts options
  • +
  • On a line, set the discount percentage and number of days
  • +
+
+
+

Payment Modes

+
    +
  • Go to Accounting > Configuration > Payment Modes
  • +
  • Create or select a payment mode
  • +
  • Link it to an ACH payment method
  • +
+
+
+

Vendors

+
    +
  • Go Contacts or Accounting > Vendors > Vendors
  • +
  • Create or select a vendor
  • +
  • On the Sales and Purchase tab, set the supplier payment mode
  • +
  • On the Accounting tab, set their bank information (account number, bank, routing number)
  • +
+
+
+
+

Usage

+
    +
  • Go to Accounting > Customers > Invoices or Accounting > Vendors > Bills
  • +
  • Select or create various records in the state posted with ACH and discounts
  • +
  • In the Action menu, click on Batch Payments
  • +
  • Review the payment information provided by default
  • +
  • Click on Make Payments
  • +
  • Review the payment order, confirm it and generate the ACH file
  • +
  • Go to your bank’s website to upload the file
  • +
  • Come back to Odoo and confirm the upload to the bank was successful
  • +
+
+
+

Changelog

+
+

12.0.1.0.0

+
    +
  • ACH Payment, Automatic Discount and Batch Payment Partial Pay Integration
  • +
+
+
+
+

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 smashing 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

+ +
+
+

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:

+

bodedra

+

This module is part of the OCA/l10n-usa project on GitHub.

+

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

+
+
+
+ + diff --git a/account_banking_ach_discount/views/account_payment_view.xml b/account_banking_ach_discount/views/account_payment_view.xml new file mode 100644 index 00000000..7f10a3dc --- /dev/null +++ b/account_banking_ach_discount/views/account_payment_view.xml @@ -0,0 +1,63 @@ + + + + + account.payment.line.form + account.payment.line + + + + + + + + + + + + account.payment.line.tree + account.payment.line + + + + + + + + + + + + + + + banking.bank.payment.line.form + bank.payment.line + + + + + + + + + + + banking.bank.payment.line.tree + bank.payment.line + + + + + + + + + + diff --git a/account_banking_ach_discount/wizard/__init__.py b/account_banking_ach_discount/wizard/__init__.py new file mode 100644 index 00000000..17731038 --- /dev/null +++ b/account_banking_ach_discount/wizard/__init__.py @@ -0,0 +1,3 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import account_payment_register diff --git a/account_banking_ach_discount/wizard/account_payment_register.py b/account_banking_ach_discount/wizard/account_payment_register.py new file mode 100644 index 00000000..370f1729 --- /dev/null +++ b/account_banking_ach_discount/wizard/account_payment_register.py @@ -0,0 +1,66 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models + + +class AccountPaymentRegister(models.TransientModel): + _inherit = "account.payment.register" + + def make_payments(self): + if self.payment_method_code and self.payment_method_code in ( + "ACH-In", + "ACH-Out", + ): + action = False + payment_mode = self.env["account.payment.mode"].search( + [ + ("payment_type", "=", self.payment_type), + ( + "payment_method_id", + "=", + self.payment_method_line_id.payment_method_id.id, + ), + ("payment_order_ok", "=", True), + ], + limit=1, + ) + payment_line_pool = self.env["account.payment.line"] + # Update invoice with Payment mode + if payment_mode: + for line in self.invoice_payments: + invoice_id = line.invoice_id + # updated discount logic + discount = invoice_id.discount_taken + # discount should not be consider for open invoices + if line.payment_difference_handling != "open": + discount = invoice_id.discount_taken + line.payment_difference + invoice_id.write( + { + "payment_mode_id": payment_mode.id, + "discount_taken": discount, + } + ) + invoice_id.line_ids.write({"payment_mode_id": payment_mode.id}) + action = invoice_id.with_context( + payment_date=self.payment_date, + payment_line_state=line.payment_difference_handling, + ).create_account_payment_line() + # Find related ACH transaction line + domain = [("move_id", "=", invoice_id.id), ("state", "=", "draft")] + ach_lines = payment_line_pool.search(domain) + if ach_lines: + ach_lines.write( + { + "payment_difference_handling": line.payment_difference_handling, + "writeoff_account_id": line.writeoff_account_id.id, + "reason_code": line.reason_code.id, + "note": line.note, + "communication": "Payment of invoice %s" + % line.invoice_id.name, + "communication_type": "normal", + "amount_currency": line.amount, + "payment_difference": line.payment_difference, + } + ) + return action + return super().make_payments()