From ad08d4ce9611e9d5f59e674bd6198a9eef2cdc5f Mon Sep 17 00:00:00 2001 From: tundebabzy Date: Wed, 16 May 2018 07:01:41 +0100 Subject: [PATCH] Ability to hold payment for disputed invoices #12048 (#13298) * add new fields to Supplier Master: - on_hold: To signal the Customer is blocked from completing certain transactions - hold_type: 3 options - All, invoices and payments * sanitize `on_hold` field input * show hold status in list view * add `release_date` field to Supplier Master: - specifies the date when transaction restraint will be removed * reset release date if supplier is not on hold * add validation to stop transactions when Supplier is blocked * add test cases * return empty list for outstanding references if supplier is blocked * block make button:payment if supplier is blocked * adjust test cases * PEP 8 clean up * more tests * adds new fields to Purchase Invoice: - release_date: once set, invoice will be on hold until set date - hold_comment: so user can add comment pertaining to why invoice is on hold * implement individual purchase invoice on hold logic * allow user to change release date * update manual * final cleanup including more validation and tests * update supplier manual * make default for release_date argument todays date * remove Auto Repeat added by mistake * add on_hold_field to purchase invoice * add 'On Hold' or 'Temporarily on Hold' status for purchase invoice in list view * implement explicit payment hold in purchase invoice * update manual * add dialog for saving comment * bug fix, refactor, clean up * more test cases --- .../doctype/payment_entry/payment_entry.py | 45 +++-- .../payment_entry/test_payment_entry.py | 63 +++++++ .../purchase_invoice/purchase_invoice.js | 121 ++++++++++++- .../purchase_invoice/purchase_invoice.json | 161 +++++++++++++++++- .../purchase_invoice/purchase_invoice.py | 47 ++++- .../purchase_invoice/purchase_invoice_list.js | 8 +- .../purchase_invoice/test_purchase_invoice.py | 101 +++++++++++ erpnext/accounts/utils.py | 47 +++-- .../purchase_order/test_purchase_order.py | 72 ++++++++ erpnext/buying/doctype/supplier/supplier.json | 132 +++++++++++++- erpnext/buying/doctype/supplier/supplier.py | 10 +- .../buying/doctype/supplier/supplier_list.js | 7 +- erpnext/controllers/accounts_controller.py | 36 +++- .../img/accounts/purchase-invoice-hold.png | Bin 0 -> 37126 bytes .../manual/en/accounts/purchase-invoice.md | 38 +++++ .../docs/user/manual/en/buying/supplier.md | 15 +- 16 files changed, 865 insertions(+), 38 deletions(-) create mode 100644 erpnext/docs/assets/img/accounts/purchase-invoice-hold.png diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 96b997f9d7..8539c36c43 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -5,14 +5,14 @@ from __future__ import unicode_literals import frappe, erpnext, json from frappe import _, scrub, ValidationError -from frappe.utils import flt, comma_or, nowdate +from frappe.utils import flt, comma_or, nowdate, getdate from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on from erpnext.accounts.party import get_party_account, get_patry_tax_withholding_details from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.general_ledger import make_gl_entries from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount -from erpnext.controllers.accounts_controller import AccountsController +from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status from six import string_types @@ -59,6 +59,7 @@ class PaymentEntry(AccountsController): self.set_remarks() self.validate_duplicate_entry() self.validate_allocated_amount() + self.ensure_supplier_is_not_blocked() def on_submit(self): self.setup_party_account_field() @@ -537,6 +538,16 @@ def get_outstanding_reference_documents(args): if isinstance(args, string_types): args = json.loads(args) + # confirm that Supplier is not blocked + if args.get('party_type') == 'Supplier': + supplier_status = get_supplier_block_status(args['party']) + if supplier_status['on_hold']: + if supplier_status['hold_type'] == 'All': + return [] + elif supplier_status['hold_type'] == 'Payments': + if not supplier_status['release_date'] or getdate(nowdate()) <= supplier_status['release_date']: + return [] + party_account_currency = get_account_currency(args.get("party_account")) company_currency = frappe.db.get_value("Company", args.get("company"), "default_currency") @@ -621,6 +632,9 @@ def get_orders_to_be_billed(posting_date, party_type, party, party_account_curre def get_negative_outstanding_invoices(party_type, party, party_account, party_account_currency, company_currency): voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice" + supplier_condition = "" + if voucher_type == "Purchase Invoice": + supplier_condition = "and (release_date is null or release_date <= CURDATE())" if party_account_currency == company_currency: grand_total_field = "base_grand_total" rounded_total_field = "base_rounded_total" @@ -638,9 +652,11 @@ def get_negative_outstanding_invoices(party_type, party, party_account, party_ac `tab{voucher_type}` where {party_type} = %s and {party_account} = %s and docstatus = 1 and outstanding_amount < 0 + {supplier_condition} order by posting_date, name """.format(**{ + "supplier_condition": supplier_condition, "rounded_total_field": rounded_total_field, "grand_total_field": grand_total_field, "voucher_type": voucher_type, @@ -854,6 +870,9 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.mode_of_payment = doc.get("mode_of_payment") pe.party_type = party_type pe.party = doc.get(scrub(party_type)) + + pe.ensure_supplier_is_not_blocked() + pe.paid_from = party_account if payment_type=="Receive" else bank.account pe.paid_to = party_account if payment_type=="Pay" else bank.account pe.paid_from_account_currency = party_account_currency \ @@ -864,15 +883,19 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= pe.allocate_payment_amount = 1 pe.letter_head = doc.get("letter_head") - pe.append("references", { - 'reference_doctype': dt, - 'reference_name': dn, - "bill_no": doc.get("bill_no"), - "due_date": doc.get("due_date"), - 'total_amount': grand_total, - 'outstanding_amount': outstanding_amount, - 'allocated_amount': outstanding_amount - }) + # only Purchase Invoice can be blocked individually + if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): + frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date))) + else: + pe.append("references", { + 'reference_doctype': dt, + 'reference_name': dn, + "bill_no": doc.get("bill_no"), + "due_date": doc.get("due_date"), + 'total_amount': grand_total, + 'outstanding_amount': outstanding_amount, + 'allocated_amount': outstanding_amount + }) pe.setup_party_account_field() pe.set_missing_values() diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 435bee0659..57516a1bbb 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -40,6 +40,69 @@ class TestPaymentEntry(unittest.TestCase): so_advance_paid = frappe.db.get_value("Sales Order", so.name, "advance_paid") self.assertEqual(so_advance_paid, 0) + def test_payment_entry_for_blocked_supplier_invoice(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Invoices' + supplier.save() + + self.assertRaises(frappe.ValidationError, make_purchase_invoice) + + supplier.on_hold = 0 + supplier.save() + + def test_payment_entry_for_blocked_supplier_payments(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.save() + + pi = make_purchase_invoice() + + self.assertRaises( + frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name, + bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + + def test_payment_entry_for_blocked_supplier_payments_today_date(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.release_date = nowdate() + supplier.save() + + pi = make_purchase_invoice() + + self.assertRaises( + frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name, + bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + + def test_payment_entry_for_blocked_supplier_payments_past_date(self): + # this test is meant to fail only if something fails in the try block + with self.assertRaises(Exception): + try: + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.release_date = '2018-03-01' + supplier.save() + + pi = make_purchase_invoice() + + get_payment_entry('Purchase Invoice', pi.name, bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + except: + pass + else: + raise Exception + def test_payment_entry_against_si_usd_to_usd(self): si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC", currency="USD", conversion_rate=50) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index d2c41938fe..c1a2c9741b 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -27,6 +27,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ }, refresh: function(doc) { + const me = this; this._super(); hide_fields(this.frm.doc); @@ -37,6 +38,27 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ this.show_stock_ledger(); } + if(!doc.is_return && doc.docstatus == 1 && doc.outstanding_amount != 0){ + if(doc.on_hold) { + this.frm.add_custom_button( + __('Change Release Date'), + function() {me.change_release_date()}, + __('Hold Invoice') + ); + this.frm.add_custom_button( + __('Unblock Invoice'), + function() {me.unblock_invoice()}, + __('Make') + ); + } else if (!doc.on_hold) { + this.frm.add_custom_button( + __('Block Invoice'), + function() {me.block_invoice()}, + __('Make') + ); + } + } + if(!doc.is_return && doc.docstatus==1) { if(doc.outstanding_amount != 0) { this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __("Make")); @@ -56,7 +78,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ } if(doc.docstatus===0) { - var me = this; this.frm.add_custom_button(__('Purchase Order'), function() { erpnext.utils.map_current_doc({ method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice", @@ -109,6 +130,104 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ } }, + unblock_invoice: function() { + const me = this; + frappe.call({ + 'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.unblock_invoice', + 'args': {'name': me.frm.doc.name}, + 'callback': (r) => me.frm.reload_doc() + }); + }, + + block_invoice: function() { + this.make_comment_dialog_and_block_invoice(); + }, + + change_release_date: function() { + this.make_dialog_and_set_release_date(); + }, + + can_change_release_date: function(date) { + const diff = frappe.datetime.get_diff(date, frappe.datetime.nowdate()); + if (diff < 0) { + frappe.throw('New release date should be in the future'); + return false; + } else { + return true; + } + }, + + make_comment_dialog_and_block_invoice: function(){ + const me = this; + + const title = __('Add Comment'); + const fields = [ + { + fieldname: 'hold_comment', + read_only: 0, + fieldtype:'Small Text', + label: __('Reason For Putting On Hold'), + default: "" + }, + ]; + + this.dialog = new frappe.ui.Dialog({ + title: title, + fields: fields + }); + + this.dialog.set_primary_action(__('Save'), function() { + const dialog_data = me.dialog.get_values(); + frappe.call({ + 'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.block_invoice', + 'args': {'name': me.frm.doc.name, 'hold_comment': dialog_data.hold_comment}, + 'callback': (r) => me.frm.reload_doc() + }); + me.dialog.hide(); + }); + + this.dialog.show(); + }, + + make_dialog_and_set_release_date: function() { + const me = this; + + const title = __('Set New Release Date'); + const fields = [ + { + fieldname: 'release_date', + read_only: 0, + fieldtype:'Date', + label: __('Release Date'), + default: me.frm.doc.release_date + }, + ]; + + this.dialog = new frappe.ui.Dialog({ + title: title, + fields: fields + }); + + this.dialog.set_primary_action(__('Save'), function() { + me.dialog_data = me.dialog.get_values(); + if(me.can_change_release_date(me.dialog_data.release_date)) { + me.dialog_data.name = me.frm.doc.name; + me.set_release_date(me.dialog_data); + me.dialog.hide(); + } + }); + + this.dialog.show(); + }, + + set_release_date: function(data) { + return frappe.call({ + 'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.change_release_date', + 'args': data, + 'callback': (r) => this.frm.reload_doc() + }); + }, + supplier: function() { var me = this; if(this.frm.updating_party_details) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index 8acdf5c4b8..a8fa9f7529 100755 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -432,6 +432,165 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "collapsible_depends_on": "eval:doc.on_hold", + "columns": 0, + "fieldname": "sb_14", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Hold Invoice", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "on_hold", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Hold Invoice", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.on_hold", + "description": "Once set, this invoice will be on hold till the set date", + "fieldname": "release_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Release Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cb_17", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.on_hold", + "fieldname": "hold_comment", + "fieldtype": "Small Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Reason For Putting On Hold", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -4072,7 +4231,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-04-19 15:48:29.457594", + "modified": "2018-04-19 15:48:29.457594", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index f6103cc89f..9599d1f53f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe, erpnext -from frappe.utils import cint, formatdate, flt, getdate +from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate from frappe import _, throw import frappe.defaults @@ -41,6 +41,13 @@ class PurchaseInvoice(BuyingController): 'overflow_type': 'billing' }] + def before_save(self): + if not self.on_hold: + self.release_date = '' + + def invoice_is_blocked(self): + return self.on_hold and (not self.release_date or self.release_date > getdate(nowdate())) + def validate(self): if not self.is_opening: self.is_opening = 'No' @@ -61,6 +68,7 @@ class PurchaseInvoice(BuyingController): if self._action=="submit" and self.update_stock: self.make_batches('warehouse') + self.validate_release_date() self.check_conversion_rate() self.validate_credit_to_acc() self.clear_unallocated_advances("Purchase Invoice Advance", "advances") @@ -78,6 +86,10 @@ class PurchaseInvoice(BuyingController): self.set_status() validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference) + def validate_release_date(self): + if self.release_date and getdate(nowdate()) >= getdate(self.release_date): + frappe.msgprint('Release date must be in the future', raise_exception=True) + def validate_cash(self): if not self.cash_bank_account and flt(self.paid_amount): frappe.throw(_("Cash or Bank Account is mandatory for making payment entry")) @@ -730,7 +742,15 @@ class PurchaseInvoice(BuyingController): def on_recurring(self, reference_doc, auto_repeat_doc): self.due_date = None - def set_tax_withholding(self): + def block_invoice(self, hold_comment=None): + self.db_set('on_hold', 1) + self.db_set('hold_comment', cstr(hold_comment)) + + def unblock_invoice(self): + self.db_set('on_hold', 0) + self.db_set('release_date', None) + + def set_tax_withholding(self): """ 1. Get TDS Configurations against Supplier """ @@ -768,7 +788,28 @@ def make_stock_entry(source_name, target_doc=None): return doc +@frappe.whitelist() +def change_release_date(name, release_date=None): + if frappe.db.exists('Purchase Invoice', name): + pi = frappe.get_doc('Purchase Invoice', name) + pi.db_set('release_date', release_date) + + +@frappe.whitelist() +def unblock_invoice(name): + if frappe.db.exists('Purchase Invoice', name): + pi = frappe.get_doc('Purchase Invoice', name) + pi.unblock_invoice() + + +@frappe.whitelist() +def block_invoice(name, hold_comment): + if frappe.db.exists('Purchase Invoice', name): + pi = frappe.get_doc('Purchase Invoice', name) + pi.block_invoice(hold_comment) + @frappe.whitelist() def make_inter_company_sales_invoice(source_name, target_doc=None): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_invoice - return make_inter_company_invoice("Purchase Invoice", source_name, target_doc) \ No newline at end of file + return make_inter_company_invoice("Purchase Invoice", source_name, target_doc) + diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js index 8283acc4f1..4103e57df8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js @@ -4,12 +4,16 @@ // render frappe.listview_settings['Purchase Invoice'] = { add_fields: ["supplier", "supplier_name", "base_grand_total", "outstanding_amount", "due_date", "company", - "currency", "is_return"], + "currency", "is_return", "release_date", "on_hold"], get_indicator: function(doc) { if(cint(doc.is_return)==1) { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if(flt(doc.outstanding_amount) > 0 && doc.docstatus==1) { - if(frappe.datetime.get_diff(doc.due_date) < 0) { + if(cint(doc.on_hold) && !doc.release_date) { + return [__("On Hold"), "darkgrey"]; + } else if(cint(doc.on_hold) && doc.release_date && frappe.datetime.get_diff(doc.release_date, frappe.datetime.nowdate()) > 0) { + return [__("Temporarily on Hold"), "darkgrey"]; + } else if(frappe.datetime.get_diff(doc.due_date) < 0) { return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; } else { return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"]; diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index a99a86a993..339d2750b0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import unittest import frappe, erpnext import frappe.model +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from frappe.utils import cint, flt, today, nowdate, add_days import frappe.defaults from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory, \ @@ -91,6 +92,106 @@ class TestPurchaseInvoice(unittest.TestCase): self.assertRaises(frappe.LinkExistsError, pi_doc.cancel) + def test_purchase_invoice_for_blocked_supplier(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.save() + + self.assertRaises(frappe.ValidationError, make_purchase_invoice) + + supplier.on_hold = 0 + supplier.save() + + def test_purchase_invoice_for_blocked_supplier_invoice(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Invoices' + supplier.save() + + self.assertRaises(frappe.ValidationError, make_purchase_invoice) + + supplier.on_hold = 0 + supplier.save() + + def test_purchase_invoice_for_blocked_supplier_payment(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.save() + + pi = make_purchase_invoice() + + self.assertRaises( + frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + + def test_purchase_invoice_for_blocked_supplier_payment_today_date(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.release_date = nowdate() + supplier.save() + + pi = make_purchase_invoice() + + self.assertRaises( + frappe.ValidationError, get_payment_entry, dt='Purchase Invoice', dn=pi.name, + bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + + def test_purchase_invoice_for_blocked_supplier_payment_past_date(self): + # this test is meant to fail only if something fails in the try block + with self.assertRaises(Exception): + try: + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.release_date = '2018-03-01' + supplier.save() + + pi = make_purchase_invoice() + + get_payment_entry('Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + except: + pass + else: + raise Exception + + def test_purchase_invoice_blocked_invoice_must_be_in_future(self): + pi = make_purchase_invoice(do_not_save=True) + pi.release_date = nowdate() + + self.assertRaises(frappe.ValidationError, pi.save) + pi.release_date = '' + pi.save() + + def test_purchase_invoice_temporary_blocked(self): + pi = make_purchase_invoice(do_not_save=True) + pi.release_date = add_days(nowdate(), 10) + pi.save() + pi.submit() + + pe = get_payment_entry('Purchase Invoice', dn=pi.name, bank_account="_Test Bank - _TC") + + self.assertRaises(frappe.ValidationError, pe.save) + + def test_purchase_invoice_explicit_block(self): + pi = make_purchase_invoice() + pi.block_invoice() + + self.assertEqual(pi.on_hold, 1) + + pi.unblock_invoice() + + self.assertEqual(pi.on_hold, 0) + def test_gl_entries_with_perpetual_inventory_against_pr(self): pr = frappe.copy_doc(pr_test_records[0]) set_perpetual_inventory(1, pr.company) diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 9c7310fb45..8c86887186 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -572,6 +572,22 @@ def get_stock_rbnb_difference(posting_date, company): return flt(stock_rbnb) + flt(sys_bal) +def get_held_invoices(party_type, party): + """ + Returns a list of names Purchase Invoices for the given party that are on hold + """ + held_invoices = None + + if party_type == 'Supplier': + held_invoices = frappe.db.sql( + 'select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()', + as_dict=1 + ) + held_invoices = [d['name'] for d in held_invoices] + + return held_invoices + + def get_outstanding_invoices(party_type, party, account, condition=None): outstanding_invoices = [] precision = frappe.get_precision("Sales Invoice", "outstanding_amount") @@ -584,7 +600,9 @@ def get_outstanding_invoices(party_type, party, account, condition=None): payment_dr_or_cr = "payment_gl_entry.debit_in_account_currency - payment_gl_entry.credit_in_account_currency" invoice = 'Sales Invoice' if erpnext.get_party_account_type(party_type) == 'Receivable' else 'Purchase Invoice' - invoice_list = frappe.db.sql(""" + held_invoices = get_held_invoices(party_type, party) + + invoice_list = frappe.db.sql(""" select voucher_no, voucher_type, posting_date, ifnull(sum({dr_or_cr}), 0) as invoice_amount, ( @@ -622,20 +640,21 @@ def get_outstanding_invoices(party_type, party, account, condition=None): }, as_dict=True) for d in invoice_list: - due_date = frappe.db.get_value(d.voucher_type, d.voucher_no, - "posting_date" if party_type == "Employee" else "due_date") + if not d.voucher_type == "Purchase Invoice" or d.voucher_no not in held_invoices: + due_date = frappe.db.get_value( + d.voucher_type, d.voucher_no, "posting_date" if party_type == "Employee" else "due_date") - outstanding_invoices.append( - frappe._dict({ - 'voucher_no': d.voucher_no, - 'voucher_type': d.voucher_type, - 'posting_date': d.posting_date, - 'invoice_amount': flt(d.invoice_amount), - 'payment_amount': flt(d.payment_amount), - 'outstanding_amount': flt(d.invoice_amount - d.payment_amount, precision), - 'due_date': due_date - }) - ) + outstanding_invoices.append( + frappe._dict({ + 'voucher_no': d.voucher_no, + 'voucher_type': d.voucher_type, + 'posting_date': d.posting_date, + 'invoice_amount': flt(d.invoice_amount), + 'payment_amount': flt(d.payment_amount), + 'outstanding_amount': flt(d.invoice_amount - d.payment_amount, precision), + 'due_date': due_date + }) + ) outstanding_invoices = sorted(outstanding_invoices, key=lambda k: k['due_date'] or getdate(nowdate())) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 873fc74537..0d46318731 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import unittest import frappe import frappe.defaults +from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from frappe.utils import flt, add_days, nowdate from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_rm_stock_entry as make_subcontract_transfer_entry) from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry @@ -163,6 +164,77 @@ class TestPurchaseOrder(unittest.TestCase): self.assertTrue(po.get('payment_schedule')) + def test_po_for_blocked_supplier_all(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.save() + + self.assertEqual(supplier.hold_type, 'All') + self.assertRaises(frappe.ValidationError, create_purchase_order) + + supplier.on_hold = 0 + supplier.save() + + def test_po_for_blocked_supplier_invoices(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Invoices' + supplier.save() + + self.assertRaises(frappe.ValidationError, create_purchase_order) + + supplier.on_hold = 0 + supplier.save() + + def test_po_for_blocked_supplier_payments(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.save() + + po = create_purchase_order() + + self.assertRaises( + frappe.ValidationError, get_payment_entry, dt='Purchase Order', dn=po.name, bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + + def test_po_for_blocked_supplier_payments_with_today_date(self): + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.release_date = nowdate() + supplier.hold_type = 'Payments' + supplier.save() + + po = create_purchase_order() + + self.assertRaises( + frappe.ValidationError, get_payment_entry, dt='Purchase Order', dn=po.name, bank_account="_Test Bank - _TC") + + supplier.on_hold = 0 + supplier.save() + + def test_po_for_blocked_supplier_payments_past_date(self): + # this test is meant to fail only if something fails in the try block + with self.assertRaises(Exception): + try: + supplier = frappe.get_doc('Supplier', '_Test Supplier') + supplier.on_hold = 1 + supplier.hold_type = 'Payments' + supplier.release_date = '2018-03-01' + supplier.save() + + po = create_purchase_order() + get_payment_entry('Purchase Order', po.name, bank_account='_Test Bank - _TC') + + supplier.on_hold = 0 + supplier.save() + except: + pass + else: + raise Exception + def test_terms_does_not_copy(self): po = create_purchase_order() diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index eedbac1dff..181b214b00 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -108,7 +108,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -770,7 +770,135 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cb_21", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fieldname": "on_hold", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Block Supplier", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.on_hold", + "fieldname": "hold_type", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Hold Type", + "length": 0, + "no_copy": 0, + "options": "\nAll\nInvoices\nPayments", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.on_hold", + "description": "Leave blank if the Supplier is blocked indefinitely", + "fieldname": "release_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Release Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 }, { diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py index 6aa3b019d5..b6d588ed96 100644 --- a/erpnext/buying/doctype/supplier/supplier.py +++ b/erpnext/buying/doctype/supplier/supplier.py @@ -10,6 +10,7 @@ from frappe.contacts.address_and_contact import load_address_and_contact, delete from erpnext.utilities.transaction_base import TransactionBase from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this + class Supplier(TransactionBase): def get_feed(self): return self.supplier_name @@ -19,6 +20,13 @@ class Supplier(TransactionBase): load_address_and_contact(self) self.load_dashboard_info() + def before_save(self): + if not self.on_hold: + self.hold_type = '' + self.release_date = '' + elif self.on_hold and not self.hold_type: + self.hold_type = 'All' + def load_dashboard_info(self): info = get_dashboard_info(self.doctype, self.name) self.set_onload('dashboard_info', info) @@ -35,7 +43,7 @@ class Supplier(TransactionBase): self.naming_series = '' def validate(self): - #validation for Naming Series mandatory field... + # validation for Naming Series mandatory field... if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series': if not self.naming_series: msgprint(_("Series is mandatory"), raise_exception=1) diff --git a/erpnext/buying/doctype/supplier/supplier_list.js b/erpnext/buying/doctype/supplier/supplier_list.js index d99e3f8f0f..c776b001a5 100644 --- a/erpnext/buying/doctype/supplier/supplier_list.js +++ b/erpnext/buying/doctype/supplier/supplier_list.js @@ -1,3 +1,8 @@ frappe.listview_settings['Supplier'] = { - add_fields: ["supplier_name", "supplier_group", "image"], + add_fields: ["supplier_name", "supplier_group", "image", "on_hold"], + get_indicator: function(doc) { + if(cint(doc.on_hold)) { + return [__("On Hold"), "red"]; + } + } }; diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index d3deb06b5b..4802e02094 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, throw -from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day +from frappe.utils import today, flt, cint, fmt_money, formatdate, getdate, add_days, add_months, get_last_day, nowdate from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.utils import get_fiscal_years, validate_fiscal_year, get_account_currency from erpnext.utilities.transaction_base import TransactionBase @@ -36,10 +36,29 @@ class AccountsController(TransactionBase): if self.doctype in relevant_docs: self.set_payment_schedule() + def ensure_supplier_is_not_blocked(self): + is_supplier_payment = self.doctype == 'Payment Entry' and self.party_type == 'Supplier' + is_buying_invoice = self.doctype in ['Purchase Invoice', 'Purchase Order'] + supplier = None + supplier_name = None + + if is_buying_invoice or is_supplier_payment: + supplier_name = self.supplier if is_buying_invoice else self.party + supplier = frappe.get_doc('Supplier', supplier_name) + + if supplier and supplier_name and supplier.on_hold: + if (is_buying_invoice and supplier.hold_type in ['All', 'Invoices']) or \ + (is_supplier_payment and supplier.hold_type in ['All', 'Payments']): + if not supplier.release_date or getdate(nowdate()) <= supplier.release_date: + frappe.msgprint( + _('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1) + def validate(self): if self.get("_action") and self._action != "update_after_submit": self.set_missing_values(for_validate=True) + self.ensure_supplier_is_not_blocked() + self.validate_date_with_fiscal_year() if self.meta.get_field("currency"): @@ -969,3 +988,18 @@ def get_due_date(term, posting_date=None, bill_date=None): elif term.due_date_based_on == "Month(s) after the end of the invoice month": due_date = add_months(get_last_day(date), term.credit_months) return due_date + + +def get_supplier_block_status(party_name): + """ + Returns a dict containing the values of `on_hold`, `release_date` and `hold_type` of + a `Supplier` + """ + supplier = frappe.get_doc('Supplier', party_name) + info = { + 'on_hold': supplier.on_hold, + 'release_date': supplier.release_date, + 'hold_type': supplier.hold_type + } + return info + diff --git a/erpnext/docs/assets/img/accounts/purchase-invoice-hold.png b/erpnext/docs/assets/img/accounts/purchase-invoice-hold.png new file mode 100644 index 0000000000000000000000000000000000000000..e87f9f58523ad62a9ae20721429bcbb6b68a1181 GIT binary patch literal 37126 zcmdSAXEa>x7dI>+LGXAq+I z&U13#N$&qz&)4U}`&!m;=3Hf;eeL?&`-HtzmchG6bq@^<4Np$?g&GVg&@9+@E{2CrD7zv7|W8f6Osds>yXJ^r#WHhyNn-eff1g|va z#Bq_5E5NZa1F%+FJca}teKTL}7-IQ5JH0%oJQZbU&wXTp4yYHc%DIGEV^Q=KQp_~Pv*Wib50RzEygZ*85f^~JO8 zYaWq=ht4pM5g7u$9hD*f?QP>pvgNP$+rKZOdD!^R?u>T4F(M(jf!-B9rg^cR-ZptP z^*nA{Fmx`@ei0IJF5C8t6}4cLYa}YCB~&?r`vqR!gSH)hrY?iw@EK=(hftOMcCVmv~efkPUtA$6gcihbegvZ_3FeKTJ=m4 zxQuu{IV8+uw^lW<59CXokzT*=-~wgFI9X;Ds*AJ$Q4EiRFk(eU|Up`lR_$2V@=oeENK@2gfwztKD6N&Hfkm5bOIaBMw(T7V{40)*wsJTztP z4~oOQc>83H^4;<2^G&cjx{3^Zcm_S{p-8>+{hW-n4$*jjWlqij~m*MM({T&0Np)@<+m9-?cnc5HsLH(r9MN;l&#I++^p!iiOxS$hr zHSui-?-00uaQE$Uq}ss}qC6_I3#|V-V$`+#ppS*|f`1<;yPS^vCpC1bPRRHsy&*a= z3b$nEeiVoQ!-L>} zu_0luj#yigC2MRdKB<%*ZD@+>c5auq+(hjZr)IJ8y?sYIlD`n}*RiZvs+4c=toR9t z1SBV;F)K13Dup;U86M0(INylQI&>orej}sgK7198Vimn6G<}@TP5+B|&G}}xql39K zYwIu;DCTuvZn<34S5Db{nc7S$pL$}I>^DQ*edeP`38{CXz-7i2qFLME|1?3??FLUC z*z4B=7`l_QPM;WNnrcH&zoOSk`Ic(}?4*PVKNFg7qU+fnOUWi$nh?aKqW7=|MCP+( zgL1^aJg3jov-<{Jp}I#$CVE59C)O3c>vAx~4Ti&;XsGv0FPLZ=+YK4S4i|_F?Yl9f z2SVvfzoQPPrq>*+g@S(4zUJb`EyUE!Am;Y9g3bG!_u?EvcpeXUE-+u+h9WzDiT`-! z{IbGV?%-YSxyZGuwEH1xJ=^wsqZE-BKA*9x8M>Mb)Hv4RB;fkuuU6W} z`uM*H)){}E$c1=u(t)SfI2_pL8f>IKy)e?nyeSZEe_zG^DRMvZqUQeDqhe6YDo$%= z#SM)l@*=O}L?C6?){L{4oq8{VbKp3~?S_L>AkY_WCT;h0(h0b|cTkRcwg6aM3+d~l`vli@GJ##v<1=En8-|%X^U;breGU~dO$S%79}w=g z@bfd_HWB13kgJ)UM9dyDZAVwk2x3IAgg$EyP>m(B&6NXRK0g)ZcXm}cX$tSk@w{QU zcy^v1I7_j%E^|^tJ?}?vcL5e!Mk-FCs}DYqzG_`)o5yyhkX~*J@4sNYNJoGWSQ|XB z&m>tDt=ms3PF}~O6Oh6^-brSpwW!M!bSW(!54VBm(-{^+S0&+;m5PfJgRRj?VHEfw zKg?K3B!J%sgnnHp|F)$-v(}V@t9%N`ZGab4!<BmK4pQECEpFxjk61jEGTj zTXik7-l_6^NcMv9#@pe5iP8<~y$Rmrrt$4s8ErjA|Iqxy%sL_%$btVU%>{>$&=cqnpykp?b z(imZ->`VrGHEwoMMh~KoDu`~2PoS<#8I#~E`{M}ay~J2rx&@|fg953usk&ixdmC@p zAT`e!nu|M$KacH}&KM*&ZM<8#bJX!9lhlZq0_iE}J*ab;OQv+Wt%;A!K=8JX(tTa(^`0s4^_*B78Smy~e0J ztzIk7o;`y(!F4zs%Iv!eCpt9h9(vdwV0iZl627&sTrn2$R>PIPcIE{7nxiLCI7nTY zA&ah&{D$ty+*T`V*aMMq{7fB#b$>H5sQIq3=(5VKa4!m$J*2evA-d+o5QHV#4M(1d>ymA64Y&!bg!Qi(T`zFO_D(Ht!s$ReLMR{c!M(Q zSmXP~^bsGqZ=zopl$Xj}pmC~L$NsOyl(#NerhMf#@<<@-Nv5Nq#xlm}0`QLW`kX zUEo+Yb8~aTdqInPZu?*aK#Tn49A&cC{Oyp|_U5k_dimn%-yU55NOOckBL?51a~EMR zOsKaf*X%TmT#z){7e;~D8vFW0L#B7etW@UCavE5~TVdS!KPz(My6#va6%WN@H@)PAzT1a(mMNBw$6bIy3p~VuUE_>iA6G~q6xDAdas-Xb*;Zy4JH}}YZR~N4 zUEcct4{h3pOj|e=re;nq%dt8zt1EhuMSS%QMf<6EEn-M;H~Ew z`0>bYS+RCdxArKwJKhsAojDTBN-t(y9?7wErEAG?uYL?055`CXdfd2vIYaekoeqvqQp+mVVg0 ze%$G-_;J)IaF{a17q0qNWRyhUwy*}=T`&YpeA1w`?djEeVmoD;n!V!1*7W=EBCHMK zyD#8kh-?wAPTc@b_!`eiXMgnDxBsCygSXEunvssF5JD4&@8wN@wAm5O+j&h7TC(jPM?HaYCL3$XpUxu!=`HbNlP8uME;K7u3y*Q?UKjX~`!&K531_(@Dl_{oK8ZWZoNmo4*qqLG4~pzz&i!QDhq8 z7zW4Q`|AD>r5KF#9}F}6+0jZy&NsVel`naCDPl-im5IN`5c^WYYVQiic-%f`hIX}N za_fhEr#be?9pf!eB3dhLgV!OY+zVYe3OoZX4Frr$^U=z^Z-0ib}g*fePMp`|>X42V|vmW0X_m4d$>K6U@Z$3yA{27nF$(UpqL5RoAkHs=}m;Om72AD7tX z^OM+}pqW<~DPEx{NxnE$sGct>7{;@f+Z%8G4Cha=7-cq zdc^1!5vdX8c2Mc9J;m9RfnFW>d5J28*%v>kx#Hf1%YNBF=HD4KhzpvDU7o0wPS|~m2P7!$YL;K^m zM1~isri#q>lLdb|toT_=XcJ3Ls{p2S=W z8(MIhB+Mm+u46i4FaO2*HrULD3}nFwQ5`ZOmF0tex3QH=Vv@$+Cx0OEZe0}WQIr2wE) zV0&9lByLWeTCMih8ttnK0M14%>Au>w` zn2tVZ(rh_l9^O92x2M8B!*3e128o&i@dthQtYUq*HLr;U?7C^=|p^N#DnD zX-{j;<(B(?>*M5y9<3r3dzw&a&rMI1lQn7pO*dX+qzvWClZ)tie zaT>TOAl;uji>?USah__w{@603^Ov=+u^ee-9JeHPA~xHTpON=glcMBHyAI zzqw?dNVy>UIT)ICJu58IdwX?O@UDMgeww+N_7O$(bNm&F_dzTZP^Jm4!^ZCCaJJUf zD;Cw`4!JmQC~n5)WK{J%ViUhr9y^$s*}(q0%TUlpnfuL?O<|@Va<4?n&8&;f|KsC- z8{;GUxn@;_JbQw+by@bRo(|c3ZzbvOonC&{bT(^O!0a$Zo>wge^ni|H{eQ0e9=9skM?&(s~Y zgml`1R#Re6EjAKPWB5}zC%t4NE=+kJb(ytq72NYGHx-^Ys21}0@5bwq{hL+L`97t$nb&>jZo29uwCi78W}d`tX_rz!F4aBs zu1&b-$J>v8Sf_Bh*>}n5ZFid_QP{Woi(;yJl#}A`$G3IAwWxm5RU{YoZ6cwKNxh6m3!CLu41KE4tRB&LKWzNHp4!NTVl z#<+X{)_cw-Ey-2!eJtEJp-~pCVZvI~XZu#Lge=3GE&q}d{FPs`Bthq*2ld>`&n8g9 zV9yel@j~>6h>9eD;X6sG`K_gG!EgB=cIPwVX?|w>np6Rg%V= zt_j_o6fz=uQ`hBSxCIw=iUc`LcAjzk*cr=Y+WRg*nFH+i}HJ<8e^9!kcs~KOY=?fBj%bc9MY7&2Y6Y(#e2p zC@5>>0;l@p^2<9>K2VBB)DsE`v7MB_;fHPIP4Lr6yGP<5>2N-76bb%N90K(ei&e(7 zX^y`^*E==bGdL6XGh7V=vOI`zqqsXwow#u5$S>_!zQPaWpR11OxL2wR^qFN zS5%`wEAaxmvvkrXaQ{)igI{_u6fSGHiznLrxmRfIjdWmO|BbX%4w-mLP!ed)1AQeP z&uu;V!W)e+yb}-lba0IB;cWJ6E;{a7JA<|k;ei^BugyR#dv&j$OZG6+%bRH2hE2Mq z1e1cuK4Zu?k}ri|w5Pk%I@Ir~_fu4oipZffBjMhhN90-8)+Srxzgb<5$wWe=Ex1 zLnXc_I+Nc~#OoK#2$=X~9w7&OAG*3}%9vthrD`w{zV}iB?vK-CKq~DE3qgC}cikxz zmvK?O$)+?)i>=ymiI&$MBjD7)jUWBQj`|fhc?daoDOV=g)Vi(l(*SFo6P_+#4%q(C zcv>_pw_(Rfse@Q=S}m}cfcc57Wv7@D(R{H*uyGAREqaGrW%hFlRbDU59Wpy<_k#!L zk!eG;tew4_6Gqx^pym=c0`;$VpE$SepPW#i57{U8w}C-pOkvFQNXO}JqqfN6dzeTz zq~fLHMp-G}eH^_SZpx*-`O0=V^LV5p+CYej|I${<(y|8aXlJYXlCcO8xdu?VvWQCG zf8?tZC3pWZzqlH7QFH?gny`g$IPNhTeOSQB!S=V!PlSsd8N?gR$ud-azp`HetDiK* z(wFigu(I?d*1kDVMM zu-{n*y_fwajM#i#dOL6&(RX9yH44Tz=JLosz8_NfDQ_BjN9@T`&5 zhkbhtJJUkeORr$%{sgBqGr#~z0{g{Rzv@Dx?a4qbQ)_(bQc_{x(g@High&hj0On~Y zPZ%a=5j>yW059EOUirMsvQpa*7r`bdhUmTa%!H^;PBX! zZIja#@*?zx`B>?GYzupd2t!vbDGfL|6sV*2xe>Tlq%w%pt`z7}M1$Loe$R4a3UV?O z)}9p0i;y)9x%+64F-A)0=O>Dlu!Bifz*chE!#G`^S_-1w%6jA6N2W(XQmvo?j}i0r zSSV42t}w(Wj)-~KYU|~FvqQ}$*1b0u%qiqkniqY~bL-H>OKmuC^%e26!bgly=|T!f za4D?}&^nR7sSpLC@MRL2Sm$zsH`I8XyB$_AFU93k%RxK$*D452b%N+cen*NQG?2;~ z;d*>)`02X0!-;Qac(0-ncAkEvW-YA`_GMDOy-7)!;As)$xyAwy4@{9=CN%u93Bt@J zvszZ{4*sD96tl004TMZvq}>IzMru3G%nTMl}T+gBa}6>CSaM4DIRbP)(a)J}c^EML{p1)6&mF$sTE7Q;LO{(6TrK#rP` z9&&rHPhVC?*=-x=!!B_cUI9{ZLj|N30j%8 zCzoZVmNw3W_tXg+bPxs_O<6(^mKQS;cIeFVZ8YRN4E{PgD@+S|TP+G6W}qq5(VdL* z;m6H#2~lo~vDAQ7R2@Y0;jUiw6HwnHeM+3qNX2D$PK!p3AQgUjF|9jP^**fpr2|EK zu*WJ`G+%l+79|b@LOO;yyP%5}Z|*O|4kZ(#!@S^Zb58eJ5E>D_Vlept6|dRnn_^e! zB?hC&_pRN@AFFZVk#E{E(n~r&qu@^eCYx_!vIQY(vxyWCwA20@vXbpMhclZc=Z6n@ z^)_Yq=J?-q6q9JZNg4G<(CHNwNl9hh(r-Y2$EENg>L+euLy;qar>4Kx4; zX*uIL;e!ssL3WsjJukr43{GdKKU%#;ssC`o!$mC90c&j!=2(YxDkRj%W zo~y2gtoLEaxPDM9QgEfm8LkTY(cjuZY0T8;6QqrN*4H!O_I83;vBsNX*svoC>DrE} zaQ(6;LC-wWL0L_}IPl|3CF?lBxJ_x07y%0g8T>`WF$O;wsZ{mBNgZP8a~a;k<}^$T zXMd$4*YD22G`gFD$9WHb&q^^0sn|-I%u5GBgYmVi#9eIPqA4ChN@(0U-g+OHnqA13 z1|KC2_z|sam|T<^3;<)5knlf%$A@sQ8lBPm&OmOAk5r6#gPdw(ovM;@+~us@e$)CW z7NuhCVY~6!r&Y z4}WPaS2eqjD7yX!b&sdoXWIsw7v;CjLhy|aI=9N?!qE3FjLrP-+S`iyc^krCOp$l( z_C#%I>uLuMR4*$R%bhglM}U5XTHKK5S5>FB26bCJy^Z;V3Gw}H#J-ie4E2prZn3lS zd*oKmHG1liS)NSBoR%1x>3;#4WV{h~kGT3%24fO6k3VPvO?&e9lDSgJIz=XjDot5s zIaOXK=v_2;gA(gP6Rka|H2zw|-f_C=DJvCmcDKUD}k=g#b`1%5QCDeR-2Ar|dtIh=!$!~jjsU`g^lVXke#v$+r19JaE z!yiPWuPfv?obecdGkA_(-i7R-zuH^~s!Tck;ijYxtFGSaU@_!x4@m<=);&A+`(u6g z?%-o<(o+j-n^Z&HlZRsG<+ldZ9iz#|yDsyE?2_YyI@~;ggj!j|o6Shlsr5j{!1Uny zo}Qh4H&Df$`jsY1Toga{`T!o0|F!;hM(RZ5`PtV6MOI>dM zKwh_+!C!|i7=)qbPd*KW!D%(h7S-L{pm3_cz*m2OURR7>-O$B5sC0s{-lTTm#KK%q zG=5t9aQNdmtye}1=>#Zg+xjE3#Pc3k?B5J|FD3sQxM%@0hHN}P7j&%B>_bHUU6zCI zH^*=~DFyYUXY+-EqpR#%bg6nsgYQk4ZdJ9hHjTye_~QNHaJln;z|vnF?!)~MmJ@#_ zt^XUW3gag1b!X~{Z+vFaa`GQuLntwVBnVCz!=ei5!t~{di~nEbi}3$<7~lUJ^ztkk z=z{~`;g`p_uw^e=)*J<0)UE|$!e6bnVvCLfaVGQ<0J3QWC1n$U=20Ttpg*AP2pGsb zd+q}e_ixyC2J9t04Vkc zr+=yxz)>h|!2?3~sQ9hqq4V!Acw!I0qn|oz1>|3jF*s#hGmkgzi7AWpnb5dvMjSP9 zwuH&_ff1V+ci%lb`gJ~z_NoWS7FOx$aH}>u8rme|G>ciN$p|CO>^{$j;(i ztC}7a*>>uXVcRz6juGIT?&vB%gE(U`+E$S-4aBCkV$tZb^oBD*yZ*_mQ08}~7xL%k z&)Sp8Ppryp+h%@Y*z7lz1Tr^c?9>ptn+;u?>(Yf^Zm#ggn%IC%mKbt)vfI-sz8HTy za(9;cc^# z_%2NicT`18XY$rF6f2Jdshgo!R31>D3Veh2SLh09M5LiP*;w{;#BJd-{+ON%M9P+q zEbWvEpkvGMT{|fdZkzY7?7r|3-kR%$(NF?%jI%W(FH&po;x^v6-WsuqN?VuP+djS} zv0?VwyAK(EgWKKt?7mNoC?U0%Z)IXK&yV#rNLae+c;KK`@RSVbn8&Pwe z#SqT?n{6zgST01xuZLDT-SMqly8a5upFY4Wn$?cT6$7O-a<;HjbA6K2%P8P%YD=*n zDUN1Byr4TdQ_}Mtr%bKU_9gH=-`e@IJt_LMvf`uFApLGHne9k+r|*q&;i-?;;glg6 z7a|UgAkV*!^jRKe4O4on^Ch{_r(5}S7(y!ds%;ktY!>isZs?nF7ng|S;AdY6?KcNI zEWXmZt=+my1(bGrsQyYK|CmMGb#U~a)$qcQ39+)}bS7J8qdD|@1QSA0Bp#L@0}Hh* z5A_|#@}>LsmEOLDTl#qq?O{Y2L`lkUWTW>^$}3}LQ>gSqiK4SAJZ`(~wx0`hFZ8vR z_XfY5H2H*>*xKvC-JwqO24`E(zsu0&tybi=$8eoAw%gC2e%;j>iawQG=%j=IK=5p2a@yfAbho;qK?ViFtialVovwegXT? z!>=H%nT?UhLCpQzPt}jrykw9R-&?cNi-Smw)OmmfTeq%tfJjPq+z6-+{!^r@WK$t8 z7iq4Nc-Re_or-=Zn-64?jCk~YFCwEesgI4P$Aim1nQU6qbp)WNmMKm66-${}P}xbo z9$u{(&5gUQRLc|lEe2$7np$;7X>b~YUbJ->PXg|)S4UfFb4pfyUuMi}l#CqmfleR< z%!rYkcG}nn8*e0|ncEd6nK(x74R~u`8RkI?RaW03$61cOdZLL{8VcsP4$^HCGXz*3 z4rSwjD8oQA{`1|Iau@z#mLF@ezuYjuk%tp5&0ZRz;IYA5o;)rz6p(EyBaz2JvWLj7WrAx)xpwJ^^!HQJq9%5K%9q8fKOsNU|3>oxJS1Zj0w$o z19Gvr3FZqU#_z+R;xlHZ&g1Kedi9!J+>v=3dJqL_6#*5BfB*CoC2lmZPIeZ<4c#CX zcU&dSi2+&PU{2Jj$t1Kt%VO?-!-NQBG`w!qry*ig#`n^KLvj^qln(aX9~j-at_2-~ z*SnJyZWv+Blri3_$1{E{yWoItqgv6MrI5*#33fVNUh4%IPSn z(zbJfYd;mc!&HP{pzrKm~R7RLElm^4YFN7tQJ4b&U z!{}9#XKpsG1)Uk-wypnj+ln1AdW9~YAU@teuHEirj&4A|68`AdH-19gl}eXHSGjx_ zPv3Rg%!Btp37XgXx9eJ$NZqJu1p{tQg%>DBfA}*k*@b_pjUH>Yfj<$G{|CdwifThc z6V-slI%VzwkN!u?4f@Dy-0TbY$9m`__COf%rX@K!*V7?;K%K#g#MyXfI#Oa^qmJXxiKe-_G2d4M{=Bsn_2bT1y=>d0;j>RFe{ZM%~< zBfPOiTcNm=IB$GuLy`UU%QsKi;e>SKORJ}CHfd4Uiurg?J#Rek+86%`!Z_T4*2~4i z^B+(9YDYH)Ct0a}Yk?5`Xk?rmxsiwgeU5_ix53&&$n$d$!wD#EBV!{J271$jN-<3e zTiZVemDByU;m#!mVZ8DWJGb^8|8(?Z)qk3M&cS|%nlFZVLVTg+QVs}FPrBbi>*x_%)74e_6-15y1s+zH!%T@ z!OS6#7Lw1+`pPc3@Cp01ja*-GH^ep6UPv_(6ku{f{lZ@APY) z1fBkvEYpW^c|twq??c9cG&&?IvnUrQYP?Ni(ftJNm{tfDjnCIyKO`L6AO6Z|Kyj_9 zGd0N5L7j#ZH0*WT-l})Exyr$5@VEAkOr=mjmRn(cTYwI|@Q*^2ta zvMI^uVF=Q1Mb<5x>4nmjZrKML;xd}O}`C~4qQ7JPm~^u{u*9- zG@huRj@{y1o0lR+d^(whJ=>CW7J6>h3gY(||beq&y)q#6g$)Vu6BVlS?=TSa&t z(?jkXvJY2VbI3t+>poGi{{s68z2MJ{D2_bCvj)qKhtQ+oJs`CK`q5JMj^3lnAsM@` zvn`$P23Q5M(_PKQ{<1Egzh6-p{+pg4%MkUqAA80IFIoo$LF0ZW?ShcrM^1=lvjF0e z7|{7?49N4o45G%t&RnD)Lp8aVNl-#1ezb7iWNSp;jNTOHe0K%E&)m+gy%!& z!i=AHOd+3TRaeP)+&Y-pyV#`1T$j_DoI3XO5!c>k&!6!7 zo{_{(O(nm0bmmsc{BLj6*r~M^z0cN9NoxqVj6INPDM`8w?A-+%7p}{Wp`lfm(3Dg=m&o4$MXi7WByJ5U!}?CJ$)3Fk_^0*C}@W2 zrj#IATia}1j>A*u_Z(SE_7bWLg9dY0dij%NLCiC`d74x~f6`kG`vUb8hFG+j&Cw)y zxpAvd&u;^g^hMdkvG;DOw~&yaLu1->J6JM72Klp2JWv3*rxXJQ zi5UZq`Ii!N!Bn(kfpSlKGaw!u4?rM4rzro<^&``C2uSLWMp-G5LbCIozGle-G61>f zObBh3ybsE@Kp~b1)9te5br5B=c+&6$8NR1m?7%ho6+$iEe;2*z zn*rVjzWb>DxB)ItLEB&CVE-W}@@p6{o*CwS9NHO>d!T+!0QyU1q{JzE$Mk0}p|AF2Gc@N(m>N z?r0Vngp8E>ynC%w*KG!m(cNL3$kYOH^mvg{YkK>2ZGyfA0}pi{^S6i8$WnttzloMZ zJ4J-}cfh8N=eIPAS=$jQT|7zsJnQtR^RccktAGIBn8uapL=L7FI|vo?te2wH2ku!Y zQ@pRzUw(=ZkzOy5=5SCr7ALoCldpHizhm&r;{>q1~}4gM*xWV z2i_`pvH_K*87^Bd5IhsJX5=r@_8&urMr&-iAPd;ku!?F7bSP*ozblzE@`PH zEmIXprAqs0=$Z@=jTQDE(faT*6=7Z~$Yn4it6rI$&(p9K3^J3B z;KI;1G=)7Fg`?Qk2U^)qX$e`XM4`fk_DHE|X{16m# z(Fpw6*D->oQQTN3x(!Z~bNK+$4J)-Bbtk;>;_1MQY(|^!joZ93T!??oI)~O@sr^a^ zNpUq9D@e0QpmvUN(1~4exE)j|Z$@X`Irg?NkcgHoVNbv}e5~+MsaZj=Gz@gTnx@@w zuHNHqh<)-XeIMluQ|n>9{~Ryssc_az)~Uz`aMt+|)6A-uUbse~__N42pORh|S1ZEH z3>N%IgIJ7YD^<*tt<{>Y`=|j;kfr&n3GM=p(n3FAPw$^9)ts;2B`LYDtP-AX7i&Uk zyezkQ$Y=?N^4rN{aj}1quxkK3pqRH!@YpPjF=gy@(PQ&DTZYVih?3YJtmq`IX{XtloS3@>E|-d(G{XZ3%}&j$3aPf~$^QO7+zu}Hn*o*`K0 zhIkA7F+y+7k2HnY&Bmp5M~fV?c!`};sA)W^HNYcW%%jKI@pBChir!oQsWEvtro!{O z_)CfsPkxH_M0h8GX4Y<~LDDOMu>&x5nWaXi!@XDGU2OKLbRMz9K25~IDR|mBXZLb} z;{YSOtb#MograP8NL?$LJ0{-V>q{^YXiqoX`oDQ34!Bz4;KB@3^`(Nf_;=&+K6kF3 z6%2indHb2!LnD{8aAN78%Myu;wt*r(ea2Xiuu^)cDZ5$+rLy|sH1~5&$fkh*ks+O~ zIYu}jTqzpVFHMzCL^LlC&xDE|lSuEdiYXh<6h#WFQhY0RAXXNsLYOoJ6`B;k7x{rH zQzAhx&{Ntf7DOMMqPAD5g$kgGJ)h8D{ncKK{FBjH0rs$m%tw2`Tsut}oM!&ixAIn@ zfc^?;aQC}kcJn{Ah01kNHqo*07ez(UvCTSTM2e7xa6AB9*?aI`l}L03{8ZmR8*~Fg zNAPJ|59lcYx*lGKjJ`c_s2ja!k3`YT)x3HgQER8Q$)AB|>q_lVtEPQjaZKqk7t?iu zs&k55bL>iQ6LzN0v-a_q^3u0`%h@|Pwgj}NDC^s_4=C$iZk*e=xi?{JAkWiE>OwA< zwf6bDi&OhuIyehm>iz-t4`cxi;RWpTLqN)aH(*0e1YA+)9YS6c40w-jX{XONuM5#C zPgLSSACUw%)Dh)b`A9eEy20kG0u+|q12O}_sI@NFA1Ol{#M@4ISFi#nJPW56bUP9g zoSe#r)-p|u4~irgKydNAHe07><#_uW2^IReEMTXSC&!lsRi0IMm%hM0P9Xic2^#BF zfxyUl9S9&~SARA9w#TSInJMnIS^ETo#!gJLjJ!}a?t6FBwWXm4pYUBdBxuU21r}fF z3v-CY52i98CPRFrHI5o4QN>$e?nt&wo_CpO{VLAjx5eUQ{v_j^>qp+e0;&-k=0O)L zRi2u_*0`C#d#0%^Sm@|@zdS7Yv;Vl^6#;s_z*8|MG+TYo*EM+?1*@Wt1s05U1|O8F z8as!1lDPi-`O>EhTfmpLGn;$Kot-fcPcE(#=nH}uj3eXRJciF?P{V6TKE9<_k#Cq3 zmya`Lz}|b5!Csc*oOX4$lb(u7j(ub9hygjy6wjaXv_1P(Y#9mq0F)2<|B)%iCKQZZ zU)&1%SwE??!}^R;1Dd4(%zKMEuJrwMiS({lBobGPgzrtCL$5xOO^cQ8x@tg~J8Gp+ zFVhHv5PZn%_pW3!#hR8$>mfa>Av65JE_bPZg`Zr0fIz;%|HnL@kwF4SJ-4M8yK;b* zri@Xfb0t|`;l@jY*W{6`PxLKD$Ctb!l+N6N*CmZu{jPO&UWpPVYB}ks7rc#aY|J8` z{^^UXWqS(`o3Ycl;Pz=(T*#)seo4{t5^Gxybcp-(SExPkWgXdH(e^7ilwbb+U$^5U zUm%@#aT4&U(GJu$3l#D^jIM7o;dsq>1zn3PR{85PGpAU1|sfP&x@! znsgND5QG4sqe3X5_Zm18-0o+;@A-b5@BDbbbN($cS!-t2%stnAUDrKnqUw1T9RLgy z^V6!xm5P1V`xbsz@0%k7s{0q~|5~s_&XxN#-JljaJ@d8bG8E)p$ISq&!#~}2<5@ID z769@5KKkZ%C^^3|zW|KOv~GAf0_CQ$vx`&SSxWO7evnBwK}6pYmn<4qvkNWsC0L#c zuw@9>*kB^du8j2{zqb3ArT-0gyW~ z3E=JHc;KFKj)a3Zj90q`icTWN1l}+j!m@C;NwLbWU_MgEb;0gfYhb5X^O$@1Wz$et|`usNt($hd9kROEr zqC9Iyy2=d^soX(8K_vP|_bXFX?%n|hp!f$g-4wB5P&R;{`0(T=&cgdb+jaRLH~x6z z9A?x^I~j(XiEKugh9_Ql0~-Ag|E`;_`m`Cp)$qgpv+QC6NFS&NR1BNB9xh0HmXbuw z7uLL*qNUcqz86L;3e$Y}MJl+BhiL&z2~xpNqpY9<@$A2M>U>vlc+qB46XI)!ohL(; zkkBLidB%?m7v9KUW=nncp_CrNmu!4*X{|zH ze;#z{Z%CDt8MImc(*->qOl#LIYRNJ?_s$of7iWXsJYu}8P;UqE2M0TBG4xxi;aR_Z3i-SERBU$q%N%2Hy!}rU1Z|2LMXCFJ0vtJ7rVP!vj zT0jR4WNi4v1xcNF{S^JDj(iVfUGuq*U5pJph%g`9UJFeKk3*)_*kz~-9Y zga{fi{B#kAKWKcU?)_y&lHaxx9P<+XXXyDTpkEwsJ^hIU*J0OeaYd!>1m%|Kli|e^ zu{3rWN|f$AT~KPdie7H31qqJV4C}Loy*Y@lyEbch;o;8@;z9JNJAtRSc-ZIM&_p`y zhWrQnW4op%H`vXPb^bWQ?|^-)ca;=8@yxQ?ykNQki1u zI^n{N?wGtk@PB{<$Obx4$l}D|$6`L5CGQzhy~6yKX}C!6GatsJ{*}M@c(RmniFzZlS-TtSLc6IEgl0Tlf0-|hdS1Z7got-LVm5)X zOZb`|Mmmgh^9vST5?)Ji^a+jctU=kpBowmGbYq+ z*j?FtV5x5*AvW*0oN>Tjv1wFmG}vdMH@_W|XuLT{IP-jlONL-;rv{gSh1XWGY3oa} z^IW_!-r;)ZyG?TSEwn;wdUNNQ=YkVjN7%Rep4aK{VWfX%({VZL+{OJ#ev-Q50hncw z@77aE!?BfH>h?Z5)8`qF>Q7x)O>E1x0}Z!VG&X0uJ!)q&?eKS+3Cqp%{GmR5F)hAly^!Vr?iwx1J&AB4| z$&yzGm0;9uED|g7s^-iGqEY5(${Rn zH@2>xl!#=}_%68g`HU9GTX}XQ!>Gfyz@+AzIT7MhFMS>&Q*yrf%!<#K;)iT&EXVJ&ph4O8M*K zS6RqwaVJJHgwyw$1}dkG&%srN$U#4-I25EZD=}v6Q@l3Z(DU9M@{*?@kY}mv26ti#Gui|kK3X3((G3z@iOkTW zni*c}a(f|a%?Fr2Els$=X@8IvH~e*5VKvfN>TFq9K40u4K&97@o-Z89{kKmRUHo|T z!cMREF}>JO-k~_M>3CK^Acn>HNTZDuy<#jklsPk%u-Z+KiQgFA zzo*I01an7Q(cHF{`uXAg6(;KAkN|l`o79frpzt^3404MM*bhK3V}yyTcWmZ_&~-ZJ z!h3Gqhm%e0>dNuqz5RZ^2>9Wi``we>I97D~y+(aLpGXKBG??uA(fcE$Ve|{njTTQ{ zUBg)3*OUagI^&5PmUf1ZJ^wT;8yW1EJwa1Bm9y|oXE~eKqe^#W|4J7^Q&2WX>p7M~ zd6&(7s*#a6-RP__?d_*1DKeICam&p0=Jk!Gpuho_>)|h^E2!do3*Bh+=Et_tUC}e| zoX_H9AODx9WFox}`>$B#BhVtzeo`h&(dn~P1d(hG{_*{nWJm$p8qYhYs5`9h6|{(S zAljJvU2mH0DaL5_MyCW+LG>i9!itP~zb&hUY2Au{t)?J1x_{q); z2HSk~Hn$n~#kG(BbnF8X(ciTlW~W&1Rj;Hho7YpF*<4@0A2)>I+uf*`&`ZEZo9;CQ zd4^pSu4L`zHcK%VYe!g=ciHB;iCR%*!f<*y$yr zO1;kv)!t%Ipl}DRRX*G_vXJp2RzKY$A@q=rrq*7vL^x0KuDI0kEQUa?7gmCNj1&q1l?@?eLEx3HkEVwU*l+KXPR*N83h}NDt{LA=J zp585_0C;^3^U$aMd>&O(gSgz_vhvM|UV&`2>Z+4Tfvk{cPLZS#@sYM2@v}Z708Njr zowRnnNtJU;v>6!K~vTc{t9SzL)qiWXjjy)-Zc}S;aywG?MCi!uF?LOJ(H*OB}zMv{C*^ z(Anyfi92cSezLhakExEWQ&{KdSB7ZMGTt zT4LGl6r^TI8`l!km`Hg>RH)ZZ{gi-Mp~3v8_{Ez;xl1V{1eOamU&{)0;AhRJH;r)Z zIm#`nX0D$o=M)^W1p;~sd=JGUJWJ#pAh*rI4m%jjuZ8iG&bcu0iFHHla;EAQeL47s zsQ_)t2Dk74D)~O#WBWACHq71tH@0&$$X7dzn~CuZOd=Ycf~=(>uYApN zeC?XG^3r3I@{jq?v${Sk@ZbSnQXtZbR8Hv`ky=oV6H(FFW{z$&`Y?tEOQjzD@M$n%^d)WV1w0K+H>Mu>n?VDTTrXW~VyX zp##2uk4jc~38Q3;+kb0^jdvHqT1ispL*t?+%;mjKKLcLfXQ`@0;ary>YMvi@^+UCk z;Z$_`LC*bB4}!KY`hG>ya%v&+0tKq7m7`@`r)qG#AXG*t>F^u}F>^Pd)X8P<#lxd)3%st+ly#3xKh(kRDGIc2+t$V&2CRcMN z0&m@^IcrITQDB^(Tb+1T0XZHQoesoo5oNWh>rIUU8I+W_TFUJ7(UsDWLY?Xm%3L{I zvEn(s#m<}b*=m%~IDx%@>3|aum(kZU& zLZ6y%HhXu~bb4z6N<9B~kdsU>f1s*z)LY)1Bzto*5m4BXyk@-8!a3jRc3#?^v8eVa z(8JKg%?&ohW)V;5cr8A{IV{pcRufDJD4*@oxr2%3}joRk<^QoHWi8nP|sKX@)_pZR2&Ct23}-mMTY zN@}I+lOu`Z6YRpnxsARZy@m5zh~?C>$QSL5W{e`K>tznm=A48vnIB@^9E;Z5Oq;V` z4`rt3)pYyCqV{Vl8rTMb9$)@l$Am~bhBQ)@qw_w*sA(lwrgg;MFnPm|{VYa~)4?$4pnwpG^9Vb(bW!m=xPL z20vxpeWXDpW}982-lAlgxFq%dMZ3N?wMrAUPL-}v+L+9VCSpYyI!Bd){e;H;8<-E+ z)oUpTY~;3<8G6+rVT+R=9`4W3O_3M0I+2)ibrF=h9GNx zrDgoS#ZJzghlNH}*G>`!P14NFPAI;y*PY!{*w)6rZDuS@pUv63i{8G- z07HM8%~>7ZRCj2Xg1?~ZjN%upp2)*7MdEKR^RJu}hdg}AztcC3F?4Gl%X2fR!=(>p z{B(#Hxt`72l(IRePT1|-ZuN0;oL-hPINBuOIKEU(^rnSIT7^gxjbZdYqBj{jYL6P^ zHtdMFHJi9A#ZZ3~dbwoqC^}`-R%nZ8;0QARN-L{9>%4JA7K>G5I z?OAXUMQf>1iGm9jw?B8jxGGhk)`He^ysu`QqDu2%`|#4DW3u*>9HrN}J=ct0D;3Q= zBjX(j@8Kb>R>a5#bE|PFjw5LOSrrkzU7U8)XeLplxl*0sU>c5tj}_`k#?04%SjFnr zg>X0$L(i04R1_wsC_#EZj%Ra-6NzfEKo#K6bv>?E-f|F8OMPuLR^OlRw%Hu}-q^E5 zKsJIWBGm$(g3B8%M9CI-97pKH{`Q*~oTCSH)9(*e9k^=bsK{%OO zx8cM^`>sp)XH^#7##prl+(%^F({ac%iO8lz5xYmrsEdLQ;mBN*mR1A+vl~{PK6~2I z7YB&tbR|d?7K_1zv;Ax72i<$_S*!bH%h$?oWI8zHOO~WW$qlhXU27!(1LAo8c)a8J zp~GlAwq*7cs5dKz|l_s?gX_4Mu~>(bXb|?TAYZSJGy*sc@5J{W75hE{SqKU{*}h$t)1&9 z6PU))x3Egi&ZZNSUh*X+iKWJW-i+#7XY&cG7Ngh~Wk@jdui!*aS|n&2Se3{U+ocZ? zY@6N$XN}$QBtXZAzjDH>uY88}SB>=TI;yC4qOyg2Z6B9_pfI3;ZYk8@jjD|d<2xPD z8@`2>awxcTWQ5B(WVzfcxecS}MhL2&?(_a&q%qFHbMad^c^7KGH~aO5`IoA|N~1D( zi>M&a#ml{wv@Jbp5p5Zlo?r2wcE(O9HLBi=j-PUJ85k-|nUdYAd^+}NG~PV8zBrI` z(!XsaI^P(KC##rW5tn}udk|W+-vuw_`k25}EYw5@DMg7Pz6U)D26a_bFM|_eQoWU|U~U zWYi49kd-S=&68tQ5>h$cgzA2_x3fJV;I747Hmo_tb&3?VR2xr(qhSS8PHpA9&@}U8 zZqK$Kc@O$`ADxld_qypqLZdE4fa?HZ+|E(I@num)0;`#WzEiaj%ytZ~p5osb=`q6t zEtnryS6}Z*n4a+?W;(wEG>G(gNI8iY`GOtkShvL%pyaOQS0LM}BP&x$ zFX!&jr-L32em*6dKNQyPJE*D$pi4HJ&m-tloWbI9w-UAs%Xw;nBUWz4ibR)YaMd=C zj?}P2(@NI5`FTEMn1^klFqZXGT~x;$ETo~`K>!-IX`s(nla$bp)98{Kxt0F4svS|b zb%kHur?2N@jn~jn$KAEADFG;5$|h>-i)W(hz24{-SKLrq?&{%7=sl@-rG|fgGMZV! zBxY)&kV0Ggu<dQL>WvViUv4K7x{iNsHF;=kF<|1TkTF!CTlsL_rW zr8gR0c=^`|-*0NkC4j{Ld8(Q5?u@;qvw*Qtv+Tj5)aq|;-U|hnaqG~f$eOs{8Aow3 z644W=Up4ilY6D%4h||!L z=5573(neYxglPMDN2^y>`9la)A;~smx$iGs9K5Y z0?pU1(~HM>n@hIg&9qj!o0O1-0c!vTckkEpxVC8xeaIfy)LQ_A?)Ke8k<|Nek<6ft z5vrpMIdK{7!Joy7yBn$Xw{koK4bmrStl~wN1EC9C!fA5xi8@i^k{O;8w{ncTdtD`U zbTd6C3dPA&?^ZE%N-1?ji9q}5bwMN-&NyZkg^7EfK^tlyZJN1b);(UY$PFDY0Pp;j zEui0?yFnn1uK@TopUB_;!T*G>uyMi4tma$(=LRYB>Y~!!7IKJ5EI*(u3n>{Kx=fR! zrm#bI5MMq}-A-*)&98|{t6f2IySm%@a zLP4$rAg)oFGxjxQ*@m}RWzqBHN1l=;1Y`l)>7jhL#2Qk+#^Uz&IoA211IEH6|Ge&b z-zk5ymJ^T>Bn7@R&$$kIz(Js2#?7_{iTU7|f2=JTDBUVqa^<|4G2B<0xv?h(-SkpP zbVLiQMYXn9bn`-4Q}f?78l_FhwrXK&rb@H=_HFj9vFcmq8JSmV9ae|qDD9p-7j=0o zUV&uLg=rxoD9+IQ0f;hJ6E?jnU9ZOvJ_8KgLssXp*lB{vlcry8`bF@d)3 z7|#H~f|1V89_^xoD|vf${h~~&Us@7>my}y$#Zw&3NCzTrMpR_3zbwnfe^29fao0f} zG*I^x2zFB&dmV^}kpao*w_eK;DGuJn-(O5vtHX>p4N-_CP!bb=w;c#(sJ>RjmkCdY@1NhkQICA7Pobs|GcF_}PGKgKaS|FlUMm$H zuu$yz1{A#6iq|7^zbU78=s3NQIXA@(>!7B{nS7}`XOv~ewQMhSQGfaH5^XklWP&u; zbyA}*9tc4{$@xm2!3k1pSELt00;M4 z^>JT5OM#}lU{$khy1AM3#};Y0upUe!-=7jt0gpasm+xJ#k}Lr~NV2x0)-Qf&7V|vL zS{|erdmCRQd_KO@_4`~nj%Go*NuUSD(&E|Jn;lM?n>jd$DCcd=9y$W@X|zkqD#9k$ zVGc{YO(0&~gU-;p#i}LAE+;+Uj?(qJ^8PbZF0mpP-%{lLM7SA{aSaxsxzg4|z9N z1{djeNyuy)D#rIdw)<+DjelRfr8X5InUIS4x$v;?;||YQ?);3NUn{lG`QLlkbV!r%7k<0&YSyicF{e4Zt$Ku*6f__I|1n|_uP*P)xIY~ z_FWtdwBi_*P9|(nSGi1+bA{7+f%UKn79&&-uHSCs?*{`N0HOL_dmBy17WC2D(_0!k;=389uwK-oS1AMRTLF`fjZzl$Pw= z#>xO~f4R(n+xq4I*NJ$yWK&}Rg{hssQ_}9R-+mkH_*Fx-Q#&3Tei(Yjx@Bgt_*zyb zQ8SSpS|@twU2;@?s)24Ju>)ab7TC=#d}yV3;#k%Z9{z-$bRpmi_dH_RV($IH6u$xV}`MIhOnLs=I-n zzz~FY&2S!?(FzMh(F@#-v^U2XyRr^H%&xGIpAC$~`&*$F>L_7|Qcw|fsaik)b zEwc`r&VfnHkH#%;+KN}&Ghzs?HhA64!4lbI6`9msR-?l3t^>0fLcKY$O2Kg3#MpiH zsd`kl1$s$7@+_BCIiH~7&r}aD4NqB{>lq51A5S?*ypL`nvxB+1Cb`ovc!aYCHVfKjoqItxXj6ZUU48{ zYBpZT@eK_2D8J8hQ#bufA9rG&B?oEl#Vt1Hz!eN@_dA_@C=J!2((j#*2>yB8A_JAv zoj;xf0_n}BmNx=bb#e|=Clx-;>%gYwfe3302buJ8<+YPl+lt{HbPU*Jz-7e zUt+pnXe5&w&K+&UoM0_)pV4BBS0@ob9SKsg@xRfDPj8Zt)|q$>27<)w`BZzQqIo@m~^z48Pa`e z7R`xg!v_vzi-b=U`g7&xxrInGeXDGC>1B7c^giy7Q%vAgXWm|-urkIkH%Q&#Bt-3( zm}G(}HR#Y<(ytfHyXS5#Nge3}OY2JzclYTWwMoXSON-_-7F=4C4Ky$hyp64U>a86* zJAEB0-`tu&5Lj+^a!paYJ+|MW>(^_=XO3L<2Xw?NDU!Wh+soc~KNB00Y$HhxG|Ni0 zTlX19G&cOxer_a+>|&bI&sjZEOnc;QjMjTF{@3!aeH<_j=8#pbbf7rI`t`y5(K z%$laSa>_1a%gDRzXMNM!hTh6Qe^#uoUgtU;HlKpJ(oAL^}blPhx2x)p9Fci|O`Ik&&Mw@W7|YrE0)xqf^K5+5oY9UHBasNr zr5UZAk@lL46JuF`MKG9P9r=m06bZy-Z;mmFF4urjiJy7rlB9=MFOhW;w!jU>$ZmOA zs>C!}B+c|^#s3)UI$EZJn4JGDNf`VY+)T4UK}c^WV`28X>vAR;oEBdKWb0>(0?Kx0 zAcjo8MV)CHwxAMuy_q1|(S=EGGg9I=coVGUmG)!VVFX384S~PC`Hwy2|FCTHEBX6v zr1^g!LUnG5)E8yY_nfIsIATa658-c?AeurTCiFNa$gzm?+SSGvVPUxEZ4q74Dahxv zCwT%=&RQS=dFJ&3bbCZzvZVH4VdA0jfs$KE_XSAN=9AhJkgwFFDM;6jShT)d&z&T` zj|BIm0{6TvO`d3f&Mx3u~ks8$_<>R+5_@K>^8`p8zt zcM2k|o&tW;;GbIz3FA8r(H=A`4?JzjnbiO{C6!|jk~LbCdrw94puWKz?A&9TQ$qqxn=1>whB2~hdki0TE zpd1YwVh(&~tt69=)Y$&WUnXDA3LJ+()S|R1LXv@p$hYrC`R9F$$>Fb?XCZ(7a!i%m zOHwl|_WfuFNvQyBEH3BKHOCr(`HsiG;cKF$Q=EX17F?Mi!PXm@v=3`deKp zSqN@(`zZff`K&>#oS)IzXBCR-+Y#{Gl~#VUQG>LfG2{68JALR-Wo_I$5@t4%i@#Br z?HfLC_hVd#u!XM_x2}=<*XTy$)%((|GC78K;ng54x-W;G;(gMeBN5waf!P7Tu!l97 zm5h5fwO4_AviQH~DGAHBWW)>R9vEWpQVh@WVT+>%(QzWX<1M&?WW{q~lidOS&BgaU zSsvW6M%M&q{0!zXL-F-2r8I;@4U^CcWC8N#Ax@TmTB>bPr7oTs4Q2^RS!YYR-D+uP^ z!Kl42^W0DrjHg7m&4=$8()8f{voqV@t*GTo>a0AM{Kzq}Z%DyXAw6I+Z1%X|5p@-s z7f0B1%&T9K#d&%z<)CrjR&ht{LDJv?8} z=Mz@$YEQoBkeLR#KYi?78rpA#F37f`7}j#vCDPR|v^M{1##l;HFSIe=iPn|A_@HH+ zBu=OXA{I0>1AQu8ZJOXcv!9L)3QL!R5z^i8aM#+~N0*Kq>K&XM8CQO{pj^<0=ezpr6Je4JJXy;CkwI_3ucKB(4U;qBpp2qZSYh;y6 z6?W?ukqV@nc1)^r9)XWvOgui>;PC^%)A**A4{_LcX*$iK!|hue?qi)THFtryA;TW2 zcftzh)ZXx)Q4=pj2Df3yQ4v=PYpfcpHtr0q!8>M)06zOp5c@@n+O@QS*?TH+c3+{X z(D+}dK{OqCVQgS2jm%%P)b**te6`pano^%-fZZND8jRulOI=J^E%>04Ry118$&&X? zMiDQ?0Q1`=F$TL$iQ^({@>@7$Dc>p^nbX$OG<;gGOx64)1TfI0`snvHcg)c(@_$Pl z&0-?{6sp(q3pE&8{%hMW%#ZJ;4{JR>5HBiPqjobaU-iROi-E}=A)12{-8A`nys<8$ z)g8#Z`&K@uY_P;L7x1z$oxh%PK4*K9mo_%hji|H#5h!dw-0ci}|2o>gX1P|%D&`Vo zoCRq8QM~%qTwo)iUFh23w>=9&XhV{~do^D(2G3=+3+SVJ2r?l1Maqaxi#2g5&z(rV zeBO5WXR+7S-0!y4l!-W=cm7{HY1bJx1Cws}1iWl+ zh7vO9dVJGhoEhetO+R)p&)75GAx8Bcne3+mW6vb1RQ~)y$15u_&ZQPw$-8?c@F3B+ zccKynOz@FTVrg!1pb~lE=IQf8pTIK&LC}K?K0khLk<*mALL)p0Rv`CYf$*V81!ij7 z1BH+1j{MZRvFYWmp1;|(x3Kj$2R%jgD(->yVYPwo$WC&KLX0YSk3e9T^ph9=D^_HL zoYWzY?>?s>Z@C<=LoI#0Dvf6LpujN|8U?@{r9H1GN(nTUE4)W!J$($7U!M*Q#^+OC zNPnf$DgD68Ka|mjZT81wg$oxy`jfWHnzzm?a6C8Zw&a1l}t$c@(@uLRmR-_$y7LMP2ov9(m1<1(8g0AIKw!37^TTHuLMaHXWAE9Bi6iJ*Kay$F#U=$3a zTyfJ*_ffcw`yFtRl}ZkIIR6uLeejiQvteM?lG?$}8DZW{FO$&jCnS^UQo>zt|af6mSV8H5HrQV~oe_Ypc0QJ|+W z33J)2gm|rMVe;66n-b{Ee@My;lRO=ehml|~Qw`c51JEaLK7SX=i4E&l2d^z~wE;%{fo+1y zPug;h5#ZAV0BvjVn`Lw9(9G%YU0VYnbT_=iR3_=o*P(nrNDKNo;X88}0iTN^nSE9~ zf?u5^8~!xrLA&a5u&a%M3_6b|xK1CTk4yu1a`cIw%lVW2Dl+p<*df6d_xf5zFfrqX zqWC(!HY&2{`m2&)<#QYC-Hxo~dZ7jHsf|1Cttmc3TLXV_dy8RQ0q(VFW7*C#M zkKk_!V19|DI`mX-oml;3e{BD47;sq#0Aih^ zgo}dY`hKF^oC~gMvpYzRaCG>?YK+O}UEs?m72@I-+OY%``|%NU75N0g$a1$5L(Xr1 z5TO0JvM5Wb9$Tqfu_?5O_b}C-xE*-piE*M!*+MNH>qo}S2Iy)tH@GIf_B}E zD_7c&csjSo>PiF1fzlc?%Qcha+9%M;+hpDw4EWCS$~6Hqe9!=y=-5~p*8rRhcX+*NX$_t z)tdo>Z;pH2*PlrYmkUDc7(QiB9zEqHE-J$Qs zCGm=N!p|s~X(4znXaYC1)9uMkv*9sn^GdlR{zcb<15=Xc!QDU2w?}$8PH#^^X>9nB zGqvV5T88Sl!#|h{?p95yG2+9pney;WXyUqCsE@m4AfZN8x1ZMVDJ58l&U zga0>Tl$MHuP=r>f#!$7-GvQtq=D%Cdc0>&5a7J2`VFo)q(B4T=XRQre@c z$73duhK$NkTf}W11#DF+1u}N*SMByBp=ww1Yutpolr^3o%e!b#V2+qo0v?KL8G$2H z_4VgkVp(Jfe%;n}LD?o{8h3q!D>4+WDg%`*x4yq(&LzDhF_pYS4>imZlkqcG1Fa zi}(~@j+t;Xfdc1crO?o>0gG|7!J8dn~0yS~NtJ1-#n7jbE1Wvn9| z)#u)Qf;KiKiVGxbqB$=M$XJ+y@&1LthJsqu9@tTL^LKX82>K{sm@;2|%d@ZFVRmQ! zD5`Nz8*kx@jf9QuW*;hoNJ3pBgzwzDgk8adN~r3!C3R30;Ww03WFJ1JKgnRNaBOZe!D}qCr$4^1#e7fm zim^@Di?#~0X(2q5WQjf7FXk6IANw=-*^<9jda`13>HCP{K_H-e%@&|S!{j*!J#}B( zXvJ*Tb`rCcrXI(Opesc3R}s^Dqvp9t1k1Nu=q>o7CjpTO&epE4$U_blsMrSxr0q75 zME;G1edlDSBfHJT%C>k)l4Nh99^q4_pl(jko$`SWUGC%j$OlcNKAd4j9=E?;Y1$!da zCmrhHM*}FW8whqN*?^F%O+Peo(#^L&N+g|Kj!-vue`I+>4$0w@?ZizSBt~6O0$VB# zShiP!pVeV5=erg7KZu-;c5qQH*FC0tEYqf0-u}%9B-$3nr;p|bSQtjl?t!6kHw2#d z;nh_qZ@&{7k>w5ssK-LMxFP>Tup*yj`A@E8(3f|WjD<{wx9uyIF=J$;kX0tw*`liI zCHt#Vjs>SgKKZa*{_m;7I!3MEY&@co~+hW z)Vm8Ha&%-Sb9JpVuQSe1Ozo?$`#TrvVv45!c$R-`aVJ$wHPBJB=$7@DA_9LU5oUx*-A~h>)bN$sZX6~q89Qtj!cXMQ5q)Q0u?R5;(@p`Vg9sw3F+woa00X4 z13f!J313eHF@H_Tz-5LT)f?P1n3!sZ=?-^2+rac6?(AXYW}FDurd%?u6lnNEa#Hf} z%kn&EC1c&uF}&~#js5f2m*D#zcmHuo7oEfZ@oe$m_oMHj5P+tExMeQ*kLCKA%rt$x zcq#H<@a~@*a+O~`+GV%>hX$+J{r|Ox`+u7ZBo);D&p6UuLK0r|bjh9t_WD=9$+PaZbY%|aDCs~tHB5g(fugibfZCS8;4$4Guzt(M0v5k@e{~#ZSy6=BRTod-3JV6c zO}T26*}1ln9&9eS+HX?jn=n+nUKZi`VAj#|GmwU6viUm>;T~0;^XX{S{(N)AHK)nt zz5<*+W>gzy_nLAmPpin#ED2Z54+;=`x&Tx1-;&SF_E>4f=ARi-zzAH@;e>Kpju(>JFI-7YU}fGRNX47U#HlJ`ZJ^a1g|R7gihZoJ>MTD8XVWUh#5-2 z!aYvOeT8S|JIdZfY16O&ko}j1?zwwU$+Toth3v6+zI{&n^Wj-D0ztR2?JlSy{JX@8 z#i5@vI}{-7mFD0-{L?^H*6V%D>;^rB)YF3;N)HYE#1asV=9|hGAmcX+{ioC|r=K&< zoAiC#zfHx(u6mDw-OO0mU?Q!`$Lh2ys?|%VH-EVKj>c=H{#j?8nT*w)_KzHSK{#Vw z-(9W}WZUI7*B>Up{YJxS9KCH^VqeV(SMGkrIxDgbDy2cH+M8d(KPEM@lb^!hypeO9 z>lj3aXsJ^PO3a%cIqFNs>?6+)k>S->Ewt+?!g7yZg5as#?}9-@9#?UZ{RXKyyRW+s zP9`wIbo_eJ_Xgl!9(JDqVYX^Ylb4loU=9$o2|mQtM`!N7JVv+L`#drk*t^s4;bR(3 zIQ2Rf6VtdGXmtV7a27}^$1c~Fk0~9V$s!#L%DLWV*HOJc3Z705$4w^EF;r(-`F3>n zNcKy~FqYJr@448)v6;YF-BePLd?rr0|6P@bv00Kxg$BT`^L;=1eB)VrgHO0AXw*3f zLmpxWOQzMYavou|fbTP{$|Len#wwO&+r~06vRPDaMn}2U=<)x8JIL(E!V~Md5(Y|} zTNzY(qgl(KaUvJ2P}-BN8FkK;s1$_Jn1da#m$vjj{h*N?qrcy+hnc#`C10x8j`&^m zmHQIZF=je@lZvo_Wd}H%c7qv*O<oxYv(1Q=( zg4zA`ZqpDg1kP3rUQ&!9+hPG#OIZX#XLLo2ACYdS@gihpIy4L^q!g{6(wq4q7jHqC zHz{RMv>YZ-UNN8U&oTF+Wi62o;9IcFa95IT7yFt7hhc&uo;T(Bky)f&R-s=ZIJ3fC@AQp+>)Ac)={385?8e+YysR=k&8jf zgW~}5tM1`o>P!CdJ4sOlmL%7XOt4>di@q;5)P|)JD z=zuwW>{txZJa#Omy4?A0L}B5qKL(d|dv31s4|QXN4UON(J3`qr)nl=_3um4u!IqZt zE?tRa6rn#|=ci26WM?2hnLQrxU}m(-o-v$(iZ24)?^H9hCd${&u(nPrwft4$b|YZ2 z;wV0=YbLuIWk4q}{RI4~TQSJON#y(Yyl=rh2|s&yV1bU5Cj?Og#VKObd#4~pKP(ej z?G4I0m=%ThF5xOcnGk)Y?oP4t#ZZUDr{Q81U4GqgU=(Y%P52TigU+Idx>OTn(pg#D zDs{a^)9=+etYsZ}LY`I7SM+ioSI;#Yx^Z$Y?}DN^Ybog>Y1if-eApkXfEb>%!}etS ztkYj~uhQ*@ui|@v48gEI9H=j8Rmb_0ZD7uy!d&5NN$tX>EK#oJGar}JbOUs=gMcVqSzz}$%7aC{ zR1WnmV?s@;Ay7FMtbsESz>h6UJwP;%8 + +Select the new release date and click "Save". You should also enter a comment +in the "Reason For Putting On Hold" field. + +Take note of the following: +- All purchases that have been placed on hold will not included in a Payment Entry's references table +- The release date cannot be in the past. +- You can only block or unblock a purchase invoice if it is unpaid. +- You can only change the release date if the invoice is unpaid. + + {next} diff --git a/erpnext/docs/user/manual/en/buying/supplier.md b/erpnext/docs/user/manual/en/buying/supplier.md index baf0fed525..f116c889e0 100644 --- a/erpnext/docs/user/manual/en/buying/supplier.md +++ b/erpnext/docs/user/manual/en/buying/supplier.md @@ -38,7 +38,20 @@ If you don't want to customize payable account, and proceed with default payable You can add multiple companies in your ERPNext instance, and one Supplier can be used across multiple companies. In this case, you should define Companywise Payable Account for the Supplier in the "Default Payable Accounts" table. - (Check from 2:20) +### Place Supplier On Hold +In the Supplier form, check the "Block Supplier" checkbox. Next, choose the "Hold Type". + +The hold types are as follows: +- Invoices: ERPNext will not allow Purchase Invoices or Purchase Orders to be created for the supplier +- Payments: ERPNext will not allow Payment Entries to be created for the Supplier +- All: ERPNext will apply both hold types above + +After selecting the hold type, you can optionally set a release date in the "Release Date" field. + +Take note of the following: +- If you do not select a hold type, ERPNext will set it to "All" +- If you do not set a release date, ERPNext will hold the Supplier indefinitely + {next}