From 54375e53bd40e691cb3df513cd432615bf9f9dfc Mon Sep 17 00:00:00 2001 From: Murtaza Mithaiwala Date: Wed, 24 Feb 2021 19:36:16 +0530 Subject: [PATCH 01/28] [MIG][14.0] account_banking_ach_discount. --- account_banking_ach_discount/README.rst | 36 ++++++ account_banking_ach_discount/__init__.py | 5 + account_banking_ach_discount/__manifest__.py | 24 ++++ account_banking_ach_discount/changes.txt | 5 + .../models/__init__.py | 8 ++ .../models/account_move.py | 108 ++++++++++++++++++ .../models/account_move_line.py | 36 ++++++ .../models/account_payment.py | 85 ++++++++++++++ .../models/account_payment_order.py | 75 ++++++++++++ .../models/bank_payment_line.py | 38 ++++++ .../static/description/icon.png | Bin 0 -> 29346 bytes .../views/account_payment_view.xml | 58 ++++++++++ .../wizard/__init__.py | 4 + .../wizard/account_register_payments.py | 83 ++++++++++++++ 14 files changed, 565 insertions(+) create mode 100644 account_banking_ach_discount/README.rst create mode 100644 account_banking_ach_discount/__init__.py create mode 100644 account_banking_ach_discount/__manifest__.py create mode 100644 account_banking_ach_discount/changes.txt create mode 100644 account_banking_ach_discount/models/__init__.py create mode 100644 account_banking_ach_discount/models/account_move.py create mode 100644 account_banking_ach_discount/models/account_move_line.py create mode 100644 account_banking_ach_discount/models/account_payment.py create mode 100644 account_banking_ach_discount/models/account_payment_order.py create mode 100644 account_banking_ach_discount/models/bank_payment_line.py create mode 100644 account_banking_ach_discount/static/description/icon.png create mode 100644 account_banking_ach_discount/views/account_payment_view.xml create mode 100644 account_banking_ach_discount/wizard/__init__.py create mode 100644 account_banking_ach_discount/wizard/account_register_payments.py diff --git a/account_banking_ach_discount/README.rst b/account_banking_ach_discount/README.rst new file mode 100644 index 00000000..9051b85d --- /dev/null +++ b/account_banking_ach_discount/README.rst @@ -0,0 +1,36 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +================================ +OSI ACH-Batch Discount Connector +================================ + +This module will provide a discount functionality in ACH and batch ACH workflow. + +Test Scenario: +============== + +a. Single payment without discount + +b. Single payment with discount + +c. Single ACH payment with discount + +d. Batch payment without discount + +e. Batch payment with discount + +f. Batch ACH payment without discount + +g. Batch ACH payment with discount + +Credits +======= + +* Open Source Integrators + +Contributors +------------ + +* Bhavesh Odedra 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..68d134b4 --- /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': 'OSI ACH-Batch Discount Connector', + 'version': '14.0.1.0.0', + 'license': 'LGPL-3', + 'author': 'Open Source Integrators', + 'category': 'Accounting', + 'maintainer': 'Open Source Integrators', + 'website': 'https://github.com/OCA/l10n-usa', + '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', + ], + 'installable': True, +} diff --git a/account_banking_ach_discount/changes.txt b/account_banking_ach_discount/changes.txt new file mode 100644 index 00000000..29e1f9d7 --- /dev/null +++ b/account_banking_ach_discount/changes.txt @@ -0,0 +1,5 @@ +init + 12.0.1.0.0 +Task Ref: FUL-02C-00-1910-18565 +Description: + * ACH Payment, Automatic Discount and Batch Payment Partial Pay Integration diff --git a/account_banking_ach_discount/models/__init__.py b/account_banking_ach_discount/models/__init__.py new file mode 100644 index 00000000..192b0fe1 --- /dev/null +++ b/account_banking_ach_discount/models/__init__.py @@ -0,0 +1,8 @@ +# 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..57e0975e --- /dev/null +++ b/account_banking_ach_discount/models/account_move.py @@ -0,0 +1,108 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import models, api, _ +import json +from odoo.tools import date_utils + + +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 == 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..acd61f34 --- /dev/null +++ b/account_banking_ach_discount/models/account_move_line.py @@ -0,0 +1,36 @@ +# 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" + ): + 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.date_invoice + ) + discount_amt = discount_information[0] + vals.update( + { + "discount_amount": discount_amt, + "amount_currency": amount_currency - discount_amt, + } + ) + + 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..6252a2da --- /dev/null +++ b/account_banking_ach_discount/models/account_payment.py @@ -0,0 +1,85 @@ +# 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): + if any(len(record.reconciled_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." + ) + ) + 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", string="Reason Code") + note = fields.Text("Note") + payment_difference = fields.Float(string="Payment Difference") + move_id = fields.Many2one( + "account.move", related="move_line_id.move_id", store=True + ) + + @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..6f7b666d --- /dev/null +++ b/account_banking_ach_discount/models/account_payment_order.py @@ -0,0 +1,75 @@ +# 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(AccountPaymentOrder, self)._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 transaction in bank_payment.payment_line_ids: + + temp_vals = vals[2].copy() + amount = transaction.amount_currency + discount = transaction.discount_amount + payment_difference = transaction.payment_difference + writeoff = ( + payment_difference and payment_difference - discount or 0.0 + ) + invoice_close = transaction.payment_difference_handling != "open" + use_debit = transaction.move_id.move_type in ( + "in_invoice", + "out_refund", + ) + + temp_vals["move_id"] = transaction.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: + discount_information = transaction.move_id.invoice_payment_term_id._check_payment_term_discount( + transaction.move_id, transaction.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)) + + 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 = transaction.move_id._prepare_writeoff_move_line( + transaction, 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..d2e8f2ce --- /dev/null +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -0,0 +1,38 @@ +# 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 + + def reconcile(self): + self.ensure_one() + amlo = self.env["account.move.line"] + transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) + for line in transit_mlines: + ap_mlines = line.move_id.line_ids.filtered( + lambda x: x.account_id == line.account_id + ) + lines_to_rec = line + lines_to_rec += ap_mlines + lines_to_rec.reconcile() 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 0000000000000000000000000000000000000000..84791119f6e93bfb3e59814787c14db9e58275e1 GIT binary patch literal 29346 zcmeEtbx@p95Z~c}-~`t|aQ8rPju71LAUFhf2<`-e69~cGf*l&%HMm}IcXtTR9ldng zX{R%lnf}vu`etr!zPs;z^S*uicK5gY+emd)c^oVXEC2w2qwoQ!2>>9C{`@DcryqzuRq}A1V0e}~2TZ(F9 zN}R*W+AJ)?Y9pfxJDLC#Ga@b`B_gE)APNe~OOzKp0z5oIs6XCIHHd&%pa8Vg)Qm!; zyk^~+G}ed-G=N$Qu{9;Ipb-cOGZy#qRwUX#S@^MAC)+Ib!J}|SQ{Z`j%@od$u3W<*X|I(2!qp3Al zjdI9f`l+~;2bdU^B6A+Ix22ykw+8!rz=Z=HkwZ55Dl?K>+{yw>jP0V@A(45z!uZ-p zL_yUPDZ(0m+3?N70T()S1ha}gG9p6L#DtK5ps=g!4L!Z$moExHU?MU~LPP{hC{e=? zL4J%n6;o>TBrOe={1jm&Q2nM`N^nF};l6gh1CzBtH@6tQ?`1Ub?ws zma?OqZ`e69dccK2Kx~Hii=m4}gC%!M)l+x;ZCleD(XDf<|8ng9x7Ydq>>+tAX98i~ z^tgh1c zvFSmahcylvH&EV92!DiLvDVA+?0^dkI->B$uc=_ejWjp{y~MuU%)t!qMj8_Ff6zde zLvNFF@b}Qv6z%8aUQi~(1SIcE0OmcKPUwUxkEwR~iZp9;J2-ZK5RiXWHu6i{?a6EV zI)36PV*jy$HYf*Dqny6L)5B@JVWB(Z6;LPVM0(wurW%<+J3$T5?$-h*)4U{QO1M#> z#sIMNMcm+C^FezR_VSwC8E$ft`L3-tdVWIO7p+Io#+axXT(gJt#&4pSxbjx?)43L3 z`t_*FX^am}Tr!qQC}$h;j&pLKuyV9 zg&Ll@Ba?vYoqwfny?Ql$f`Or5nZEe&OHW(#Pf@q5>edZ(;-nV>zR$0>iEhNrV5{<@09#W|PUKJ@#givh zDz>clG`DN6T)v}BHE#5!K5Dtr=bQ3lrEJR4FS(Y|{<@X*Za?*n08#=Xbz8ssdk4y! zoJ}=dO@^EAQKw}8>aeopmZY>*SDoo<>@)!;ID6Y!H7Z)Mw$W_05J{F}BR5uuXi>JR1`Qq}48#&47P zUH0(E0k#m#7jM5_&*t>nU5)J)Gqzaa+)1`8d%LkG@`A|rh<$ii@qL4hv(3?8dpUU5 zth{!^b@OFBQPOXYrK0ZoUVHftSYL-~F#C}sS&nQf#iaxrCd8XSZJDveg z*Xtyyow8spwDN9Tuf*0*-&@;zGteKls>)jn#Xc-mIH#T2Qg>U-!7~HwpTjJ;-KzLY zD>sIJyVRm~YUEikb0l;xpX@PRI>Zh6FU_s}vZJ#&R2vI^;M*QCdfBUj?^^~*{<$zd zdILG?Y<`I1v|G%f8cZ6VZdO`FzFCQb39xr8OhXLL;Y%D9xNW_*$wKaHi{D8GlP2FcAW zl(Zf_!#57WMdNlKsi+$x2Bmq=M#&wQIyx>oAN0Kc5aaAIdOxMT;BvoKjr1vD*p_U! zyEZ)XolRTd(IE0p!lFR+IN@FMtR>-Zvpi}cp7?8Au3ztS%CAHB6`i+L56rE6^=b5} zx{PD0ZS7ZK0qmD)$=DAUR|+n2f^*+Z`0Enwq7|NWm=w@yX>sx9Y6@KTzSwBr_o6~i zym5C}qgyfu6_zN)MI1jp(}>Kfk>rhR_X3h2uhvm_&~?MCY3vY78R!y9qu9qpw zctop&ugoS7=rK*t>SXe}pFR?u?1_A1gs&OY)!_(MQl?dpBMY$sOMXFXOIGcJ28!~ z*I{gWN9WOu>-Qnt$xq0wLhnZrO4VX}rpeMl9BjluPc(I`g__?bK+t9e()C{W;y}8F zXD?PdGoR$H#ErSIZKnPR!cSuk2IW`?6R>KTd=Xg{6ct_^VV;HUPTpLHG%c3nCAqt< zWtaHB(}2kmlv1dR&=tJ;NM^8Vn0No3TDP&%VWMRk1FA8T(#as@M3AJ{WbiY157(gS zgmk{nCSqp%I`7lx-G2OSbOHkwwiK_fmXIYH-=?a1dF`<#9zb9olgz}F`d!2U{N6=y;K#fE z>7XDOV&hqREet}Hmb+%3rLwi5m>g1QOh>!6ASOC;9D=M>LYzVd2lMA#GS|2OC4-Fg z9|ia_lxg@~`fDE>B|Ch1c5-V+c#XX{3{t1-Bpw}8{WQSL#IT7;$1W$NwUcuce6XM= z0p_ldD(ar7`hq_rWD+$>lE=I%ozL@vQ-8Md&LUlgnl0hush)Fn^6aJc2|!&_`-U;R zT?gdbvx?1)+@O%Bzbu|kb06FqME`N)z!MPJws&0UAfjA&=-hzE{+0F&TPL+ z!dkBU9{qCI6rhKClPR0SH#0^^ASa#^XOtG992a%<`&b4%h8$F)jdaKNX^1$A*40FePYG@4YZ19rJYm33~}$9nVU{f+F=Llir~ zg#_6nGCn9DpxGgfC{kljebE3v-Mj(R%rp@|38ZNx`eg8y{L{ZB2e{osT02Xt8-}r& zx~_s8_z!IHpsogAzG3`S1Q*@BQsUJVyl>q<*~>dk#>4&_6|hVk{a(T7SrGHHzbRuz z8m;!A6Mgl9N&rJ7wG=kRl@WcUF<|cH##_AVi}nGcW^!pGugKkzFBYu9vUY*wDN z`WP)gig?2}Z|#lSHjRl zrqXq;5y^uyC~d{qO`~EM;_vCVve|g%sQ1f*t}sp1>7WezLZ5AsGPxS1vW4EM!*2jC ze)L%Or@Y)QHJ!J;yN{GqJBiILUgl<+qvW|v;5HCTEL&nhW5vSDDM9V6-tJ@dnqTrZ=)#8_q*`igZuwB7#-%wbU zD2TW_O$o*Bv)L@&x%q3`H!qcl?(yO-Iy2^s|Jobl`2JHt8}IuWi-^) zxweg=_+er@v0;zXHQ7Wpde|dLq~4%qxn8I+D4D^R^1bBG?4-w6Xz-zDdHm03V{vQs zRTJN=5P_BmCFvWDH7iS}BZ-4hTL&J2*=bInrP-o)v}*b!qK~h%1qTrxH((A`k^@(1 zt^71+RqXS~iOm^-N=yDm+i#fbCokU(aa{huKR^-({aU}3iw0WHzI=@xnt=Q+_LdRA zoud@@LC3((u3i3S!jg95vramD6ym+Pog)LQ^3OO794+nQ51QB_xm#GA z=6iOjGj$&lr>CYx4_0l!>u(xj`-8t9$AKnnygEysT<3@_Q>@`ryG8xN#r`TbDp_;Y zRP;D2abx!3ZzWnRwv+q6EQeDG7jOLGyWkGb)ww-=IhGY`=UHh8pO0*)%APup{608< zm6B$bmGSI-wb*a`Gf-T~80~IN_8ii)LjW%@(1-74sV%o}HSMG>ofBZf$wQOVblqkq z1X7$Hw$;yukiSFjqa_mPo`@&i8Lgu5ob=nD?EEXlOyS>ZN0iTw;f|E{Wx(aVC>@U+ zDvI(k!!!QIcQviOYMO$28TD?;?$xwk>0Y*yrgc+U$0T>UKen_A6?*d~x#Q?C23L`0 zTc_vEpam|rS>AUREes^K^cfS)NFFcHBzx{#G1mEe&@n@jt|%RGcXM=RperN!_i7ne zKH@?gzPu*^`^SHXViGKcl&gcdY1}RD-Ec3n$Lw^Lg}&3C#mZCWI2pBX`gp?3p$Vl1 zkg(482uLFp3QB`OTw8nj(x0cVKCd*)e3Mkvhcy)1EYyAwr4U=BmOt?i^Gx=q2TLQO z`h--NyT-?K@y$&q>JJ~0yWlt@M%G5HPd)_qjvJS#@Oy=;6r*GhNeJ7fM^=R48gViY zDJ)*jxUJBpZ%*qgBf#sbrQa!HqY8@@YkLNKMY)>Lz+n@U)45F_Vyw}`flNDJsjvh2%Rt)Q0+ZdW9+`$`|cAE zYDG@x#5rV6f@5xM^{SVQpgoU=gLBhttis`%Q-wX?dqb$F%3z>o@NK4YYWMWTy=CHR zrVs~GZei&~-V5J3jT}#J07a3CzMfD1N%5%R(`tUTfNR`kX9mJu%uCt1>uC$a^+ES* z=tnlcC_$?r@+Z!&3ZFKIgX5X&Zq?4BHwxPm#7!lSw3hQpBafW#(v#OD2mO7o7I$(Y zy&q+uMCfg0w!nk%$EAHB_Kj=(vWmVihZD_ygFdb9!Yq_rlESpicer3MBDceG_QxTS zgE9G&yYx{}U8Ogy*%>u{ z>XjP#l>Hdr?V|^TpoU{JG-clsjZ}*~vtgKbej5O?l;SjIVwo9)Z ztFdy%8EBO3%qYz-s@17K(@owyJM$gzSi>P2?>Nc;Z4hB!acia2gakwnNse{$+E6E< za0wB4^hoK2&W`GTMtfsWHJQC6S#&qNi_+>yz27bmm~Yq}FJ8X*k{=qZvomoaDZc9Q zAALMVd$V&|b3lW)!{C;_Qb;)6?uVtS-p4is_STS&K4s@`Ml1^yIv${*EU4+sa0Ik9*TeA8 zm#Fi+VJm9UBeyS8(BvW74K_Aixpp4B2`8Rf4 zz0%XXdJAVg&3L7J-4a#`+Chu|R1V9Keyh_3ASeU&r%wyKMHAQP`P3|xa}>Z4_43D` z<0P`tTpqW6t?5}SB_eN0EQl43m+4h>tAsF>TD8L%wLa+LTRt<`+h4$7WladiJEO1W z_RRk+xlO{fI_EYKBfV)IXJCmbAxRYT@+P|mfVU@lagvve1~9nz)`QXkF<{%=!*LdV zhqtpG7rw~M!MqB(n>PXf+cUWOjqaQL{bBC+*xtEcPYQCDtPTSyzf|pPoQ2+Fn7x0r z9~AT+|29R)DRJR)?b}eHdy^x}i2*n^Xn!(|zBAq63t3AX`;?orN6tWdk#=&F68QYv zj)YP&a7YfHRn$e}uJ}g6XU_yf010sHeRvZh5})61jh>^+Uu9})e55mG{5cjFXtq() zrnqHdM}+0D()78i83s9LfnsMu6pOrPfO%+o}s6-#Bgx7+gBL z-j~B50CSKHN?^m=GO!P9D+drrwx1=3+h{+bc$(ASY>g5?vy~(lCoaKfP_S7iAn52( zzxIkNn%5}l#;B_M66<~xj#Hr@rbjZe5!CH-wilrqK{8H#7~oq%y23tOl6k&5g;}AO zwARpaAwO1@5@@}2*ze?|+q#+3s?EK$ItL*iGvI)T&y1qmhu;VFu6v1w_^piQ90!y` zh@pj!Z*#UlyjNL~ia99MnID?B!V2_eC9-tx(ZWnq6&suS@`>Fr6BPOXib%;jcj|>0 zEWzIPXz$ULJ?MGzycxtpILd&Nz!&>@kjR#RoO1AoDN zSg_rd^bdC_wBot=l_89~Rxh~7ZxY9_!pT=rV3Ff_xMMa;^sN7i@oV;D6k?qt6OVeH z)iOqN6y?Rx=nqZhH5}wgwq{fQ;dmCOWU($PSM-n5llp}$*?6J`Fl40ghF9V@cZ$<1 ze7Q(3!+MsZ-=Dh-6pX2`pB^~Dy64+{?wt%Z1pcVt2fC^_z?$FrT?@ z>@LuZ!ui{P`5QGWY_wx{7OIN@XpO`5rFbtKD4l(DD798={+{q;6W++O`@Np5A8Gn( zb`d>A-dC|ekN&j*bgqRuTUkr`?3pS9g(tTogP9|4J+!A56p?ktba3k1T*XD{>s(@p z4*;hTJz}(ND^AIEZn>*%Cr;q7lWY z=8OI7f1us^_h^1->=yQtu=799z(g-MGwSGb>MVhgMGgXS^~FD?SPqJ8qA>5Ijb@X? z2ivllD^IP=i4fNHQfxl*G=Qn(@d$*PtK4Uz&sw{yy8XmI|o7283D6#*}XpCAn>F;J){@dU}n8towQo30`0MsK(qV5`b%3r+3y=XxShc zCGnXro89S;d06&E6xdzwm70#3a~Mx=G;jab&Fd4jPc9@u*+dfjM;r>~^R^LyTm&v$ zVLnX$k(e|mRenp;rmHmC#~fBm1y~QxXP;g7ARyrq^LQgDT2|ULeoDQINl?_w$8rzV zLlNpex4+O-5{dnJd?0{*_E$osaYy{uX2=(l-}5aGQ2DC#5;b-`75n+Mf)k-n?cPo( zWZr<$wjmID zZvY+(!&~`R;uUek*6!B{d|O}jPB9~hPu?|U8m{0>D}w%foAIlAG5Yleoe6bf4{lPVU40Cp&n$i>YsXU>)$P8YSA{eed^)JNrpz7YwHiV9NXhsTu>kb64vnedf< zuGg&DL4u$wX}ihmW5s3TL6Pi<(&OcJr@5kTp${WFX5LI%JdT{z?}qT4t_>RTdtP%> zr4UPja&XrrGJBeJ)kt-eiKl*KU&Q9=H|+LSFAI@He4mNupBm6}%%;MeG)j6ZEEMsj!=%`W&f`jY^V- zdg7(I*IpOOWZK~DkDjW!sBb|YBt|5lWX1wuVXIVOjdtX@B|83!S_}1 zC^44ew1P+*sh0V-ll%M^mh18hE#W^xLbEOeXi=QY06AVmv>#>&014SHIej@f+u4*L z*eQOhuh0R_6)|A6C~~uw$Gmm-D|OhOI>xB%;sM^lG5A#~)`tOSfvdB)(3MafH)vnA z?Ivbt9owfrv)+2Re2ARCK+L{~)+vRkx|(8jJ9bUYQoL_hpstATg}=HnTi6`&xSQ7g zV7agsk}}SuD1e7x#Im}yA0{m1G!(eA{4qtgG4Qm2OWt%7Tyo*h(~ zBmd^}3nhTMFu%q?Jnqw}2aA?_5-0M1<^rsW{5}{)*T7}Xb}6;8KiYs-P}fU_TTFUK zAUzPeYJ+ytT77hH>iq5(1-umM^3eq?2ayJ)PktsT|&R)I^vc$ciGLg%?rE9MohtK39#Hxk{uPP7p#YBFBb4p zjr0s#ggRL}H?&5B*yriY&89;(Z(-GiXUu@wvknVhKiZ>kWS6AFZgX{G>HzjC66KS6 zI%EheLF5%6r&DIDb^zA!_0JI?%yPo-6|NLMZ>o-9ZGNWXnm21X;mzyWG(r(@+r8 z7qW=)b#4>06V^^EC1#xq;v#z1u}~Yp>}~tQ_ZL6C3^aMEKJX`I$k<*k_@M=vI%Kh( zBH~M)x$)rGSpa^5eK_!#y@~H2hU>_q%7JP&Aa(j^@Vf-xz@q9HFA8rLPSRN030w&nU}Ql&y)Y+++Z zPBv=SVQqwwqrjH%=k4)FYEr?ycQpV#H471C^YB#f39%+e?z(crizzK#be{wDl3RF? z3K0>um-=6U@#ecCtOyeDuQVkHIw@|>ruRXkkK^|@#*=E;0E6qv*qM<4!zTNsjfN1G zw_ato3iu+PsUtE2*O*IBcZQEbRn14u)G8xqh_J>_?&|RBS*Boi?+U)qBS@^r6r904 z(d@Da^&NU64`wa_xs~7@tzU}&9$t>t=iUoz?)AgsA}HQtV_sZkXyj_aLM|&;7FE0F zq{Ga{sPjbSoQZyTaKtanV{s(Tz>HFQH1vI84(cGZ6$~9g7F#hvjWURQIA5K}2vE@u z$|slpeLR1-|Bd|}f%>%5h|I-N?$icRgZD{Ikwbw{AP0^3N`jtB5F85CCv(oVu47HO zC_J4~dw;OZKSiuPUEKJF9G<*7AP7q*M2bDo@l$-$LVUiu$k6u}yN8t$`B#SYjjS(* zIB%oEhwbgmH&7)b`mVqX(=av=F}W-GWbVFAQmP@#_r+61iV*6E@1WivtabfkVeTJ) z{VHHrM0yp+j^*E0VO;6Z#GNK_UoQ0brMz)*;Qp0b6|N8A(1;2^T_Z1S7b~1@6Sy9 z%nzuujOVgab1=)$WVyI00QS-g-G6xFoG^_%Kkjn#)-Q3^K6@=u`LiBRY4 zcwp(Klpeea_U~tpCHXf!--IPC1a;!3HRPMk-Dr%B?e_VPn8g(Dya^r;pddH{A60C5 znt-Gpzx6(FAQE{l^bL$PFXuKuB(&Kg2hO)E@jCuSn_7O>WJ= zS(Z#NRWuELVTn zskVVB*b8=H(0sWtd#()D!;o-9&LbvlvlK#A%QPeSN@Ya|Bv_jadRb?U3vC+`hv8Jy zMU^kcZ?J;Vs$F`a;S&T_knEzv0jtxKpxnD{;7&`ag<1HCxk0c&E@UfDuM|Rzdm>iE zeF_14V?^1;f_jgNMwWNdMFYPCI>X!h^9rfy@TPz1eie3_dL-bH6mv`^g5<4{IP zx_XPh@t$%{MA=VITvk%*NEqAPQ((H~$6IiUtyp@y{O#2Nb}pw%WBpFPU!^mTeHV90 zdgFHLd!i6t8_V7o;N@J1I7VQ|O0$K^<1i92NRZO>j5@Y#G~P(UaJ~IXKm9~FX-k#9 z{5%|MuMC?_$6MiUU47)wNEK$61w>SdcrWTGHbsvVb!cg^2c_i2>K~1C@??J48)LDn zZ!N9Ng#$5?Z5D=R3G5x3O`~~p%GsUfA0hJy9N7-&IwR&3fNeGidfw<~{2=v4ukLZN zD&8%xn@Ds0Q5WazdG9wun!|O znA}6X;ylt>-jjl&-Y{LBD)3W=?HsUtipC2KmTo|jG6)b$<#{13k;uudFOYZ{Q&C1s9rA3p0b?w2 zTt)zorGcf&YiEykd0p)$<#l0|%=V@Ug<_0++kjqQy3{4uyovq_uOHEJ_Uq5hcUson z(?*ajnYuY#_s)M7NBzQ+n2UcO9$<@YKqV>kae}y%Z5$_7k}@{3d<-^))KtFr>t6NZ zj)lM*Oz?wtUwQ z^~^-O+*wp03C$+ujWYdo3fe=b3_$0=sEw=Ark9p#a>SF8B zzO?Q9hA7;kWh|3|e-tH#*N9>AfBi5Ow(jngxG0Xt%XYcEg?~#LHgSkLuGJJ>ybD<= zZt`#TaqN{xcXcj9l)c+z6gvqfmxBKUi96mLY&l>6e6w3%CR~G!CbLV!YJZ>X^Pe@f z8m;@KM*cV`uKam9{nU$flE$ua`B0c#WYI}}EHMFkm(%1M{{Gd99q2+$XTnh6XY#@V z$2ifxZ!2v^_wrNcqkiB~`c9mW5xmq{7r(7yQNL3p(RToRMe2XEpBoj`d1CiziyjgG zQq5{*l|;qRFMmCV!`*);flzTECp+wYP@5IqQkJsjp9|hDAHCi($lLQ$)>@^t;Cv10 zKO^s3js9f`=EoY`A!`9`4no3$m7sx6VlJ(jxVcedv7bcepSd3?5lMZ7x>=4e#szWi z8~^YbH)*6@gkAXoq5CY5&laVo7VayXYt)0P|CkEK;!Z<4AbanPq|7w&A3D#|QZH>R zsaIfovDj4S3${)$%b6#}(&9NtTK!MGc31}eXa9)+Q*VrprRBQK>@9~ShrPocJo$wD zM;W$Ml)>kU%q5<6UwZIW@~F#v7G%-a-KIfW29ANUGPjUV{Q%}aI_Ro`6v%DAL)_AV z6Y@4F_Ft0Nv-4#igMdRUjbvckN$`X4sEZS{CRa&J=YjTLdgH*UwF5NE*=|0(97bC* zWK6;p7rV4n0rR!FG1WK|xawVrTY>#ahZ4aVXTX*#oN0$5(JAJ9aQ2ev+vBO@Y_anl zq_#xU#_RYb2k1_miS|dqcjNq*-ws7^XlW;eKgireuH{nY@MqwEGG)Il>ga4{v0xg!O7(_GN-8*_|hM(P*j2fYVibrZqsDCa_=nd&w{dMt~=1#)8poBJE-to)vd1 zVr)5<**AqKdi!*mZ!1Y(EUsR5EpI>GX9nJ##|~(!*}r>Ojt)G@R(|zC&DEcPoca8y zA2co|ti}J>Sg|BTb~6jwusr$A#*Ge4iWBw$iax?`b?sdM-pDW96XfgCO4k>58%j{?x#9N>|W7(k`c zh~xeK@gFY(^`)oOIXZyn?&@&y`%SHq-Ebyj;Gu2K+dpir;YhwF^E>A%-TnzL^CQJM zH7>b)T!O3&oJ(IoHuS(=7B-epxfH=Fn^wjX@ymbI98Rre5>O=@oBW)AV^+|KN1s-= zxv!gf-VeNUsKyhaAn2W&%my(!0C<&y9!b00wp)pWHnzQhGslFq#(rF7kFGV3OS-VN z`EL1I;q7}FUbRt0fU^2bKGcu?*mr2W+HI=H;soqUA(8Or5_;;Nw%NO+&Hb!rJR$j? z%7|ZWIRRwloV({Y$=B?sl!Hh6T8Ltut-?Z%)Wt=C1V2VK-egvg6VaX4EChN+6#zirE`j9Q>Wp@moIK=C_zJ4B%S;OFjBNA3hlIo*CofF6Rx|@CB6GR!uO_m zHC}6Re5^=S4)Jl{;Cuy;s#_A}ylO-+xp>RB45!j|QV+j7l%<>@qUvW-M#cr^_z!AZ zsSH!BDyTdJ(p>y6wVg#d^V$4oiu?B0B9iat)jK|*V-nW6A)MQ-YWB)`Hulrn_TYls z+$(iaKGe*$l|PNebc^Et7SQpQt>pQYJ4HdaX}Px=_O`e z-ac0OX4_IZ7sQ<$QJz&~w{G+EJms9KANW-0u}T$6aS0C`#kQ$oal${2Rx7Q*8=Bj< zmgehfh?>Ae-W{F*w@`Z8M+f_a3n2FDX}xyPTI1}xwscGy^1ITRB0#ejU3f&(vk~I)X!~BuqXi(tx4VT36zb>Z|ryu zhMs$!5hGNo&$VgP{3w6o^Zv#@5vx7eRr|#>Em7$RDJ?ux#MV}-o;_?h8n4|Mdk?Z- zLY+8|_1*2$qKq@r+~4=sST{&1E=o4uU$owsTKg?6_E+r+a73JQQ>cS*${_h{3?N=~ zh*@0Az$}>Q?h1PG1$Sl+Z<}Lv)KmH#h#G*i&Kp{vyIE>#WB%Q)KJ&epT^Q;54Hc@| z`x)8ZUuOjl`YU&44insi?^dlWDP!S*XI88Xpn4f_@2{LP+Sqe$+dB3iSwoW4X>9gV z4UQjImzSqIeiyVerNeTq+jQLmlLziQC~^DS{PP64-T##@KS3w$QMQI??&aeG+KB~_ z*|?N))=p#{Z`>2^4#(`c?<~)-rE<>6CXo^3zNexicoWTDBbh!mJdxapXT7n~t6bLq zPS4lTU;&Y5+@wjld&j%!gdH$?l120sRv>bE0t(ccFMYP-FkzEvXTXrCGJpNB_QP;?z!WsM?SG?Z$!`g56Z(CvW+wzt?_b zkh1TIuxkK7RtT9Iv+tD2`I0M+CG>H1PA@;MBuQ-TkK0mi(WOX>;_RrWM~gwb>&fK+ z*2s)E3psI3S{a&A@#f21me4#Btk4aGP>-I9A+#g8J@IeH_y@NJFE3}~^CKH=&VMW7{(WpUXyPdq0|_$mPDC-St`ayD z@7!i3PQVA9iuftHS-)-AhD-$And@*OFygErK~EL&Arx_}0@I{uMOZ2(u$|t{g#U6c zAv@TaVt7({adT)Qid4;~m03K=CoFbU4GTK;>`NfIsMb3TT&!u?`fy9o5aFLj_Q5?G zK?_&U9%@Y4t|jiiJXs{Dv0&I}Zu_b_26Sb2u(Mg5(}ZL#dMnNw8HD*ilJR0iZ(^H0ug*El)MfYzFdgH=O>Yo(E0K4$Ar?p+sFtm zRCQZV|Cfse?o-}%o&;$28z&NOvMR}SU8tIeYJ9cgID z{yV3upCeb)*2Z_B%#mWPX}cgxt?*+kUwm7DDJ%RF7wgIV#r(ZP7ju~B%^Oj6?5(V- z4gTtX%~zuLZ>4ztSKFO>bO}@aakZ2Xoe;yB&0okXO#Z&wDxpAJ>J{d+UB6FIs$4?NG^`8@&m zS|3UNO`atexqD!pT#F2*y=M@mJoi<0!)jkf)-t+nnH!7YT`foCITFd_z?>~dvUn0+ z&$p^pNRgV~bvhqrs$RTqBb9p8crJ6kmDj1zyS|>%;K@&^Nij6py}DPD{U8Bq;z0$d zHw{Dr$WqlbXMiWF|Qe9zh&iMY4o!z-J2(|_~~WC%H2exnqw(Vx`)pxQP(q@kmuBk}2X z#K>~HDsUXF#~aWO7>rJh_#jYA4QN2wXtZn&r}5d(+vELByV?`_k~Ct!cK|+cbqY@c zmubTe)Ndq%K2JvsCh-m%#Cyd?D8Gv5<3Et-Vxn&tTDfg`oPDD}v-ZnupttxkyuqY9 zxaP-?A83|0Yy_nmGf+!DTq&a&VyV{FvuF+D7u_`I=jRY`8EzKjvhZhBAnb_UET-Pl z11ZSwMfaF_iZ49WUTEp*D(BfxZ86Kx|9Xbu^8RVZQk$Kg6p75tk_Z3-yuPU<4~aNN zb$-z`b^4I#3%@XiU#95uVsE(2fOXm#75F3%A3Na*7>M0S&y$d+cuC4dMPz0`N1qZs zXb!FrO3(;Biuf4b>g0NRati+nar8Rx#xU+8$It&KRF|#(EJ~TCk3go~D?;F9*6XjF zmJQywrz;ugml?jWBV*&0g#{6Yug-a`#<`Ha)P;|9RQ~1SsJ}^Wt7!)%Iq6mKB<|J| zdY>+>Q-|z~9~ZfseIwII0=yIO{hYW`9*3&}uFD)?4z11`J;Kq0-*|_=^aPeHe)NQ0 zgy5|P&Os^LH*lQDR#s`&vfjXP`I|vAWk@2YNtJ2!NM7$z@k$SM&N`pr#yR_;oCZzj zmYz9`^m=!Tpc63y@m-TomX&Os9=DI9b=&*Tq#uBvxccv4#;`*jyv!1+P+L2@S|TQ8 zuh!NMxf#2$Hf`|8l*5Vs*zug@hSj~_QX{SRc6@@+U=LWw(7gk)`i5So=|~x@ED9B} zoJTPUc{f|KNL{8~@u0HtOv+{8RU6T^yMGq-fr>z;X$$^%7Kjg{lB#Msgj~t-(PDLv zm>rAesSCPzYR$OgiJf;f#;)#q2JCr`NAeooJ}>XKE}rv zuLWnBOe9<@&} z>i0e3m7BJR{F_d*T|7^BcX1&otZ?dJ3w-^JULa044$XNVIB_z*j}A~Pu}w~M+CMz> z;W-SDKlfj&o$W|xX)xb*0%}J_Aho4NO!ZX~#TNr$rH}V# zneWmr5mX=e_l;%x%gNEth&!#)4qWUGr0Jj75)K{&nHsT*&G}#yUD*k=ewQ)JIT259 z5{6#`*i(48GzbJG0R7r=}s{$kiDq1zMg=GTyHGo;^fTY&-_&eVt(X z@eFVl2+|!jm<$X^D>9Qm zyy<|htv$CVvFqH(8x}-0@@1g%(EAIwILIB*0hsZjm|JI79Ow{BhM-eSN@F^jxX$#n zwX)$-KT$!Mv@1AR&UbnOhipFHf6gUlg%EKq^U%q338Iuo2yqOkx@eh1R+qXui8)0eEG5 zzREpAFYz9@Hn7g{K$({?Az@;48r<0q0knI)abFqqG`!2glqz_EOB|m+tj2(la{-&_l>^dQyp=7Yldu21~H4!VRa_1bJhKF$; zi8)Roa(f0Crs0`7_$y;~@(8+6q)!Jg@MH5_!2vhI=*57CMRUFt%=IGWqNAHAEVjwT z#X}^aA^TOnA|^mk#65G+35ul{P0o(@YHROg#~9h!ykCY#2RdC`+6|caQ>@$#FW|ds zF^uQ;5~y&6j0*Hk{{_Er?qnzL$PrQ-^cHp$On0Zz7bY3z)+Rluoy9F^9mG-uax3V2)YLTLGFDS9fBGVb@KgA^1_b9y~3@_9y zJ>$v9NEfPZDzZaXy~ajwTcho_8` zaRYmhO>N6756jpYh`~fJsob`~ad&n{B=8dr!5aFc?}%`_HgeX9_v%>3S*IIc;ZMy{ z{s=E}{?2@DPBY6-LRKZ4{ZGcn$$ClR6f;j!EeMxL0$#?NYzCa@?AD`b4EU`%S?wAF z+>|R8MM*4;0Fx>mXLA*?dh3sH-m$SU;gZXwopdAiFKJeh`xt4><(S%xWC=L}#hRsZ zLAvPbZNVcs5u)Ee{OtVjJ7xAWeLep+!9^&bGW(sUqpbg-Ge}V!BLneB-_c(=JXdW# zAd{GTgq{Vu{4#?qs-M!x6!P%v+bQ9~hs-ZQ^l)DOPW${z;I;}0P8&kYT_9iD6B|FRp~@qZ z6j)^M5pF+W*A|+VKy363{3XehFr)>(&)C7nrLRGoG$MFXMa`n$2PL)2ZO75i3qh|( zpXk9)leaLJ5WnuJI>+qeQJ&f4?B9ra1pvReh_&AaVd7->XFy;!yWi)X`q0RQwy20o zJ3G5~`I&aZ(n)iDTNllM0HblECo-W|k7jI14`T6t#y0RQR$GJt~-+hh^cb}<~< zJ7|StjRK)89^F3}l_dOcS3J+AMF^m^Xc#Co4&!D;WKv@U4iw1dl!}g;E+|3LX3E=9 zL$@$9(1n9#fX1{4eNE_#IU;Bl(2LMJZmM1yAG@KUK{ZR%{m>1ZC2bzdB$E+g)HN!X z^)}U|rvB#f7l!n&6LIJf#Ml4uP5(TfD}dy@23i4yF%78 z@*!@fAHu|~R+a&hhgapgmJS5aD!$&TZ5aX$Fu9es6JjhkI4ohL5r}j}4+8jC@WVE; zetj(GP8qot6cE7P`7DACn23(7Z%~*Suhxze>NlX1IVeF2Pa_J-M%dMhE$CyDn#BE2 z8s{z~ec)X)y^bI-E%Peml_cJWQp}52mW#XI(2$4CzUbn;&&^fxBBUo$vIK`9oHLW% z0l;ZMBZJ5rJ#OK6t?m9 z<=3;v6SU5_!N{-(bGbXAiK$MC-i)yD@Er0XM8CW(HaR$(m7bISa}DnEJ&%` zyjM#3hrH;tn7!w|02*@Feb=A>S98jQ2oUfsc6;L92D{v1;iy|U8<)n+hclU(?-3u; z+KZ#8&-V9u?B*QrXwC4Og$vm{@i%HJ^c&%(JHts^mg{I%MFD^bz{j0O_(~9523Tpb zo4*73bTM0@KY?;Cp19%O^v@R9xsdZy>mG^`paPrRtgY|9V2ZhVZ}f!D@eL$L?DxGp z3(;)9F~ttLA1&HPeK9FbpsNZT0pAnE8=wSzkFcaL!+moZz<(13hr@MZJ?R!oM;*cU zqYu}Vz9ZPecU$>#(Wkhw-jcNe96mKgq*X@k54eD(&MibNze5=LyFO^O85Z^}o(CJJ z%L>r+Hh)~wo5uZX-g`Zq7+(7dV)_TGX_01wQ-0u6x~6b3TW8&@kwZ+gWWtam5OZP; z0S{K~m3vu(j9BuA+T?9{-#Anlp{-XG9%hrR>VQ{!M;}Svu?4#WKa&!`USs#VkMc@O zl%H4=r0sgIO7{PU_OAM^srUVl5KyU6(&CUtYBVDqAtF5xloq5xQhI{O=r_$kT51S_ zlt_z`qof<8J4WZ{@Hc#a{azQ>wO`oI_MCIy_p6?BO!Fcla}6+%15&V@mDA}IEuXM@nx67*uun4PGjR)9YCT%|-h-&!$I6eNe$*mV9FS@KW1MO- zyK)iu4K;1lUBv@=28y*DRsa*W{s?0M#dmoOYv>`4&=m`mN#6sn&TARkR#jOHlU$CE zjsmLU$IF7b9L3|*KHnKQfgd8QdV8_V)C*d@=@PqKKyAYzcpCqi${wS!Cn^0!}OjyjG>d1*Z}GqcI9 zM;{^I4Sh-Y0Tb{_Jl(|PnautEgH_#PebRwgIzNfjv+?$Jm4_`(NT0`|;Bz&*TvgCyg zC%B>gM)T~D+b|KSZwc&9I-?lUS7sO(FR-?$g_(D}2bW;iX_fPJP#!)1GC$JEpU zT3u4ISrHQx(=Ug^ev<2N8;i{RA}N)jc>&N*#n5FL`%VG(RB-E)sTwCIL96oVa5j=r z#W-xLeZMS9r z6N$BVbc8*m{w=$-$k>=$Iicg!$eX_H8-xy7)+AKi^LQHK(}?jv_-?-ewoIdyJL@MOlOmPJG+^BalSeLEN|QcuC~ciB`L zz7avIK8MZyF$jAO!U}k~(f^f5PfPQ~I2q1%Ctnm}XlV1ySK5@9zS{SA4}UTdF~@Xk zg)eRA|Kof)8Da=>Rg0-AgbqDNfaqB+l>8+QBkjy zS4{pkQuJoX)F!eM<5di$g4bW>@IJ4vua}3Rz3Bekm&afGVz8BzL%|d>1Vxvb=|cwy zQaO~uXk}Wy%5Sf)*S(D=`$*zUSY=xjjLQ5dt56nSxBnWcJNR}Du{rkbX0EFfLZnz; zoby4RY;A@^Q-(LCC!lCjx*Zan_0i(Jig4pSOys4bX5+0gkC-46(g=4WxLrIZpG0Uw+|r8vV3t#1yFUJCJE$?xjP@Xl_OY{SHld#KLj{*FjU6#QuB# zPr`1-!|%W_1^C#oQtrF6gJq^@;Vi5SC?*V@4llC$egsmOeJXa61AY{3ul;0)mJbp{ z(dPQg>A!;wlA8iv?+4wq;PoCqi>Y+<`teJCx^=UKB6-h@-j!af^Rv6d8r{LDpLo7R z+&hh(SnIlmu}FV40@`g`_)yqf}R!YNT-_e zb%L#P+N{KjkzHQ@j}d5t$~aNAR%8*1*67D{CF+vs&e`c!zUhj$YinzJadmYi2By5_ zj{NAuyx@gGA)E$Cxxvz#tUYx|A6yq(!WdXx!pBs>p)IvJT6)ogMMJ;gX?zrkt9nco zeg~kCv-X>mtyb`ZE_Ey}sYOE%Z6#*pkIj_zyPB+akk9XyXOF)Kw{0<==*GU%_bjii z9Sg77RoQU7uBf#ZK|4NChvbq`|GC`Og!MZ_%VBzg+4LNSmUiKXR#+<*wNAZ}q8jbu z)bUHX3WmZP8HQlCaR7%t)E>j5Zk(B!nJZOo4Ry`GyT}9JQ3yYWQs`sC*wODz>e?eI z&~#K14J*N(`IF68fIj64V{Ha!$Ev2vP>8lS+*KGM;H(tb-YLU+=ZdTi2$4EKG;~q) zKPYHBX{EhbJVm=U0Dj<9s=f)dy|%A$D>PuKVgpZvIzWjyl;Vz{I5T^b)vbxjhvotT zciT1GBc9oxHOaFme5p%DXJX&{j40FKdS*CVrSAtXe}Ql*R-HIfLaWP#K;KtFwC+e6|G!VWTqem)GI_cNz(}oSyV!weXg);V7>_) zc50!?Q58}@{6Uu(FH)I&(&`-FWwm03RQvF$+1SA$v*zvk-+Tkajo`mM8Zi3R(rz!r z!%`m&5wRGJv8RkPMMk!ljv`Ba0tOzC!>gWj%Os+_UR`S-WYI>kv2uMuN-PI{awpU7 zPe*l^;L+&gUW%2ZHVq0J5p-_IyZoYm3&GdU!g3>Xb3sk+-Df8SnVb{R^&-k-I^5$< zXGd5IEI029)yFrGe|Lun5FxxSIMP2Skyb*zB6d5K?YQy9OB zH3no=T23nBOAd?-RSrXY>K%}CX_X_wB$|=z08=Lb!I5XDMm>I^Ga{&!K6=@%&wCX8 zT)@|qr5(=MQhJ;VA}VOTq`#1R9`3glLtR62b<;#ARn6_%wm!3Ul< zCNnE;Y!Rcz)&)s-?soXHLEKzCjAg?SeCL|L4IX|)tOH$>3#~CnkzEw5tEj`x1L4w4 zAN4l^Xi6i>hqAL&q#eLsioLxP3DyAYA_lr1fEdgj&@e440Qh`pZ*On*^zu5kh)*D! zJ1InU_JA5RMyRde;kJ#aOR0n|I7S1}c6o+7$r7>drgGR;#A;wJJLl(XGTlDD{M#YxZOk@-DQqGqifre~{bURjF63uMSVDo8F3Fegt`3U} z#Ui;amopF|z2)=Tb9x#6KGX2u#G)O>?RDcu8KISH1*Y%LT4#}ast*y80pg}F*PPFw zuPm_3(pwU~!~-@`UXtaAuWv4V>oz+xB9yyCxc3h7m$2M|2%Z?VPoxY7NI3!WZ1Mwf z-=&j&*`#GvO-*}&W`-{vG%K$ghkAdH?FC#L@rhRn=_s=Pje6l>3AtIb<)4ULpA^+r zfR~$q`Vb>?Ojb>RVtV!%1T?f1VO$}K8_)b6*gY_pcOj8yh)B11m<8kejFM2sNNuGr zV`|{<-va^AYQ!2dB2y)?zp|wJ5%@fzYpD@)UZ=FRvqKSoErEn-QACJK=t7Yzz8>-U z-JoL)+zKmYNF(oEqSFJ$-w5OwzJ8L^rqd~l7qJ(DKH3{H?L7mY^=|v2(ZT} z6vXNhwKoPpKds?nM0B+miXliq-~*2a%@eOCzIC8ux13zv6k^6S6RE4_kEZlbK??Vg zQV>cAEB@mS<{plOYaIMX7AhHv>z9#{*|do}2)Lm^_-CLVY^65E1|~Cr3Rb3E=)&2W zytY3v`On?JrCvGSr{lK`|6@5;WLd$RhE|nsuh_j$&~lAE%|&@T0}b>?Vc}{`k(-RP zwC(mJ87@lkb&?G!1%(*}2w2Yqh_CQq%wf3I`JVthU#@J`i>9DJKv)5tO&1~0c(c2d ze(gc34?I9`!_^>3>H%Ns>n{p#OkJ*=v-9#o5T3Q|$WeT-NmkEt?q?Jy@RPZ_2d%Xp zqcxf!=tC&n{{hrAv;VEJ1o%vr6~bs-=Vm=)kQ#c|^v(FV+U$_*2R4!&^8$(RsOP8J zbe{{u(I?jJcv$~L-af*;5uvJ}XZgC`9gF*w4%3dm7x#xyw(BTHG>VkKw~=Hvm-j|k zQqmQPdVznA2d)#GGZ1AA@s8Z1jeH;|z$2fg!KroB@R9=DT=t(45Aj-m8|5yz;{Gq8 zF^HuW{$Vr##r^GJNVM56$G~{?e7T2+V?cINwf_8<8JZ3>pKv9Sy>MifELp$=6Am}dJ25jA+FJCPT>x!-kLcXx*~)YcSAWJin|2QbQ~xN2kt_=g58DhuQAUu&k5Sq9wrb1+Krf{~OFl@^%4$&=e%R!emBf9o zWRNHH$Ht$`)%^IbLt_25SO8GTYk6G3zYMJ#I4nni^}eItAeIScW+sT^%yyzRY=Apg z-pD1~Ez```&7vFB5^_%vmXuHF@gz7Ej(`yW0uAw}oU08rUwN>P_V4SHJ=Y)q%G=19 zSgrqV6YNnUUja>N48492X__(iWR!w~Dx3@GOBCuekth*xfHM36lp4O}hPZmqnQ(>P|^G)!PTC70nYrJvV#X zQ`2#CXl50f`uRIJWRrGR$*b0#pRq-1I;zAO3c1erkPM#Zj0$k#F}@t{C(j;J?V7j0 z$d_)p^yW`{@Fs;n+mL!857YTiY32#5b;NpLE`nEuAP<8G48Fx^Zl$IC+P z6@}U0uK<=w5gDpgjYpRJ;{#S~k<%BD$m%`^5>8n)9BEgiVY-6R+gs5vah)ys=-`DC69T*;DwOpl1xq(V}1uv!Mm*!XF*PejO~G&gCrzsHxOs2kzp|(~{H*Cd|5Sb+1EK z|Cll(8d1aiurI7M208?tJ&barlauE|$;ou&Z3Fk)_Wod`hm8F&)w85luJI(MzrGGB za}%lYOQXeGuosc@@gCAwwuXjd5$aRzSSR*=vC6>0dKta&>?qKwShnDk-)Wf@fVbBN z24BR)kXHiDp924TEQ$minB4V-?;vz-eccFfKE9c^d*dn4Htk-75dmEIO&*-}5Z1(K z-4Fsf5UQL2&2zKK4~XP_9S+IJ$N+@%Kns_Q39{4#Redp$Y?)pD?^mIdrKryTcwK;7 zO3g6X%&OA<8UDce0)Wqta*21l=q6s1e>;GsJ@kByD4PO`)KBG3-pv$gvcfFs>P4X0 zp;F(LAz$OLC1so;%8I{TJhzCs-0$&sx8-i|&^)CV$+?T!AT~p0EM3ZdWf0q-KpP1P z$Uup!l)}(HG1c!SkP$>!59xr0YBv!u8>Am(Tye+&zEVN-_qr&$ep~gKy2D`b&2(m9 zU|>8mRxlY4hN5&c`Qz?8Y6N6rrO=ID*vaP2sqJgKKkGw^ z2uYL&_znFE%=f1L`%Qi{&LB{0;X*|2`dp#k)sEDQGQ_qzHGQ_u|F1 z2E>G_5r0(P$f5c5(`7mezj?P;A1~A;zp+?F+2Lsqnd87wcHBo>%P4LL$K?HpZNJda zL)#!e|FR=sazp&@o;@B>wipGX90$KOIQ448Jq5y2po_mRj}DR52k`u8fiD~|R+;0| znxXOW_b_lTcj8<#pfN2cB~vWXqT!Ft#XFV~!k#Bq<3U8Q8bO#Z{x-8^Pc`ARJO;uz za*7#s0S_8gI0O-mT4MwAFnp^nv2}FxXB|*;kK!eNRycG-LMh*<|7&9=8pUj?Hv5_e zaiy%KrKNv881X1KWh+IHnYj&n5;ei?}ZF(`?6Lz74jmwv_3&Gl9i&{4L#o zXF;#RnNr5;Zol30l?6T<2-t~4kny5!v1gZ!d05*I$eCyKpKb_6-R4lJjRR&0QBhIM z#xJTuN7D*R<;LF^C-lC4ep!eS!3Y{Ht`002;M8zfGv-b8u0ak?-%Lq1`MZ;_z20LE z`ktSkzfpP*5oo?N2iNbult)ns*B|{xzfpbk3vo40Mn)DJ(RsxkFU|LjC|8i6d+JRY z;oM1jn-FuYZr8FKO@3A?g<3)G4hky-nJroE%ebfx?y~(00MZkY&0yI9e5tgngLmeC z{tE(+2#AYiXl8xI44wSmZNoK$fncQb1C|}(wt+xpAvWRITUrl4j3`=!7a1wx?9uW; z*VpZ-JejN8Rl!tX%atZZ=Qh=?a|5xY!A-f_dE6oWwwcay)R5%Y~QOQ z0rq*Ku11p_SzRr$4oF3caCOPCR71Z^!_9_D4R~Rs?V#+Ae=7c*-l8-gC!NF3>C{gb zsI{F8`PtplYB9gs5Vr9znEHTVZ8D5$Z(;*npO}c~Skk(@(YfU@ZyDz=EDSxtM5$tP zZocwGm@FKyriaw{dmp5ay{igWXbs7Wl!VxL8zgXF1-Wf;JR4I6|iZ|A2y*iQt`Si=g@Ao_rCB5`I8)$k@@+HaZz! zwu)hlvQ?%FK~v~0_XNy+v+u_M_&^wN7Eve}OkJQMyHWs0q7k|7NLuH(n7FtpezuAx zf+Hc`P?im_L#EI?e}{BvACQuT(n@8*7cQDseO~OsKYDwaT7(87B7bs5lBoI?Fk1D+ zb}Wj4Id3l{8ml*6Pr4Z+g$p*WOX27`e@;#tP~mBl*->h|Rzj*O;zaG+TBB17VO*km zex+HpX)ao0B@07kxAA60FMQ3aN#4gZ|2-_Q2GtiS3FC&u@u`7Gg6HiJb>D}jeEyJm z6zCS$UNo5*5N}-sx9vveBJN?ts0Wq3d=e{alw6ULeU6qsOw$W=UVZP#47pM@KRtZ1c z6Z##5cvS;fy@aA7a@;QB`TbfdM_jD2{YS9fgCaH;o2$hLKpvE3QPm!qix^K=&qzr5 z^>*dS!eE^$vf z6xD>*EYvEiiSdx$CRizC*R@_8R0Ln|--1JfSE^4KSXocSkh;4gDFJg?!^clmRl@*V z$`kviloBG+@|EN3%SD}$Fjd$o!Y31BV7(mi$I=Wj;%w7LZ&kA^!DO{V{C;Uh&iDLq z9bsf;)A!kj#9XDyRzeuZ%Ais;MwV(pPadH?$l#%9e)1J?Zwl0t7A~%7=1w{$HtT=> zWX03gwR@NiY*0QAG${7*+L^8cRG8*OspH6^rE17um(nX(cW3(Pwt9v@u_c-d~`}+DkF_YAu zpV6_9+64$c$cVavI4&w8IH%Yotp8*hq z`qL#`rhoIL_sTu}C~D@C0|J4&Z=Vssr>e`YJpA8xIth-1^|6}TT2)eCWJ4o*s3JV5s8ODt&`>C==p=&JQ^2yzGteq4ya+ou)I|H#nRH2K@ zXF)^k?Wr?6Ylx^ufv>-fWwIG($=-kC_k!I}NnFC>)chG*cJ;+X+l4?y_ED%9{Sj#D zSuChY_V~QDwUvp9F^x_r?{~=4w*(&XyBBl1BRfK*V}C3w-rg@;XD5g`Z<^zWKvR8w zR$uxe&RlG0OpI1WPR}y0uS>1_u_9<}9)MJwkVeKB9ODPH706WQG)j)jCmO@wYg1_k zlyAPqj440|b~VowY_6XMaiSSa-@uN8l8Lu5^C4b8{vecslYoT zJ2z2lh%-l=3_IisRFL?WA_D~k485NMwmJt=8GPbSpFIZ14rt7^F`Sz&Qs)tAu5d~Z z%a?8!dwcs0yuFNyF=EBN=W{1o9Jy+-b~elu^M-(zR4>IEYxXem4=Y()d2@4f5DrLL z5`)$nd~nB3&d#%3z(<%z`Gk`+#u`!O@0h@^i+LZu1blGil@4F($U$q!L3cx2|NB%x zM(PO*3;Tu(xKSOQ6ayxIIv~DW2+6Hwt@NgkF-QJ6hPE>+!dW2a*LM&3cfwJsEPjK> zKY^(_1^~=vkQ&{!hXIV$%;1?VCAd+Qwi0)uRvRUbD|=n{7if;@@GnVdCiqh0{`<>U zp?7_Z_)Qz$pYN3iY{oQ?hb0T{PO|ggx0=`_!UFlH75};B7jU@;w0<2?1fX3v@L8^K zl_{J7Njwk;pZ;&mx~i93#Qga9m>keUo_`pB#||y{=ebt_YRcj5Z?~+F2c7EiCQW`~ zX?{jBuLc3QrREV#1Kq;5SFX0xmehSK;LiaqcPw&G*QU&FEfUq(g=@I!yGbul@r=VX zyz_S0CQ=+o0-_)`q;f0)bS!pJCq53!AV<*YBb$I2Gy)zAvaR8*rOb(w>fR`mC-8XK z!cUZojkA7)VNBHgrw9#S6G7A^PB@m+k!C9*-o7`*5+Idg{=OsN1RVm7sn_UNBPecm zY)~j&0{DB$7bMXRYwDM~fPGfa7k}ySWWG@iPR{ku0BN(7^>2Za z)tvIDUj&Jlcx_@l4j%o(fj+PL9L5Tf-fUc4t2<2in$Av1lRk?E6U+-C8WPc3y=D(g zYDo-GA5)^Q{D5Y>=wljIT@#K3(Ko zV^0V|G|G~A{k2E}3M)%oR5XyeWdS?i~$pR-6^!@x|d$h zV9JZw4EgFYRWcPKVr((;j6BydU;}o!QNe7dogh zkE-r5Pu_(82ZUwTajAG!AX}y`UA?|teb;+3S|k3UYLi^`>>{F~hcEd@4>_z{_X)S1 z@U=UIf4FC0{2hT|RFir*2gGJS1zY;7%<2RdGLB8{1!pWk9jON)&1A@mGBkPEq7)F^=I9L=Im{}a@QP&O7)61GyWdz>pd0QVB~Ddj|U;;@HL zK`FqI)-D3|^wX-<*S5D=_5yA$ApQo4d7W^fO)z}L7NlJ5cHc-tj&KbMIp2@llBtHn zKcaW6+fxA5ZuhI_Sv4m~@a|)(yX?OnmNu(=mxF(dC05kd=ZTUsaJA%TnJU4gVE$HM z-v$&JY#^-z+kWeKrj{Wzs_7`A)40%{R2IPpGPgkz7%Hs=Jdn%m!otnx z2^UQp&%*-9Jt04Y)gS(N4L4Wb zBL@4mCO>&4$4c1B31Z7FD3XPNF>8MJg6Ho-r1aqGf2@;{HdH!Y&#Wj3LvGbL{7X)7 zm&#ox{SXu^zZ;l-WBSmSULGFhn4N<_?)2Wk=VJbqs79#zg{KIrvmDiEgqWitRQ!?` zt5&86=Mvo}RuocuoJDx}Rwhv1R}8vSMd$6C>D^d;ERASNp5P}~lK@4(FTH;HyQxlJ zx)jL2kV!~rok>Ci2nNWoLf91q!~?3(EK;x_?Xc}RI=Kn!zo3N%_C!fM*r_%{TtDE5 zSGj2FZ+E=OkkxM-PvFe$o~;-Ri0b%Sf+rfe=GCumV-K<=Q@=lr5nicNBCpEOxP$d@ zmBrUWcs61j@r1mRhX}`4bu3tG02Cw!3=~qtwc{-O)6@GgPdK`o*}i8oXlZyjQxY

