From 581e8f335b1164c0bdf13502991928d6b2e2c309 Mon Sep 17 00:00:00 2001 From: Murtaza Mithaiwala Date: Wed, 24 Feb 2021 19:36:16 +0530 Subject: [PATCH 01/20] [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 15be18dce989d5ad4ac64bcca815d11859d62b55 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Mon, 8 Mar 2021 17:15:44 -0600 Subject: [PATCH 02/20] [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 a7ea731b76d7efc4ce34ab41853460679e5f18c6 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Wed, 10 Mar 2021 18:25:43 -0600 Subject: [PATCH 03/20] [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 b809493051f2943582ff43702bc3eeb6a1ff6d25 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Fri, 12 Mar 2021 16:18:48 -0600 Subject: [PATCH 04/20] [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 7bcbd426bb8204458466e7bd44362afcf86e1fa2 Mon Sep 17 00:00:00 2001 From: Maxime Chambreuil Date: Sat, 13 Mar 2021 03:56:03 -0600 Subject: [PATCH 05/20] [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 f2ffdb95cb8a955f8a2f775e67aa629eb00ea603 Mon Sep 17 00:00:00 2001 From: AmmarOfficewalaSerpentCS Date: Fri, 9 Jul 2021 19:26:03 +0530 Subject: [PATCH 06/20] [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 12c1e7cc13477949beed6518b0a6eff285617885 Mon Sep 17 00:00:00 2001 From: Bhavesh Odedra Date: Fri, 24 Sep 2021 13:53:38 -0700 Subject: [PATCH 07/20] [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 79c5b960e850c0088860c3a1e5c28051db648404 Mon Sep 17 00:00:00 2001 From: Murtaza Mithaiwala Date: Thu, 30 Sep 2021 13:22:12 +0530 Subject: [PATCH 08/20] [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 944a392490f1fda08008f02d7f75cdacd17bb98d Mon Sep 17 00:00:00 2001 From: Murtuza Saleh Date: Wed, 2 Mar 2022 17:48:52 +0530 Subject: [PATCH 09/20] [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 fcd4a3b8b31c539f15511d36a04b513522cabca2 Mon Sep 17 00:00:00 2001 From: Sandip Mangukiya Date: Wed, 2 Mar 2022 14:53:08 -0700 Subject: [PATCH 10/20] [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 5a4cd8fbe77c211320f07ca2855d9e32878d6990 Mon Sep 17 00:00:00 2001 From: Murtuza Saleh Date: Wed, 9 Mar 2022 18:53:49 +0530 Subject: [PATCH 11/20] [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 dba533e2e9bab920a705d8c9b4ac3d2418e21778 Mon Sep 17 00:00:00 2001 From: oca-travis Date: Tue, 15 Nov 2022 12:17:22 +0000 Subject: [PATCH 12/20] [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 ff4efc09550719ae8895cdad5a43c0c69bbec1ab Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Tue, 15 Nov 2022 12:37:32 +0000 Subject: [PATCH 13/20] [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 59b5770b5d1d25f1a6a7b85511f3b7fc3d192725 Mon Sep 17 00:00:00 2001 From: OCA-git-bot Date: Sun, 3 Sep 2023 14:14:34 +0000 Subject: [PATCH 14/20] [UPD] README.rst --- account_banking_ach_discount/README.rst | 15 +++-- .../static/description/index.html | 66 ++++++++++--------- 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/account_banking_ach_discount/README.rst b/account_banking_ach_discount/README.rst index a63e6077..3c82bac0 100644 --- a/account_banking_ach_discount/README.rst +++ b/account_banking_ach_discount/README.rst @@ -2,10 +2,13 @@ Discount on ACH batch payments ============================== -.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:49de557183014de2a8dba5fce829d24e0fd04061b538c048a3553c6f835b02b9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status @@ -19,11 +22,11 @@ Discount on ACH batch payments .. |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 +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/l10n-usa&target_branch=14.0 + :alt: Try me on Runboat -|badge1| |badge2| |badge3| |badge4| |badge5| +|badge1| |badge2| |badge3| |badge4| |badge5| This module will add support for discount in ACH and batch ACH workflow. @@ -83,7 +86,7 @@ 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 +If you spotted it first, help us to smash it by providing a detailed and welcomed `feedback `_. Do not contact contributors directly about support or help with technical issues. diff --git a/account_banking_ach_discount/static/description/index.html b/account_banking_ach_discount/static/description/index.html index eefb0179..efab1716 100644 --- a/account_banking_ach_discount/static/description/index.html +++ b/account_banking_ach_discount/static/description/index.html @@ -1,20 +1,20 @@ - + - + Discount on ACH batch payments