yC-;=t(a<)Il^_PgdDUjJ65qmQ(n?T?heyJ-_=Fz0HiNVN-&o z18L^g#>P39y59<;ntFq{gbOm5-pJuhND?9)CjB(Qv)WsDLbJJ@2@AVI5bH*9P5Xx1m-a+={1!sD@06TA#sk9kNP4&4**|?cO5XO|V72 z$EN1J4|St{ zsbBDbe^Si90-_M{`@3tyF|%B*y2$8rzB z!6r9Z97W!`&d75=Ute|KA%e5JwYwJ&*>g=5=6IBD;%asGBS8(ehv)peUr`&L_ z-BCjv8G>$lCl@j-x8Szw{B$KgvoOfH2-2ZVXu3EA$P0h!yre}mcmQ%%01FGtjmykW zPcHDFrNU}>rySCbNM+b8HBO7LK3Q$FxI0VvOXc{rb-3>}`Ft}Fu6!>Ey(#2f0S{V0 zd?k@QAPM~M@}O_!ZoYIa1~!aD<%%Ci365PbzA3zO^?W!7x=B+{-x4@PnCZWO2nJF3 zQdYZbmm-8NQdSxUyJUsz{}P&Bmk=+=DNvdRfZ|W*lYkomt0DwK4{|^S3d0{JG_Oyf zg2s^;fzt6VB*5$BZ8_PswY5bUApaHyh#WmA7~|C}!%%hwz5GYs%tgRO*KlyyB)-i_ zF&$)nJ{Lh`6m;u(!#aep!-9~_)H4Pf*q4Z9Sy>b6_U#z$@q{K{VE9S^{8#X80S7XZ zO^KK`b53Qc+D(-x`pMbu9Rkn^xJwTcZ=vE%Nf@i|{>@6;ralq^bftp z_@LG&HWDJdY0`Nc08?rwC6Z*_GZ$yO5Y~I%YUfHx2b_zXm;A??kbb=+6`Oz`QQ|>ECsz-{$0fnn)GdJ+e-LsVG+Jb-m-ATw zbYl2-jHa)#uCeh7$V?JxJ*W_5&y~VR(`H}c1<(nlhwnWG-jW)CWAsRnV;(R#dTZ%Q`CO37SA+Q(%Dt$(p90DSIjtod4O1P8l=6e0xx z-0OwqB9270$C`&Qmroe+R}K`=c2Nv3^`PG9q#P*ZR?XVx!J8fd^2#eWbqbJ^g$^LBX}`qi3hK{&OcpL_`HKRbr%1C%xwa z!Zt0EUjA@hXbs&%*+9|=%+=}RMXW-q+iomZR?%ns;L5{UxzQl&76k-p~M@3j`Yz5S4l4}zxJ+m#(QlFTCO z47g})(YX2Dq7C4ut10@2wJnV6xe~0HwgX_k(9d*{81G!mW%}DL-arY+|A)a5;MTgB zfo0{o1>s4C2D~-%u9D34A+ntOzbhP4(^j$w~4@|0GnyB*)E|tn8VyHh>R3Y9&kc zdjv2H2x{+VJ`coW;f)^c?a}9ihD%gEL?W%6C(;`@S;#x?Gf9~5gp!hyUh0WOX<&Pz z7WLmn?p{2{bj<*1xm7@?yZX!!7w(E!PJz_P8yMHLEx2w8ynfkbs{ROk6ruYpTDcr; zvG&aiCSob79jS?;e?3xG(5u9TF#)H-p0Nc%t?8GpW zhvhZ>z;hSmj8kg>$n4MXKI%9NkQQ`k5poDa{--vIDrd|ijvUZe05*>(Nuf0X;}b{| z$~D%9RI>nYrn>zS*hC(^WO53M<|vdPzo)6E#(q%nOj_3)`^?7n+sli?xy2*uHl0}kU_*}4N%jxp z%vMW;2)XkA%GS(5mCB;TAS?}ajwkZ#qhBljX%G`-IEAr;ArOiY;}#5vy*&?uz!fa~ z$bGfXDOJ|*I7*1L5P!U%zVe%zTpSe2<{9 literal 0 HcmV?d00001 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..5c4bffd9 --- /dev/null +++ b/account_banking_ach_discount/views/account_payment_view.xml @@ -0,0 +1,58 @@ + + + + + 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..c3139970 --- /dev/null +++ b/account_banking_ach_discount/wizard/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from . import account_register_payments diff --git a/account_banking_ach_discount/wizard/account_register_payments.py b/account_banking_ach_discount/wizard/account_register_payments.py new file mode 100644 index 00000000..2f8fef6e --- /dev/null +++ b/account_banking_ach_discount/wizard/account_register_payments.py @@ -0,0 +1,83 @@ +# Copyright (C) 2019 Open Source Integrators +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import api, models, _ +from odoo.exceptions import UserError + + +class AccountPaymentRegister(models.TransientModel): + _inherit = "account.payment.register" + + def make_payments(self): + payment_method = self.payment_method_id + if self.is_customer: + invoice_payment_method = self.invoice_customer_payments[ + 0 + ].invoice_id.payment_mode_id.payment_method_id + else: + invoice_payment_method = self.invoice_payments[ + 0 + ].invoice_id.payment_mode_id.payment_method_id + if ( + payment_method.code in ("ACH-In", "ACH-Out") and not invoice_payment_method + ) or ( + payment_method.code not in ("ACH-In", "ACH-Out") and invoice_payment_method + ): + raise UserError(_("Payment Method does not match Invoice payment mode.")) + if payment_method: + if payment_method.code in ("ACH-In", "ACH-Out"): + action = False + payment_mode_id = self.env["account.payment.mode"].search( + [ + ("payment_type", "=", self.payment_type), + ("payment_method_id", "=", payment_method.id), + ("payment_order_ok", "=", True), + ], + limit=1, + ) + payment_line_pool = self.env["account.payment.line"] + # Update invoice with Payment mode + if payment_mode_id: + for payment_line in self.invoice_payments: + invoice_id = payment_line.invoice_id + # updated discount logic + discount = invoice_id.discount_taken + # discount should not be consider for open invoices + if payment_line.payment_difference_handling != "open": + discount = ( + invoice_id.discount_taken + + payment_line.payment_difference + ) + invoice_id.write( + { + "payment_mode_id": payment_mode_id.id, + "discount_taken": discount, + } + ) + invoice_id.line_ids.write( + {"payment_mode_id": payment_mode_id.id} + ) + action = invoice_id.with_context( + payment_date=self.payment_date, + payment_line_state=payment_line.payment_difference_handling, + ).create_account_payment_line() + # Find related ACH transaction line + domain = [ + ("move_id", "=", invoice_id.id), + ("state", "=", "draft"), + ] + ach_transaction_line = payment_line_pool.search(domain) + if ach_transaction_line: + ach_transaction_line.write( + { + "payment_difference_handling": payment_line.payment_difference_handling, + "writeoff_account_id": payment_line.writeoff_account_id.id, + "reason_code": payment_line.reason_code.id, + "note": payment_line.note, + "amount_currency": payment_line.paying_amt, + "payment_difference": payment_line.payment_difference, + } + ) + return action + res = super(AccountPaymentRegister, self).make_payments() + return res From 9b37601427e4bf23bfaa3ca7e48cb3c8c4af5383 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Mon, 8 Mar 2021 17:15:44 -0600 Subject: [PATCH 02/28] [FIX] account_banking_ach_discount: Missing required field 'communication' --- .../wizard/account_register_payments.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/account_banking_ach_discount/wizard/account_register_payments.py b/account_banking_ach_discount/wizard/account_register_payments.py index 2f8fef6e..cc6721b5 100644 --- a/account_banking_ach_discount/wizard/account_register_payments.py +++ b/account_banking_ach_discount/wizard/account_register_payments.py @@ -1,7 +1,6 @@ # Copyright (C) 2019 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import api, models, _ +from odoo import _, models from odoo.exceptions import UserError @@ -38,15 +37,14 @@ def make_payments(self): payment_line_pool = self.env["account.payment.line"] # Update invoice with Payment mode if payment_mode_id: - for payment_line in self.invoice_payments: - invoice_id = payment_line.invoice_id + 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 payment_line.payment_difference_handling != "open": + if line.payment_difference_handling != "open": discount = ( - invoice_id.discount_taken - + payment_line.payment_difference + invoice_id.discount_taken + line.payment_difference ) invoice_id.write( { @@ -59,7 +57,7 @@ def make_payments(self): ) action = invoice_id.with_context( payment_date=self.payment_date, - payment_line_state=payment_line.payment_difference_handling, + payment_line_state=line.payment_difference_handling, ).create_account_payment_line() # Find related ACH transaction line domain = [ @@ -70,12 +68,14 @@ def make_payments(self): if ach_transaction_line: ach_transaction_line.write( { - "payment_difference_handling": payment_line.payment_difference_handling, - "writeoff_account_id": payment_line.writeoff_account_id.id, - "reason_code": payment_line.reason_code.id, - "note": payment_line.note, - "amount_currency": payment_line.paying_amt, - "payment_difference": payment_line.payment_difference, + "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": line.note, + "communication_type": "normal", + "amount_currency": line.paying_amt, + "payment_difference": line.payment_difference, } ) return action From 13443a9e3ca6b93b78bdee8f28f8e20230c5121b Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Wed, 10 Mar 2021 18:25:43 -0600 Subject: [PATCH 03/28] [FIX] account_banking_ach_discount --- .../wizard/account_register_payments.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/account_banking_ach_discount/wizard/account_register_payments.py b/account_banking_ach_discount/wizard/account_register_payments.py index cc6721b5..5014589c 100644 --- a/account_banking_ach_discount/wizard/account_register_payments.py +++ b/account_banking_ach_discount/wizard/account_register_payments.py @@ -72,7 +72,8 @@ def make_payments(self): "writeoff_account_id": line.writeoff_account_id.id, "reason_code": line.reason_code.id, "note": line.note, - "communication": line.note, + "communication": "Payment of invoice %s" + % line.invoice_id.name, "communication_type": "normal", "amount_currency": line.paying_amt, "payment_difference": line.payment_difference, From 958bc03d5d3d0714959a847e4e453436004976c1 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Fri, 12 Mar 2021 16:18:48 -0600 Subject: [PATCH 04/28] [FIX] account_banking_ach_discount --- account_banking_ach_discount/__manifest__.py | 34 ++++---- account_banking_ach_discount/changes.txt | 5 -- .../models/__init__.py | 1 - .../models/account_move.py | 44 +++++----- .../models/account_move_line.py | 7 +- .../models/account_payment.py | 6 +- .../models/account_payment_order.py | 23 +++-- .../readme/CONFIGURE.rst | 22 +++++ .../readme/CONTRIBUTORS.rst | 4 + .../readme/DESCRIPTION.rst | 1 + .../readme/HISTORY.rst | 4 + account_banking_ach_discount/readme/USAGE.rst | 8 ++ .../wizard/__init__.py | 3 +- .../wizard/account_payment_register.py | 62 ++++++++++++++ .../wizard/account_register_payments.py | 84 ------------------- 15 files changed, 159 insertions(+), 149 deletions(-) delete mode 100644 account_banking_ach_discount/changes.txt create mode 100644 account_banking_ach_discount/readme/CONFIGURE.rst create mode 100644 account_banking_ach_discount/readme/CONTRIBUTORS.rst create mode 100644 account_banking_ach_discount/readme/DESCRIPTION.rst create mode 100644 account_banking_ach_discount/readme/HISTORY.rst create mode 100644 account_banking_ach_discount/readme/USAGE.rst create mode 100644 account_banking_ach_discount/wizard/account_payment_register.py delete mode 100644 account_banking_ach_discount/wizard/account_register_payments.py diff --git a/account_banking_ach_discount/__manifest__.py b/account_banking_ach_discount/__manifest__.py index 68d134b4..511f07a0 100644 --- a/account_banking_ach_discount/__manifest__.py +++ b/account_banking_ach_discount/__manifest__.py @@ -2,23 +2,23 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). { - 'name': 'OSI ACH-Batch Discount Connector', - 'version': '14.0.1.0.0', - 'license': 'LGPL-3', - 'author': 'Open Source Integrators', - 'category': 'Accounting', - 'maintainer': 'Open Source Integrators', - 'website': 'https://github.com/OCA/l10n-usa', - 'maintainers': ['bodedra'], - 'depends': [ - 'account_payment_term_discount', - 'account_payment_batch_process', - 'account_payment_order', - 'account_banking_ach_credit_transfer', - 'account_banking_ach_direct_debit', + "name": "Discount on ACH batch payments", + "version": "14.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', + "data": [ + "views/account_payment_view.xml", ], - 'installable': True, } diff --git a/account_banking_ach_discount/changes.txt b/account_banking_ach_discount/changes.txt deleted file mode 100644 index 29e1f9d7..00000000 --- a/account_banking_ach_discount/changes.txt +++ /dev/null @@ -1,5 +0,0 @@ -init - 12.0.1.0.0 -Task Ref: FUL-02C-00-1910-18565 -Description: - * ACH Payment, Automatic Discount and Batch Payment Partial Pay Integration diff --git a/account_banking_ach_discount/models/__init__.py b/account_banking_ach_discount/models/__init__.py index 192b0fe1..e4adbac1 100644 --- a/account_banking_ach_discount/models/__init__.py +++ b/account_banking_ach_discount/models/__init__.py @@ -1,6 +1,5 @@ # 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 diff --git a/account_banking_ach_discount/models/account_move.py b/account_banking_ach_discount/models/account_move.py index 57e0975e..a749d283 100644 --- a/account_banking_ach_discount/models/account_move.py +++ b/account_banking_ach_discount/models/account_move.py @@ -1,9 +1,6 @@ # Copyright (C) 2019 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import models, api, _ -import json -from odoo.tools import date_utils +from odoo import models class AccountMove(models.Model): @@ -17,19 +14,21 @@ def _get_reconciled_info_JSON_values(self): 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() + 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"] - ): + 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: @@ -43,10 +42,7 @@ def _get_reconciled_info_JSON_values(self): and item["account_payment_id"] == mvl.payment_id.id ): if mvl.full_reconcile_id and not flag: - item["amount"] = ( - item["amount"] - - self.discount_taken - ) + item["amount"] = item["amount"] - self.discount_taken flag = True return res @@ -59,8 +55,10 @@ def _prepare_discount_move_line(self, vals): 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_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] @@ -100,9 +98,7 @@ def _prepare_writeoff_move_line(self, payment_line, vals): } ) if invoice.move_type == "out_invoice": - vals.update( - {"credit": 0.0, "debit": payment_line.payment_difference}) + 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}) + 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 index acd61f34..683cdf18 100644 --- a/account_banking_ach_discount/models/account_move_line.py +++ b/account_banking_ach_discount/models/account_move_line.py @@ -22,8 +22,11 @@ def _prepare_payment_line_vals(self, payment_order): 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.date_invoice + discount_information = ( + invoice.invoice_payment_term_id._check_payment_term_discount( + invoice, + self._context.get("payment_date") or invoice.date_invoice, + ) ) discount_amt = discount_information[0] vals.update( diff --git a/account_banking_ach_discount/models/account_payment.py b/account_banking_ach_discount/models/account_payment.py index 6252a2da..a03a2066 100644 --- a/account_banking_ach_discount/models/account_payment.py +++ b/account_banking_ach_discount/models/account_payment.py @@ -1,7 +1,7 @@ # 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 import _, api, fields, models from odoo.exceptions import UserError @@ -38,7 +38,9 @@ def action_validate_invoice_payment(self): 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() + action = ( + payment.reconciled_invoice_ids.create_account_payment_line() + ) payment.unlink() return action res = super(AccountPayment, self).action_validate_invoice_payment() diff --git a/account_banking_ach_discount/models/account_payment_order.py b/account_banking_ach_discount/models/account_payment_order.py index 6f7b666d..10a96b10 100644 --- a/account_banking_ach_discount/models/account_payment_order.py +++ b/account_banking_ach_discount/models/account_payment_order.py @@ -16,22 +16,22 @@ def _prepare_move(self, bank_lines=None): 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 transaction in bank_payment.payment_line_ids: + for line in bank_payment.payment_line_ids: temp_vals = vals[2].copy() - amount = transaction.amount_currency - discount = transaction.discount_amount - payment_difference = transaction.payment_difference + amount = line.amount_currency + discount = line.discount_amount + payment_difference = line.payment_difference writeoff = ( payment_difference and payment_difference - discount or 0.0 ) - invoice_close = transaction.payment_difference_handling != "open" - use_debit = transaction.move_id.move_type in ( + invoice_close = line.payment_difference_handling != "open" + use_debit = line.move_id.move_type in ( "in_invoice", "out_refund", ) - temp_vals["move_id"] = transaction.move_id.id + temp_vals["move_id"] = line.move_id.id if use_debit: temp_vals["debit"] = amount + discount else: @@ -40,8 +40,8 @@ def _prepare_move(self, bank_lines=None): line_ids.append((0, 0, temp_vals)) if discount > 0: - discount_information = transaction.move_id.invoice_payment_term_id._check_payment_term_discount( - transaction.move_id, transaction.date + discount_information = line.move_id.invoice_payment_term_id._check_payment_term_discount( + line.move_id, line.date ) discount_vals = temp_vals.copy() discount_vals["account_id"] = discount_information[1] @@ -61,8 +61,8 @@ def _prepare_move(self, bank_lines=None): temp_vals["debit"] = amount + discount + round(writeoff, 2) else: temp_vals["credit"] = amount + discount + round(writeoff, 2) - writeoff_vals = transaction.move_id._prepare_writeoff_move_line( - transaction, temp_vals.copy() + writeoff_vals = line.move_id._prepare_writeoff_move_line( + line, temp_vals.copy() ) writeoff_vals["bank_payment_line_id"] = False if writeoff_vals: @@ -70,6 +70,5 @@ def _prepare_move(self, bank_lines=None): # payment order line else: line_ids.append(vals) - values["line_ids"] = line_ids return values 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/wizard/__init__.py b/account_banking_ach_discount/wizard/__init__.py index c3139970..17731038 100644 --- a/account_banking_ach_discount/wizard/__init__.py +++ b/account_banking_ach_discount/wizard/__init__.py @@ -1,4 +1,3 @@ # Copyright (C) 2019 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from . import account_register_payments +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..45e8cf17 --- /dev/null +++ b/account_banking_ach_discount/wizard/account_payment_register.py @@ -0,0 +1,62 @@ +# 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_id and self.payment_method_id.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_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() diff --git a/account_banking_ach_discount/wizard/account_register_payments.py b/account_banking_ach_discount/wizard/account_register_payments.py deleted file mode 100644 index 5014589c..00000000 --- a/account_banking_ach_discount/wizard/account_register_payments.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (C) 2019 Open Source Integrators -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, models -from odoo.exceptions import UserError - - -class AccountPaymentRegister(models.TransientModel): - _inherit = "account.payment.register" - - def make_payments(self): - payment_method = self.payment_method_id - if self.is_customer: - invoice_payment_method = self.invoice_customer_payments[ - 0 - ].invoice_id.payment_mode_id.payment_method_id - else: - invoice_payment_method = self.invoice_payments[ - 0 - ].invoice_id.payment_mode_id.payment_method_id - if ( - payment_method.code in ("ACH-In", "ACH-Out") and not invoice_payment_method - ) or ( - payment_method.code not in ("ACH-In", "ACH-Out") and invoice_payment_method - ): - raise UserError(_("Payment Method does not match Invoice payment mode.")) - if payment_method: - if payment_method.code in ("ACH-In", "ACH-Out"): - action = False - payment_mode_id = self.env["account.payment.mode"].search( - [ - ("payment_type", "=", self.payment_type), - ("payment_method_id", "=", payment_method.id), - ("payment_order_ok", "=", True), - ], - limit=1, - ) - payment_line_pool = self.env["account.payment.line"] - # Update invoice with Payment mode - if payment_mode_id: - 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.id, - "discount_taken": discount, - } - ) - invoice_id.line_ids.write( - {"payment_mode_id": payment_mode_id.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_transaction_line = payment_line_pool.search(domain) - if ach_transaction_line: - ach_transaction_line.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.paying_amt, - "payment_difference": line.payment_difference, - } - ) - return action - res = super(AccountPaymentRegister, self).make_payments() - return res From 0ad89e30733e166fbfee10c4717acb4b61c37a96 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Sat, 13 Mar 2021 03:56:03 -0600 Subject: [PATCH 05/28] [FIX] account_banking_ach_discount --- .../models/account_payment_order.py | 3 +-- .../models/bank_payment_line.py | 11 ----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/account_banking_ach_discount/models/account_payment_order.py b/account_banking_ach_discount/models/account_payment_order.py index 10a96b10..d90b051c 100644 --- a/account_banking_ach_discount/models/account_payment_order.py +++ b/account_banking_ach_discount/models/account_payment_order.py @@ -1,6 +1,5 @@ # Copyright (C) 2019 Open Source Integrators # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - from odoo import models @@ -8,7 +7,7 @@ class AccountPaymentOrder(models.Model): _inherit = "account.payment.order" def _prepare_move(self, bank_lines=None): - values = super(AccountPaymentOrder, self)._prepare_move(bank_lines) + values = super()._prepare_move(bank_lines) bank_payment_line_pool = self.env["bank.payment.line"] line_ids = [] for vals in values.get("line_ids"): diff --git a/account_banking_ach_discount/models/bank_payment_line.py b/account_banking_ach_discount/models/bank_payment_line.py index d2e8f2ce..222c6734 100644 --- a/account_banking_ach_discount/models/bank_payment_line.py +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -25,14 +25,3 @@ def _compute_discount_amount(self): discount_amount = sum(bline.mapped("payment_line_ids.discount_amount")) bline.discount_amount = discount_amount - def reconcile(self): - self.ensure_one() - amlo = self.env["account.move.line"] - transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) - for line in transit_mlines: - ap_mlines = line.move_id.line_ids.filtered( - lambda x: x.account_id == line.account_id - ) - lines_to_rec = line - lines_to_rec += ap_mlines - lines_to_rec.reconcile() From 2ffda178580b0e2dff9875f891b2279f97dd9839 Mon Sep 17 00:00:00 2001 From: AmmarOfficewalaSerpentCS Date: Fri, 9 Jul 2021 19:26:03 +0530 Subject: [PATCH 06/28] [FIX] Fixed issued of a reconcie functionlity. --- .../models/bank_payment_line.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/account_banking_ach_discount/models/bank_payment_line.py b/account_banking_ach_discount/models/bank_payment_line.py index 222c6734..5cb597fd 100644 --- a/account_banking_ach_discount/models/bank_payment_line.py +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -24,4 +24,16 @@ def _compute_discount_amount(self): for bline in self: discount_amount = sum(bline.mapped("payment_line_ids.discount_amount")) bline.discount_amount = discount_amount + + def reconcile(self): + self.ensure_one() + amlo = self.env["account.move.line"] + transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) + for line in transit_mlines: + ap_mlines = line.move_id.line_ids.filtered( + lambda x: x.account_id == line.account_id + ) + lines_to_rec = line + lines_to_rec += ap_mlines + lines_to_rec.reconcile() From 5de9d081eebf57bf8e3a3bdac6fc7b301ea286c7 Mon Sep 17 00:00:00 2001 From: Bhavesh Odedra Date: Fri, 24 Sep 2021 13:53:38 -0700 Subject: [PATCH 07/28] [FIX] account_banking_ach_discount: Comparing apples and oranges --- account_banking_ach_discount/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_banking_ach_discount/models/account_move.py b/account_banking_ach_discount/models/account_move.py index a749d283..bdd4cecb 100644 --- a/account_banking_ach_discount/models/account_move.py +++ b/account_banking_ach_discount/models/account_move.py @@ -38,7 +38,7 @@ def _get_reconciled_info_JSON_values(self): # Discount is applied on the last payment (i.e. fully reconciled). if ( not mvl.bank_payment_line_id - and mvl.move_id == self.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: From d19dcdf454fb7e69eba0bf6aecebb0955ba317c0 Mon Sep 17 00:00:00 2001 From: Murtaza Mithaiwala Date: Thu, 30 Sep 2021 13:22:12 +0530 Subject: [PATCH 08/28] [FIX] Fix the issue allowed tho pay non ACH IN/OUT payment. --- .../models/account_payment.py | 70 ++++++++++--------- .../models/bank_payment_line.py | 3 +- .../views/account_payment_view.xml | 44 +++++++----- 3 files changed, 64 insertions(+), 53 deletions(-) diff --git a/account_banking_ach_discount/models/account_payment.py b/account_banking_ach_discount/models/account_payment.py index a03a2066..cf76d50d 100644 --- a/account_banking_ach_discount/models/account_payment.py +++ b/account_banking_ach_discount/models/account_payment.py @@ -9,40 +9,46 @@ class AccountPayment(models.Model): _inherit = "account.payment" def action_validate_invoice_payment(self): - if any(len(record.reconciled_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." + # 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." + ) ) - ) - 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} + 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, ) - action = ( - payment.reconciled_invoice_ids.create_account_payment_line() - ) - payment.unlink() - return action + 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 diff --git a/account_banking_ach_discount/models/bank_payment_line.py b/account_banking_ach_discount/models/bank_payment_line.py index 5cb597fd..d2e8f2ce 100644 --- a/account_banking_ach_discount/models/bank_payment_line.py +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -24,7 +24,7 @@ def _compute_discount_amount(self): for bline in self: discount_amount = sum(bline.mapped("payment_line_ids.discount_amount")) bline.discount_amount = discount_amount - + def reconcile(self): self.ensure_one() amlo = self.env["account.move.line"] @@ -36,4 +36,3 @@ def reconcile(self): lines_to_rec = line lines_to_rec += ap_mlines lines_to_rec.reconcile() - diff --git a/account_banking_ach_discount/views/account_payment_view.xml b/account_banking_ach_discount/views/account_payment_view.xml index 5c4bffd9..7c37a412 100644 --- a/account_banking_ach_discount/views/account_payment_view.xml +++ b/account_banking_ach_discount/views/account_payment_view.xml @@ -1,14 +1,17 @@ - + account.payment.line.form account.payment.line - + - - + + @@ -16,17 +19,20 @@ account.payment.line.tree account.payment.line - + - - - - - - - - + + + + + + + + @@ -34,11 +40,11 @@ banking.bank.payment.line.form bank.payment.line - + - - + + @@ -46,11 +52,11 @@ banking.bank.payment.line.tree bank.payment.line - + - - + + From a7d47811edd6f13e8f3f5cd25e52cff338789ee0 Mon Sep 17 00:00:00 2001 From: Murtuza Saleh Date: Wed, 2 Mar 2022 17:48:52 +0530 Subject: [PATCH 09/28] [FIX] account_banking_ach_discount --- .../models/bank_payment_line.py | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/account_banking_ach_discount/models/bank_payment_line.py b/account_banking_ach_discount/models/bank_payment_line.py index d2e8f2ce..d1f4a1da 100644 --- a/account_banking_ach_discount/models/bank_payment_line.py +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -25,14 +25,14 @@ def _compute_discount_amount(self): discount_amount = sum(bline.mapped("payment_line_ids.discount_amount")) bline.discount_amount = discount_amount - def reconcile(self): - self.ensure_one() - amlo = self.env["account.move.line"] - transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) - for line in transit_mlines: - ap_mlines = line.move_id.line_ids.filtered( - lambda x: x.account_id == line.account_id - ) - lines_to_rec = line - lines_to_rec += ap_mlines - lines_to_rec.reconcile() + # def reconcile(self): + # self.ensure_one() + # amlo = self.env["account.move.line"] + # transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) + # for line in transit_mlines: + # ap_mlines = line.move_id.line_ids.filtered( + # lambda x: x.account_id == line.account_id + # ) + # lines_to_rec = line + # lines_to_rec += ap_mlines + # lines_to_rec.reconcile() From d53433db55d878b06910cc9681ddd92e37674421 Mon Sep 17 00:00:00 2001 From: Sandip Mangukiya Date: Wed, 2 Mar 2022 14:53:08 -0700 Subject: [PATCH 10/28] [FIX]account_banking_ach_discount: fix manual discount case --- .../models/account_payment_order.py | 59 +++++++++++++------ .../models/bank_payment_line.py | 12 ---- .../views/account_payment_view.xml | 13 ++-- 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/account_banking_ach_discount/models/account_payment_order.py b/account_banking_ach_discount/models/account_payment_order.py index d90b051c..329e3d64 100644 --- a/account_banking_ach_discount/models/account_payment_order.py +++ b/account_banking_ach_discount/models/account_payment_order.py @@ -21,10 +21,13 @@ def _prepare_move(self, bank_lines=None): amount = line.amount_currency discount = line.discount_amount payment_difference = line.payment_difference - writeoff = ( - payment_difference and payment_difference - discount or 0.0 - ) - invoice_close = line.payment_difference_handling != "open" + 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", @@ -39,21 +42,41 @@ def _prepare_move(self, bank_lines=None): line_ids.append((0, 0, temp_vals)) if discount > 0: - discount_information = line.move_id.invoice_payment_term_id._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] + if payment_difference: + discount_information = line.move_id.invoice_payment_term_id._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: - 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)) + #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: diff --git a/account_banking_ach_discount/models/bank_payment_line.py b/account_banking_ach_discount/models/bank_payment_line.py index d1f4a1da..9ee03c19 100644 --- a/account_banking_ach_discount/models/bank_payment_line.py +++ b/account_banking_ach_discount/models/bank_payment_line.py @@ -24,15 +24,3 @@ def _compute_discount_amount(self): for bline in self: discount_amount = sum(bline.mapped("payment_line_ids.discount_amount")) bline.discount_amount = discount_amount - - # def reconcile(self): - # self.ensure_one() - # amlo = self.env["account.move.line"] - # transit_mlines = amlo.search([("bank_payment_line_id", "=", self.id)]) - # for line in transit_mlines: - # ap_mlines = line.move_id.line_ids.filtered( - # lambda x: x.account_id == line.account_id - # ) - # lines_to_rec = line - # lines_to_rec += ap_mlines - # lines_to_rec.reconcile() diff --git a/account_banking_ach_discount/views/account_payment_view.xml b/account_banking_ach_discount/views/account_payment_view.xml index 7c37a412..3d0c41e7 100644 --- a/account_banking_ach_discount/views/account_payment_view.xml +++ b/account_banking_ach_discount/views/account_payment_view.xml @@ -11,6 +11,7 @@ + @@ -25,14 +26,12 @@ /> - - - - - - - + + + + + From 241a1c7b66d416f902300c32de670554b8223cd4 Mon Sep 17 00:00:00 2001 From: Murtuza Saleh Date: Wed, 9 Mar 2022 18:53:49 +0530 Subject: [PATCH 11/28] [FIX] Improved code. --- .../models/account_move_line.py | 12 ++++++++---- .../models/account_payment.py | 5 +++++ .../models/account_payment_order.py | 16 +++++++++++----- .../views/account_payment_view.xml | 10 +++++----- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/account_banking_ach_discount/models/account_move_line.py b/account_banking_ach_discount/models/account_move_line.py index 683cdf18..5d1b5f87 100644 --- a/account_banking_ach_discount/models/account_move_line.py +++ b/account_banking_ach_discount/models/account_move_line.py @@ -13,8 +13,12 @@ def _prepare_payment_line_vals(self, payment_order): 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" + ( + "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 @@ -25,7 +29,7 @@ def _prepare_payment_line_vals(self, payment_order): discount_information = ( invoice.invoice_payment_term_id._check_payment_term_discount( invoice, - self._context.get("payment_date") or invoice.date_invoice, + self._context.get("payment_date") or invoice.invoice_date, ) ) discount_amt = discount_information[0] @@ -33,7 +37,7 @@ def _prepare_payment_line_vals(self, payment_order): { "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 index cf76d50d..dead2639 100644 --- a/account_banking_ach_discount/models/account_payment.py +++ b/account_banking_ach_discount/models/account_payment.py @@ -79,6 +79,11 @@ class AccountPaymentLine(models.Model): "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: diff --git a/account_banking_ach_discount/models/account_payment_order.py b/account_banking_ach_discount/models/account_payment_order.py index 329e3d64..b9edf1af 100644 --- a/account_banking_ach_discount/models/account_payment_order.py +++ b/account_banking_ach_discount/models/account_payment_order.py @@ -43,8 +43,11 @@ def _prepare_move(self, bank_lines=None): if discount > 0: if payment_difference: - discount_information = line.move_id.invoice_payment_term_id._check_payment_term_discount( - line.move_id, line.date + 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] @@ -61,9 +64,13 @@ def _prepare_move(self, bank_lines=None): # Discount Taken Update line.move_id.discount_taken = discount else: - #Case: If user Manually enters discount amount + # 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["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 @@ -77,7 +84,6 @@ def _prepare_move(self, bank_lines=None): # 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) diff --git a/account_banking_ach_discount/views/account_payment_view.xml b/account_banking_ach_discount/views/account_payment_view.xml index 3d0c41e7..7f10a3dc 100644 --- a/account_banking_ach_discount/views/account_payment_view.xml +++ b/account_banking_ach_discount/views/account_payment_view.xml @@ -11,7 +11,7 @@ - + @@ -28,10 +28,10 @@ - - - - + + + + From 04851a7656115375f90c0c280fa73606695ca1ca Mon Sep 17 00:00:00 2001 From: oca-travis Date: Tue, 15 Nov 2022 12:17:22 +0000 Subject: [PATCH 12/28] [UPD] Update account_banking_ach_discount.pot --- .../i18n/account_banking_ach_discount.pot | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 account_banking_ach_discount/i18n/account_banking_ach_discount.pot 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 "" From 424b85637827e4ddef393b2af50a75d931488001 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 15 Nov 2022 12:37:32 +0000 Subject: [PATCH 13/28] [UPD] README.rst --- account_banking_ach_discount/README.rst | 128 ++++- .../static/description/index.html | 487 ++++++++++++++++++ 2 files changed, 598 insertions(+), 17 deletions(-) create mode 100644 account_banking_ach_discount/static/description/index.html diff --git a/account_banking_ach_discount/README.rst b/account_banking_ach_discount/README.rst index 9051b85d..a63e6077 100644 --- a/account_banking_ach_discount/README.rst +++ b/account_banking_ach_discount/README.rst @@ -1,36 +1,130 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg +============================== +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 -================================ -OSI ACH-Batch Discount Connector -================================ +Vendors +~~~~~~~ -This module will provide a discount functionality in ACH and batch ACH workflow. +* 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) -Test Scenario: -============== +Usage +===== -a. Single payment without discount +* 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 -b. Single payment with discount +Changelog +========= -c. Single ACH payment with discount +12.0.1.0.0 +~~~~~~~~~~ -d. Batch payment without discount +- ACH Payment, Automatic Discount and Batch Payment Partial Pay Integration -e. Batch payment with discount +Bug Tracker +=========== -f. Batch ACH payment without discount +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 `_. -g. Batch ACH payment with discount +Do not contact contributors directly about support or help with technical issues. Credits ======= -* Open Source Integrators +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. -* Bhavesh Odedra +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. 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.

+
+
+
+ + From df328c9ca834a7deac240afd7b6b016e2d503335 Mon Sep 17 00:00:00 2001 From: Dave Burkholder Date: Mon, 19 Jun 2023 17:18:51 -0400 Subject: [PATCH 14/28] [15.0][MIG] account_banking_ach_discount: Migration to 15.0 --- account_banking_ach_discount/__manifest__.py | 2 +- account_banking_ach_discount/models/account_payment.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/account_banking_ach_discount/__manifest__.py b/account_banking_ach_discount/__manifest__.py index 511f07a0..1b1ed383 100644 --- a/account_banking_ach_discount/__manifest__.py +++ b/account_banking_ach_discount/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Discount on ACH batch payments", - "version": "14.0.1.0.0", + "version": "15.0.1.0.0", "license": "AGPL-3", "author": "Open Source Integrators, Odoo Community Association (OCA)", "category": "Accounting", diff --git a/account_banking_ach_discount/models/account_payment.py b/account_banking_ach_discount/models/account_payment.py index dead2639..4a47850b 100644 --- a/account_banking_ach_discount/models/account_payment.py +++ b/account_banking_ach_discount/models/account_payment.py @@ -72,9 +72,9 @@ class AccountPaymentLine(models.Model): domain=[("deprecated", "!=", True)], copy=False, ) - reason_code = fields.Many2one("payment.adjustment.reason", string="Reason Code") - note = fields.Text("Note") - payment_difference = fields.Float(string="Payment Difference") + 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 ) From f3ef503e9a28ada59fdea03762337a48fd03c32d Mon Sep 17 00:00:00 2001 From: Dave Burkholder Date: Mon, 17 Jul 2023 10:45:57 -0400 Subject: [PATCH 15/28] Add setup.py file --- .../odoo/addons/account_banking_ach_discount | 1 + setup/account_banking_ach_discount/setup.py | 6 ++++++ 2 files changed, 7 insertions(+) create mode 120000 setup/account_banking_ach_discount/odoo/addons/account_banking_ach_discount create mode 100644 setup/account_banking_ach_discount/setup.py diff --git a/setup/account_banking_ach_discount/odoo/addons/account_banking_ach_discount b/setup/account_banking_ach_discount/odoo/addons/account_banking_ach_discount new file mode 120000 index 00000000..cd5ad99d --- /dev/null +++ b/setup/account_banking_ach_discount/odoo/addons/account_banking_ach_discount @@ -0,0 +1 @@ +../../../../account_banking_ach_discount \ No newline at end of file diff --git a/setup/account_banking_ach_discount/setup.py b/setup/account_banking_ach_discount/setup.py new file mode 100644 index 00000000..28c57bb6 --- /dev/null +++ b/setup/account_banking_ach_discount/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From 4bbdcd20c5f31256b1471bdac741a9847298b506 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Mon, 1 Jul 2019 10:34:33 -0500 Subject: [PATCH 16/28] [ADD] account_banking_ach_direct_debit --- account_banking_ach_direct_debit/README.rst | 55 +++++++++ account_banking_ach_direct_debit/__init__.py | 2 + .../__manifest__.py | 27 +++++ .../data/account_payment_method.xml | 14 +++ .../data/mandate_expire_cron.xml | 18 +++ .../demo/ach_direct_debit_demo.xml | 46 ++++++++ .../models/__init__.py | 2 + .../models/account_banking_mandate.py | 109 ++++++++++++++++++ .../models/account_payment_order.py | 59 ++++++++++ .../post_install.py | 13 +++ .../views/account_banking_mandate_view.xml | 55 +++++++++ .../views/account_payment_order.xml | 10 ++ 12 files changed, 410 insertions(+) create mode 100644 account_banking_ach_direct_debit/README.rst create mode 100644 account_banking_ach_direct_debit/__init__.py create mode 100644 account_banking_ach_direct_debit/__manifest__.py create mode 100644 account_banking_ach_direct_debit/data/account_payment_method.xml create mode 100644 account_banking_ach_direct_debit/data/mandate_expire_cron.xml create mode 100644 account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml create mode 100644 account_banking_ach_direct_debit/models/__init__.py create mode 100644 account_banking_ach_direct_debit/models/account_banking_mandate.py create mode 100644 account_banking_ach_direct_debit/models/account_payment_order.py create mode 100644 account_banking_ach_direct_debit/post_install.py create mode 100644 account_banking_ach_direct_debit/views/account_banking_mandate_view.xml create mode 100644 account_banking_ach_direct_debit/views/account_payment_order.xml diff --git a/account_banking_ach_direct_debit/README.rst b/account_banking_ach_direct_debit/README.rst new file mode 100644 index 00000000..a10d849f --- /dev/null +++ b/account_banking_ach_direct_debit/README.rst @@ -0,0 +1,55 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :alt: License: AGPL-3 + +================================ +Account Banking ACH Direct Debit +================================ + +Create ACH files for Direct Debit + +Module to export direct debit payment orders in Nacha file format. + + +Installation +============ + +This module depends on : + +* account_banking_mandate +* carta-ach +* stdnum + + +Configuration +============= + +1. Your Company record must have the Legal ID specified. +2. Your Bank must have the Routing Number specified. + + +For defining a payment mode that uses ACH direct debit: + +#. Go to *Accounting > Configuration > Management > Payment Modes*. +#. Create a Customer Invoice. +#. Select the Payment Method *ACH* (which is automatically created upon module installation). + + +Usage +===== + +In the menu *Accounting > Payments > Debit Order*, create a new debit +order and select the Payment Mode dedicated to ACH Direct Debit that +you created during the configuration step. + +Known issues / Roadmap +====================== + + * Add support for EFT 1464 byte payment files required in Canada + +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. diff --git a/account_banking_ach_direct_debit/__init__.py b/account_banking_ach_direct_debit/__init__.py new file mode 100644 index 00000000..e2b93a53 --- /dev/null +++ b/account_banking_ach_direct_debit/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .post_install import update_bank_journals diff --git a/account_banking_ach_direct_debit/__manifest__.py b/account_banking_ach_direct_debit/__manifest__.py new file mode 100644 index 00000000..0a24cdf3 --- /dev/null +++ b/account_banking_ach_direct_debit/__manifest__.py @@ -0,0 +1,27 @@ +# Copyright 2018 Thinkwell Designs +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +{ + 'name': 'Account Banking ACH Direct Debit', + 'summary': 'Create ACH files for Direct Debit', + 'version': '11.0.1.0.0', + 'license': 'AGPL-3', + 'author': 'Thinkwell Designs', + 'website': 'https://github.com/thinkwelltwd/countinghouse', + 'category': 'Banking addons', + 'depends': [ + 'account_banking_mandate_sale', + 'countinghouse_ach_base', + ], + 'data': [ + 'data/mandate_expire_cron.xml', + 'data/account_payment_method.xml', + 'views/account_payment_order.xml', + 'views/account_banking_mandate_view.xml', + ], + 'demo': [ + 'demo/ach_direct_debit_demo.xml', + ], + 'post_init_hook': 'update_bank_journals', + 'installable': True, +} diff --git a/account_banking_ach_direct_debit/data/account_payment_method.xml b/account_banking_ach_direct_debit/data/account_payment_method.xml new file mode 100644 index 00000000..a98ab8d2 --- /dev/null +++ b/account_banking_ach_direct_debit/data/account_payment_method.xml @@ -0,0 +1,14 @@ + + + + + + ACH + ACH-In + inbound + + + + + + diff --git a/account_banking_ach_direct_debit/data/mandate_expire_cron.xml b/account_banking_ach_direct_debit/data/mandate_expire_cron.xml new file mode 100644 index 00000000..38461b9e --- /dev/null +++ b/account_banking_ach_direct_debit/data/mandate_expire_cron.xml @@ -0,0 +1,18 @@ + + + + + + Set ACH Direct Debit Mandates to Expired + + + 1 + days + -1 + + + + + + + diff --git a/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml b/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml new file mode 100644 index 00000000..0baccdd9 --- /dev/null +++ b/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml @@ -0,0 +1,46 @@ + + + + + + ACH Direct Debit of customers + + variable + + + + + + FR78ZZZ424242 + + + + + + ach + recurrent + first + + valid + + + + + + + + + + ach + recurrent + first + + valid + + + + + + + + diff --git a/account_banking_ach_direct_debit/models/__init__.py b/account_banking_ach_direct_debit/models/__init__.py new file mode 100644 index 00000000..52fce8ea --- /dev/null +++ b/account_banking_ach_direct_debit/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_banking_mandate +from . import account_payment_order diff --git a/account_banking_ach_direct_debit/models/account_banking_mandate.py b/account_banking_ach_direct_debit/models/account_banking_mandate.py new file mode 100644 index 00000000..ce6bb382 --- /dev/null +++ b/account_banking_ach_direct_debit/models/account_banking_mandate.py @@ -0,0 +1,109 @@ +from odoo import models, fields, api, exceptions, _ +from datetime import datetime +from dateutil.relativedelta import relativedelta +import logging + +NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY = 36 + +logger = logging.getLogger(__name__) + + +class AccountBankingMandate(models.Model): + """ACH Direct Debit Mandate""" + _inherit = 'account.banking.mandate' + _rec_name = 'display_name' + + format = fields.Selection( + selection_add=[('ach', 'ACH')], + default='ach' + ) + type = fields.Selection([ + ('recurrent', 'Recurrent'), + ('oneoff', 'One-Off'), + ], + string='Type of Mandate', + track_visibility='onchange' + ) + recurrent_sequence_type = fields.Selection([ + ('first', 'First'), + ('recurring', 'Recurring'), + ('final', 'Final') + ], + string='Sequence Type for Next Debit', + track_visibility='onchange', + help="This field is only used for Recurrent mandates, not for One-Off mandates.", + default="first" + ) + scheme = fields.Selection([ + ('CORE', 'Basic (CORE)'), + ('B2B', 'Enterprise (B2B)'), + ], + string='Scheme', + default="CORE", + track_visibility='onchange', + ) + unique_mandate_reference = fields.Char(size=35) # cf ISO 20022 + display_name = fields.Char(compute='compute_display_name', store=True) + + @api.multi + @api.constrains('type', 'recurrent_sequence_type') + def _check_recurring_type(self): + for mandate in self: + if mandate.type == 'recurrent' and not mandate.recurrent_sequence_type: + raise exceptions.Warning( + _("The recurrent mandate '%s' must have a sequence type.") + % mandate.unique_mandate_reference + ) + + @api.multi + @api.depends('unique_mandate_reference', 'recurrent_sequence_type') + def compute_display_name(self): + for mandate in self: + if mandate.format == 'ach': + name = '%s (%s)' % ( + mandate.unique_mandate_reference, + mandate.recurrent_sequence_type) + else: + name = mandate.unique_mandate_reference + mandate.display_name = name + + @api.multi + @api.onchange('partner_bank_id') + def mandate_partner_bank_change(self): + for mandate in self: + super(AccountBankingMandate, self).mandate_partner_bank_change() + res = {} + if (mandate.state == 'valid' and + mandate.partner_bank_id and + mandate.type == 'recurrent' and + mandate.recurrent_sequence_type != 'first'): + mandate.recurrent_sequence_type = 'first' + res['warning'] = { + 'title': _('Mandate update'), + 'message': _("As you changed the bank account attached " + "to this mandate, the 'Sequence Type' has " + "been set back to 'First'."), + } + return res + + @api.model + def _achdd_mandate_set_state_to_expired(self): + logger.info('Searching for ACH Mandates that must be set to Expired') + expire_limit_date = datetime.today() + \ + relativedelta(months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY) + expire_limit_date_str = expire_limit_date.strftime('%Y-%m-%d') + expired_mandates = self.search([ + '|', + ('last_debit_date', '=', False), + ('last_debit_date', '<=', expire_limit_date_str), + ('state', '=', 'valid'), + ('signature_date', '<=', expire_limit_date_str), + ]) + if expired_mandates: + expired_mandates.write({'state': 'expired'}) + logger.info( + 'The following ACH Mandate IDs have been set to expired: %s' + % expired_mandates.ids) + else: + logger.info('0 ACH Mandates had to be set to Expired') + return True diff --git a/account_banking_ach_direct_debit/models/account_payment_order.py b/account_banking_ach_direct_debit/models/account_payment_order.py new file mode 100644 index 00000000..381af824 --- /dev/null +++ b/account_banking_ach_direct_debit/models/account_payment_order.py @@ -0,0 +1,59 @@ +from odoo import api, models, _ + + +class AccountPaymentOrder(models.Model): + _inherit = 'account.payment.order' + + + @api.multi + def generate_payment_file(self): + """ + Creates the ACH Direct Debit file by calling + generate_ach_file in countinghouse_ach_base + """ + self.ensure_one() + + if self.payment_method_id.code == 'ACH-In': + return self.generate_ach_file() + + return super(AccountPaymentOrder, self).generate_payment_file() + + @api.multi + def generated2uploaded(self): + """Write 'last debit date' on mandates + Set mandates from first to recurring + Set oneoff mandates to expired + """ + # I call super() BEFORE updating the sequence_type + # from first to recurring, so that the account move + # is generated BEFORE, which will allow the split + # of the account move per sequence_type + res = super(AccountPaymentOrder, self).generated2uploaded() + Mandate = self.env['account.banking.mandate'] + for order in self: + to_expire_mandates = Mandate.browse([]) + first_mandates = Mandate.browse([]) + all_mandates = Mandate.browse([]) + for bank_line in order.bank_line_ids: + if bank_line.mandate_id in all_mandates: + continue + all_mandates += bank_line.mandate_id + if bank_line.mandate_id.type == 'oneoff': + to_expire_mandates += bank_line.mandate_id + elif bank_line.mandate_id.type == 'recurrent': + seq_type = bank_line.mandate_id.recurrent_sequence_type + if seq_type == 'final': + to_expire_mandates += bank_line.mandate_id + elif seq_type == 'first': + first_mandates += bank_line.mandate_id + all_mandates.write({'last_debit_date': order.date_generated}) + to_expire_mandates.write({'state': 'expired'}) + first_mandates.write({'recurrent_sequence_type': 'recurring'}) + for first_mandate in first_mandates: + first_mandate.message_post(_( + "Automatically switched from First to " + "Recurring when the debit order " + "%s has been marked as uploaded.") + % (order.id, order.name)) + return res diff --git a/account_banking_ach_direct_debit/post_install.py b/account_banking_ach_direct_debit/post_install.py new file mode 100644 index 00000000..f17ed0a2 --- /dev/null +++ b/account_banking_ach_direct_debit/post_install.py @@ -0,0 +1,13 @@ +from odoo import api, SUPERUSER_ID + + +def update_bank_journals(cr, registry): + with api.Environment.manage(): + env = api.Environment(cr, SUPERUSER_ID, {}) + journals = env['account.journal'].search([('type', '=', 'bank')]) + ach_dd = env.ref('account_banking_ach_direct_debit.ach_direct_debit') + if ach_dd: + journals.write({ + 'inbound_payment_method_ids': [(4, ach_dd.id)], + }) + return diff --git a/account_banking_ach_direct_debit/views/account_banking_mandate_view.xml b/account_banking_ach_direct_debit/views/account_banking_mandate_view.xml new file mode 100644 index 00000000..9e60cc2e --- /dev/null +++ b/account_banking_ach_direct_debit/views/account_banking_mandate_view.xml @@ -0,0 +1,55 @@ + + + + + ach.mandate.form + account.banking.mandate + + + + + + + + + + + + ach.mandate.tree + account.banking.mandate + + + + + + + + + + + + ach.mandate.search + account.banking.mandate + + + + + + + + + + + + + + + diff --git a/account_banking_ach_direct_debit/views/account_payment_order.xml b/account_banking_ach_direct_debit/views/account_payment_order.xml new file mode 100644 index 00000000..69ed50c8 --- /dev/null +++ b/account_banking_ach_direct_debit/views/account_payment_order.xml @@ -0,0 +1,10 @@ + + + + + + From a6c1f5695e3496584fded149253e2015fc97723e Mon Sep 17 00:00:00 2001 From: Murtuza Saleh Date: Fri, 5 Jul 2019 17:09:45 +0530 Subject: [PATCH 17/28] [IMP] account_banking_ach_direct_debit --- account_banking_ach_direct_debit/README.rst | 81 +++- account_banking_ach_direct_debit/__init__.py | 2 + .../__manifest__.py | 8 +- .../data/account_payment_method.xml | 18 +- .../data/mandate_expire_cron.xml | 28 +- .../demo/ach_direct_debit_demo.xml | 78 ++- .../models/__init__.py | 2 + .../models/account_banking_mandate.py | 69 ++- .../models/account_payment_order.py | 14 +- .../post_install.py | 9 +- .../readme/CONFIGURE.rst | 9 + .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 3 + .../readme/INSTALL.rst | 5 + .../readme/USAGE.rst | 3 + .../static/description/index.html | 453 ++++++++++++++++++ .../views/account_banking_mandate_view.xml | 23 +- 17 files changed, 665 insertions(+), 143 deletions(-) create mode 100644 account_banking_ach_direct_debit/readme/CONFIGURE.rst create mode 100644 account_banking_ach_direct_debit/readme/CONTRIBUTORS.rst create mode 100644 account_banking_ach_direct_debit/readme/DESCRIPTION.rst create mode 100644 account_banking_ach_direct_debit/readme/INSTALL.rst create mode 100644 account_banking_ach_direct_debit/readme/USAGE.rst create mode 100644 account_banking_ach_direct_debit/static/description/index.html diff --git a/account_banking_ach_direct_debit/README.rst b/account_banking_ach_direct_debit/README.rst index a10d849f..e648c396 100644 --- a/account_banking_ach_direct_debit/README.rst +++ b/account_banking_ach_direct_debit/README.rst @@ -1,25 +1,48 @@ -.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg - :alt: License: AGPL-3 - ================================ Account Banking ACH Direct Debit ================================ +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! 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/12.0/account_banking_ach_direct_debit + :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-12-0/l10n-usa-12-0-account_banking_ach_direct_debit + :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/12.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + Create ACH files for Direct Debit Module to export direct debit payment orders in Nacha file format. +**Table of contents** + +.. contents:: + :local: Installation ============ This module depends on : -* account_banking_mandate +* account_banking_ach_base * carta-ach * stdnum - Configuration ============= @@ -33,7 +56,6 @@ For defining a payment mode that uses ACH direct debit: #. Create a Customer Invoice. #. Select the Payment Method *ACH* (which is automatically created upon module installation). - Usage ===== @@ -41,15 +63,44 @@ In the menu *Accounting > Payments > Debit Order*, create a new debit order and select the Payment Mode dedicated to ACH Direct Debit that you created during the configuration step. -Known issues / Roadmap -====================== - - * Add support for EFT 1464 byte payment files required in Canada - 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. +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 +~~~~~~~ + +* Thinkwell Designs + +Contributors +~~~~~~~~~~~~ + +* Dave Burkholder +* Maxime Chambreuil +* 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. + +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_direct_debit/__init__.py b/account_banking_ach_direct_debit/__init__.py index e2b93a53..f1a6eca6 100644 --- a/account_banking_ach_direct_debit/__init__.py +++ b/account_banking_ach_direct_debit/__init__.py @@ -1,2 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from . import models from .post_install import update_bank_journals diff --git a/account_banking_ach_direct_debit/__manifest__.py b/account_banking_ach_direct_debit/__manifest__.py index 0a24cdf3..0d5a67ef 100644 --- a/account_banking_ach_direct_debit/__manifest__.py +++ b/account_banking_ach_direct_debit/__manifest__.py @@ -4,14 +4,14 @@ { 'name': 'Account Banking ACH Direct Debit', 'summary': 'Create ACH files for Direct Debit', - 'version': '11.0.1.0.0', + 'version': '12.0.1.0.0', 'license': 'AGPL-3', - 'author': 'Thinkwell Designs', - 'website': 'https://github.com/thinkwelltwd/countinghouse', + 'author': 'Thinkwell Designs, Odoo Community Association (OCA)', + 'website': 'https://github.com/OCA/l10n-usa', 'category': 'Banking addons', 'depends': [ 'account_banking_mandate_sale', - 'countinghouse_ach_base', + 'account_banking_ach_base', ], 'data': [ 'data/mandate_expire_cron.xml', diff --git a/account_banking_ach_direct_debit/data/account_payment_method.xml b/account_banking_ach_direct_debit/data/account_payment_method.xml index a98ab8d2..6e27fdf8 100644 --- a/account_banking_ach_direct_debit/data/account_payment_method.xml +++ b/account_banking_ach_direct_debit/data/account_payment_method.xml @@ -1,14 +1,12 @@ - - + - - ACH - ACH-In - inbound - - - + + ACH + ACH-In + inbound + + + - diff --git a/account_banking_ach_direct_debit/data/mandate_expire_cron.xml b/account_banking_ach_direct_debit/data/mandate_expire_cron.xml index 38461b9e..270af809 100644 --- a/account_banking_ach_direct_debit/data/mandate_expire_cron.xml +++ b/account_banking_ach_direct_debit/data/mandate_expire_cron.xml @@ -1,18 +1,16 @@ - - - - Set ACH Direct Debit Mandates to Expired - - - 1 - days - -1 - - - - - - + + + Set ACH Direct Debit Mandates to Expired + + + code + 1 + days + -1 + + + model._achdd_mandate_set_state_to_expired() + diff --git a/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml b/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml index 0baccdd9..c2287f51 100644 --- a/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml +++ b/account_banking_ach_direct_debit/demo/ach_direct_debit_demo.xml @@ -1,46 +1,44 @@ - - + - - ACH Direct Debit of customers - - variable - - - + + ACH Direct Debit of customers + + variable + + + + + + + + ach + recurrent + first + + valid + - - FR78ZZZ424242 - + + + - - - - ach - recurrent - first - - valid - + + + + ach + recurrent + first + + valid + - - - - - - - - ach - recurrent - first - - valid - - - - - - - + + + diff --git a/account_banking_ach_direct_debit/models/__init__.py b/account_banking_ach_direct_debit/models/__init__.py index 52fce8ea..255aed42 100644 --- a/account_banking_ach_direct_debit/models/__init__.py +++ b/account_banking_ach_direct_debit/models/__init__.py @@ -1,2 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from . import account_banking_mandate from . import account_payment_order diff --git a/account_banking_ach_direct_debit/models/account_banking_mandate.py b/account_banking_ach_direct_debit/models/account_banking_mandate.py index ce6bb382..996b38dc 100644 --- a/account_banking_ach_direct_debit/models/account_banking_mandate.py +++ b/account_banking_ach_direct_debit/models/account_banking_mandate.py @@ -1,6 +1,10 @@ -from odoo import models, fields, api, exceptions, _ +# Copyright 2018 Thinkwell Designs +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from datetime import datetime +from odoo import api, exceptions, fields, models, _ from dateutil.relativedelta import relativedelta +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT import logging NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY = 36 @@ -11,53 +15,41 @@ class AccountBankingMandate(models.Model): """ACH Direct Debit Mandate""" _inherit = 'account.banking.mandate' - _rec_name = 'display_name' - format = fields.Selection( - selection_add=[('ach', 'ACH')], - default='ach' - ) - type = fields.Selection([ - ('recurrent', 'Recurrent'), - ('oneoff', 'One-Off'), - ], - string='Type of Mandate', - track_visibility='onchange' - ) - recurrent_sequence_type = fields.Selection([ - ('first', 'First'), - ('recurring', 'Recurring'), - ('final', 'Final') - ], + format = fields.Selection(selection_add=[('ach', 'ACH')], default='ach') + type = fields.Selection([('recurrent', 'Recurrent'), + ('oneoff', 'One-Off')], + string='Type of Mandate', + track_visibility='onchange') + recurrent_sequence_type = fields.Selection( + [('first', 'First'), ('recurring', 'Recurring'), ('final', 'Final')], string='Sequence Type for Next Debit', track_visibility='onchange', - help="This field is only used for Recurrent mandates, not for One-Off mandates.", - default="first" - ) + help="""This field is only used for Recurrent mandates, not for + One-Off mandates.""", + default="first") scheme = fields.Selection([ - ('CORE', 'Basic (CORE)'), - ('B2B', 'Enterprise (B2B)'), - ], + ('CORE', 'Basic (CORE)'), ('B2B', 'Enterprise (B2B)')], string='Scheme', default="CORE", - track_visibility='onchange', - ) + track_visibility='onchange') unique_mandate_reference = fields.Char(size=35) # cf ISO 20022 - display_name = fields.Char(compute='compute_display_name', store=True) + display_name = fields.Char(compute='_compute_display_name', + store=True) @api.multi @api.constrains('type', 'recurrent_sequence_type') def _check_recurring_type(self): for mandate in self: - if mandate.type == 'recurrent' and not mandate.recurrent_sequence_type: + if mandate.type == 'recurrent' and not \ + mandate.recurrent_sequence_type: raise exceptions.Warning( _("The recurrent mandate '%s' must have a sequence type.") - % mandate.unique_mandate_reference - ) + % mandate.unique_mandate_reference) @api.multi @api.depends('unique_mandate_reference', 'recurrent_sequence_type') - def compute_display_name(self): + def _compute_display_name(self): for mandate in self: if mandate.format == 'ach': name = '%s (%s)' % ( @@ -82,16 +74,17 @@ def mandate_partner_bank_change(self): 'title': _('Mandate update'), 'message': _("As you changed the bank account attached " "to this mandate, the 'Sequence Type' has " - "been set back to 'First'."), - } + "been set back to 'First'.")} return res @api.model def _achdd_mandate_set_state_to_expired(self): - logger.info('Searching for ACH Mandates that must be set to Expired') + logger.info( + _('Searching for ACH Mandates that must be set to Expired')) expire_limit_date = datetime.today() + \ relativedelta(months=-NUMBER_OF_UNUSED_MONTHS_BEFORE_EXPIRY) - expire_limit_date_str = expire_limit_date.strftime('%Y-%m-%d') + expire_limit_date_str = expire_limit_date.strftime( + DEFAULT_SERVER_DATE_FORMAT) expired_mandates = self.search([ '|', ('last_debit_date', '=', False), @@ -102,8 +95,8 @@ def _achdd_mandate_set_state_to_expired(self): if expired_mandates: expired_mandates.write({'state': 'expired'}) logger.info( - 'The following ACH Mandate IDs have been set to expired: %s' - % expired_mandates.ids) + _('The following ACH Mandate IDs have been set to ' + 'expired: %s' % expired_mandates.ids)) else: - logger.info('0 ACH Mandates had to be set to Expired') + logger.info(_('0 ACH Mandates had to be set to Expired')) return True diff --git a/account_banking_ach_direct_debit/models/account_payment_order.py b/account_banking_ach_direct_debit/models/account_payment_order.py index 381af824..114ee613 100644 --- a/account_banking_ach_direct_debit/models/account_payment_order.py +++ b/account_banking_ach_direct_debit/models/account_payment_order.py @@ -1,10 +1,12 @@ +# Copyright 2018 Thinkwell Designs +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + from odoo import api, models, _ class AccountPaymentOrder(models.Model): _inherit = 'account.payment.order' - @api.multi def generate_payment_file(self): """ @@ -12,10 +14,8 @@ def generate_payment_file(self): generate_ach_file in countinghouse_ach_base """ self.ensure_one() - if self.payment_method_id.code == 'ACH-In': return self.generate_ach_file() - return super(AccountPaymentOrder, self).generate_payment_file() @api.multi @@ -29,11 +29,9 @@ def generated2uploaded(self): # is generated BEFORE, which will allow the split # of the account move per sequence_type res = super(AccountPaymentOrder, self).generated2uploaded() - Mandate = self.env['account.banking.mandate'] + mandate = self.env['account.banking.mandate'] for order in self: - to_expire_mandates = Mandate.browse([]) - first_mandates = Mandate.browse([]) - all_mandates = Mandate.browse([]) + to_expire_mandates = first_mandates = all_mandates = mandate for bank_line in order.bank_line_ids: if bank_line.mandate_id in all_mandates: continue @@ -50,7 +48,7 @@ def generated2uploaded(self): to_expire_mandates.write({'state': 'expired'}) first_mandates.write({'recurrent_sequence_type': 'recurring'}) for first_mandate in first_mandates: - first_mandate.message_post(_( + first_mandate.message_post(body=_( "Automatically switched from First to " "Recurring when the debit order " " Configuration > Management > Payment Modes*. +#. Create a Customer Invoice. +#. Select the Payment Method *ACH* (which is automatically created upon module installation). diff --git a/account_banking_ach_direct_debit/readme/CONTRIBUTORS.rst b/account_banking_ach_direct_debit/readme/CONTRIBUTORS.rst new file mode 100644 index 00000000..5a46796e --- /dev/null +++ b/account_banking_ach_direct_debit/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* Dave Burkholder +* Maxime Chambreuil +* Serpent Consulting Services Pvt. Ltd. diff --git a/account_banking_ach_direct_debit/readme/DESCRIPTION.rst b/account_banking_ach_direct_debit/readme/DESCRIPTION.rst new file mode 100644 index 00000000..ae200b11 --- /dev/null +++ b/account_banking_ach_direct_debit/readme/DESCRIPTION.rst @@ -0,0 +1,3 @@ +Create ACH files for Direct Debit + +Module to export direct debit payment orders in Nacha file format. diff --git a/account_banking_ach_direct_debit/readme/INSTALL.rst b/account_banking_ach_direct_debit/readme/INSTALL.rst new file mode 100644 index 00000000..b869995a --- /dev/null +++ b/account_banking_ach_direct_debit/readme/INSTALL.rst @@ -0,0 +1,5 @@ +This module depends on : + +* account_banking_ach_base +* carta-ach +* stdnum diff --git a/account_banking_ach_direct_debit/readme/USAGE.rst b/account_banking_ach_direct_debit/readme/USAGE.rst new file mode 100644 index 00000000..b0bc2e37 --- /dev/null +++ b/account_banking_ach_direct_debit/readme/USAGE.rst @@ -0,0 +1,3 @@ +In the menu *Accounting > Payments > Debit Order*, create a new debit +order and select the Payment Mode dedicated to ACH Direct Debit that +you created during the configuration step. diff --git a/account_banking_ach_direct_debit/static/description/index.html b/account_banking_ach_direct_debit/static/description/index.html new file mode 100644 index 00000000..cb70d720 --- /dev/null +++ b/account_banking_ach_direct_debit/static/description/index.html @@ -0,0 +1,453 @@ + + + + + + +Account Banking ACH Direct Debit + + + +
+

Account Banking ACH Direct Debit

+ + +

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

+

Create ACH files for Direct Debit

+

Module to export direct debit payment orders in Nacha file format.

+

Table of contents

+ +
+

Installation

+

This module depends on :

+
    +
  • account_banking_ach_base
  • +
  • carta-ach
  • +
  • stdnum
  • +
+
+
+

Configuration

+
    +
  1. Your Company record must have the Legal ID specified.
  2. +
  3. Your Bank must have the Routing Number specified.
  4. +
+

For defining a payment mode that uses ACH direct debit:

+
    +
  1. Go to Accounting > Configuration > Management > Payment Modes.
  2. +
  3. Create a Customer Invoice.
  4. +
  5. Select the Payment Method ACH (which is automatically created upon module installation).
  6. +
+
+
+

Usage

+

In the menu Accounting > Payments > Debit Order, create a new debit +order and select the Payment Mode dedicated to ACH Direct Debit that +you created during the configuration step.

+
+
+

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

+
    +
  • Thinkwell Designs
  • +
+
+
+

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/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_direct_debit/views/account_banking_mandate_view.xml b/account_banking_ach_direct_debit/views/account_banking_mandate_view.xml index 9e60cc2e..5d819c15 100644 --- a/account_banking_ach_direct_debit/views/account_banking_mandate_view.xml +++ b/account_banking_ach_direct_debit/views/account_banking_mandate_view.xml @@ -4,7 +4,8 @@ ach.mandate.form account.banking.mandate - + ach.mandate.tree account.banking.mandate - + @@ -34,20 +36,23 @@ ach.mandate.search account.banking.mandate - + - - + + + context="{'group_by': 'type'}"/> + context="{'group_by': 'scheme'}"/> + string="Sequence Type" + context="{'group_by': 'recurrent_sequence_type'}"/> From 47e3218e6e8260a24efb4e4e9e5c765501c23b49 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Tue, 9 Jul 2019 07:32:56 +0000 Subject: [PATCH 18/28] [UPD] Update account_banking_ach_direct_debit.pot --- .../i18n/account_banking_ach_direct_debit.pot | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 account_banking_ach_direct_debit/i18n/account_banking_ach_direct_debit.pot diff --git a/account_banking_ach_direct_debit/i18n/account_banking_ach_direct_debit.pot b/account_banking_ach_direct_debit/i18n/account_banking_ach_direct_debit.pot new file mode 100644 index 00000000..c640d4ae --- /dev/null +++ b/account_banking_ach_direct_debit/i18n/account_banking_ach_direct_debit.pot @@ -0,0 +1,181 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * account_banking_ach_direct_debit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.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_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_banking_mandate.py:101 +#, python-format +msgid "0 ACH Mandates had to be set to Expired" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model,name:account_banking_ach_direct_debit.model_account_banking_mandate +msgid "A generic banking mandate" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,format:0 +#: model:account.payment.method,name:account_banking_ach_direct_debit.ach_direct_debit +msgid "ACH" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:account.payment.mode,name:account_banking_ach_direct_debit.payment_mode_inbound_ach_dd1 +msgid "ACH Direct Debit of customers" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_banking_mandate.py:75 +#, python-format +msgid "As you changed the bank account attached to this mandate, the 'Sequence Type' has been set back to 'First'." +msgstr "" + +#. module: account_banking_ach_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_payment_order.py:51 +#, python-format +msgid "Automatically switched from First to Recurring when the debit order %s has been marked as uploaded." +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,scheme:0 +msgid "Basic (CORE)" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,format:0 +msgid "Basic Mandate" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,field_description:account_banking_ach_direct_debit.field_account_banking_mandate__display_name +msgid "Display Name" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,scheme:0 +msgid "Enterprise (B2B)" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,recurrent_sequence_type:0 +msgid "Final" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,recurrent_sequence_type:0 +msgid "First" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,field_description:account_banking_ach_direct_debit.field_account_banking_mandate__format +msgid "Mandate Format" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_banking_mandate.py:74 +#, python-format +msgid "Mandate update" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,type:0 +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_search +msgid "One-Off" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model,name:account_banking_ach_direct_debit.model_account_payment_order +msgid "Payment Order" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.ui.menu,name:account_banking_ach_direct_debit.customer_payment_orders +msgid "Payment Orders" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,type:0 +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_search +msgid "Recurrent" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: selection:account.banking.mandate,recurrent_sequence_type:0 +msgid "Recurring" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,field_description:account_banking_ach_direct_debit.field_account_banking_mandate__scheme +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_search +msgid "Scheme" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_banking_mandate.py:83 +#, python-format +msgid "Searching for ACH Mandates that must be set to Expired" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_search +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_tree +msgid "Sequence Type" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,field_description:account_banking_ach_direct_debit.field_account_banking_mandate__recurrent_sequence_type +msgid "Sequence Type for Next Debit" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.actions.server,name:account_banking_ach_direct_debit.ach_mandate_expire_cron_ir_actions_server +#: model:ir.cron,cron_name:account_banking_ach_direct_debit.ach_mandate_expire_cron +#: model:ir.cron,name:account_banking_ach_direct_debit.ach_mandate_expire_cron +msgid "Set ACH Direct Debit Mandates to Expired" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_banking_mandate.py:98 +#, python-format +msgid "The following ACH Mandate IDs have been set to expired: %s" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: code:addons/account_banking_ach_direct_debit/models/account_banking_mandate.py:47 +#, python-format +msgid "The recurrent mandate '%s' must have a sequence type." +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,help:account_banking_ach_direct_debit.field_account_banking_mandate__recurrent_sequence_type +msgid "This field is only used for Recurrent mandates, not for\n" +" One-Off mandates." +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_search +#: model_terms:ir.ui.view,arch_db:account_banking_ach_direct_debit.view_mandate_tree +msgid "Type" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,field_description:account_banking_ach_direct_debit.field_account_banking_mandate__type +msgid "Type of Mandate" +msgstr "" + +#. module: account_banking_ach_direct_debit +#: model:ir.model.fields,field_description:account_banking_ach_direct_debit.field_account_banking_mandate__unique_mandate_reference +msgid "Unique Mandate Reference" +msgstr "" + From 97013c9a270cefb8c1af147c9cef288f4d496df1 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 9 Jul 2019 08:01:57 +0000 Subject: [PATCH 19/28] [ADD] icon.png --- .../static/description/icon.png | Bin 0 -> 9455 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 account_banking_ach_direct_debit/static/description/icon.png diff --git a/account_banking_ach_direct_debit/static/description/icon.png b/account_banking_ach_direct_debit/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0328b516c4980e8e44cdb63fd945757ddd132d GIT binary patch literal 9455 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 z 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 Xviia!U7SGha1wx#SCgwmn*{w2TRX*I literal 0 HcmV?d00001 From 3f24bfbe68cd08f72149a6b86d7157bff43921f4 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Mon, 29 Jul 2019 03:08:44 +0000 Subject: [PATCH 20/28] [UPD] README.rst --- account_banking_ach_direct_debit/static/description/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_banking_ach_direct_debit/static/description/index.html b/account_banking_ach_direct_debit/static/description/index.html index cb70d720..a7748a23 100644 --- a/account_banking_ach_direct_debit/static/description/index.html +++ b/account_banking_ach_direct_debit/static/description/index.html @@ -3,7 +3,7 @@ - + Account Banking ACH Direct Debit