diff --git a/account_banking_pain_base/models/account_payment_line.py b/account_banking_pain_base/models/account_payment_line.py index 589c3617b..e90f40630 100644 --- a/account_banking_pain_base/models/account_payment_line.py +++ b/account_banking_pain_base/models/account_payment_line.py @@ -55,6 +55,111 @@ class AccountPaymentLine(models.Model): ], string="Category Purpose", help="If neither your bank nor your local regulations oblige you to " "set the category purpose, leave the field empty.") + purpose = fields.Selection( + # Full category purpose list found on: + # https://www.iso20022.org/external_code_list.page + # Document "External Code Sets spreadsheet" version 31 August, 2018 + selection=[ + ('ACCT', 'Account Management'), + ('CASH', 'Cash Management Transfer'), + ('COLL', 'Collection Payment'), + ('INTC', 'Intra Company Payment'), + ('LIMA', 'Liquidity Management'), + ('NETT', 'Netting'), + ('AGRT', 'Agricultural Transfer'), + ('BEXP', 'Business Expenses'), + ('COMC', 'Commercial Payment'), + ('CPYR', 'Copyright'), + ('GDDS', 'Purchase Sale Of Goods'), + ('LICF', 'License Fee'), + ('ROYA', 'Royalties'), + ('SCVE', 'Purchase Sale Of Services'), + ('SUBS', 'Subscription'), + ('SUPP', 'Supplier Payment'), + ('TRAD', 'Trade Services'), + ('CHAR', 'Charity Payment'), + ('COMT', 'Consumer Third Party Consolidated Payment'), + ('CLPR', 'Car Loan Principal Repayment'), + ('GOVI', 'Government Insurance'), + ('HLRP', 'Housing Loan Repayment'), + ('INSU', 'Insurance Premium'), + ('INTE', 'Interest'), + ('LBRI', 'Labor Insurance'), + ('LIFI', 'Life Insurance'), + ('LOAN', 'Loan'), + ('LOAR', 'Loan Repayment'), + ('PPTI', 'Property Insurance'), + ('RINP', 'Recurring Installment Payment'), + ('TRFD', 'Trust Fund'), + ('ADVA', 'Advance Payment'), + ('CCRD', 'Credit Card Payment '), + ('CFEE', 'Cancellation Fee'), + ('COST', 'Costs'), + ('DCRD', 'Debit Card Payment'), + ('GOVT', 'Government Payment'), + ('IHRP', 'Instalment Hire Purchase Agreement'), + ('INSM', 'Installment'), + ('MSVC', 'Multiple Service Types'), + ('NOWS', 'Not Otherwise Specified'), + ('OFEE', 'Opening Fee'), + ('OTHR', 'Other'), + ('PADD', 'Preauthorized debit'), + ('PTSP', 'Payment Terms'), + ('RCPT', 'Receipt Payment'), + ('RENT', 'Rent'), + ('STDY', 'Study'), + ('ANNI', 'Annuity'), + ('CMDT', 'Commodity Transfer'), + ('DERI', 'Derivatives'), + ('DIVD', 'Dividend'), + ('FREX', 'Foreign Exchange'), + ('HEDG', 'Hedging'), + ('PRME', 'Precious Metal'), + ('SAVG', 'Savings'), + ('SECU', 'Securities'), + ('TREA', 'Treasury Payment'), + ('ANTS', 'Anesthesia Services'), + ('CVCF', 'Convalescent Care Facility'), + ('DMEQ', 'Durable Medicale Equipment'), + ('DNTS', 'Dental Services'), + ('HLTC', 'Home Health Care'), + ('HLTI', 'Health Insurance'), + ('HSPC', 'Hospital Care'), + ('ICRF', 'Intermediate Care Facility'), + ('LTCF', 'Long Term Care Facility'), + ('MDCS', 'Medical Services'), + ('VIEW', 'Vision Care'), + ('ALMY', 'Alimony Payment'), + ('BECH', 'Child Benefit'), + ('BENE', 'Unemployment Disability Benefit'), + ('BONU', 'Bonus Payment.'), + ('COMM', 'Commission'), + ('PENS', 'Pension Payment'), + ('PRCP', 'Price Payment'), + ('SALA', 'Salary Payment'), + ('SSBE', 'Social Security Benefit'), + ('ESTX', 'Estate Tax'), + ('HSTX', 'Housing Tax'), + ('INTX', 'Income Tax'), + ('TAXS', 'Tax Payment'), + ('VATX', 'Value Added Tax Payment'), + ('AIRB', 'Air'), + ('BUSB', 'Bus'), + ('FERB', 'Ferry'), + ('RLWY', 'Railway'), + ('CBTV', 'Cable TV Bill'), + ('ELEC', 'Electricity Bill'), + ('ENRG', 'Energies'), + ('GASB', 'Gas Bill'), + ('NWCH', 'Network Charge'), + ('NWCM', 'Network Communication'), + ('OTLC', 'Other Telecom Related Bill'), + ('PHON', 'Telephone Bill'), + ('WTER', 'Water Bill'), + ], + help="If neither your bank nor your local regulations oblige you to " + "set the category purpose, leave the field empty.", + ) # PAIN allows 140 characters communication = fields.Char(size=140) # The field struct_communication_type has been dropped in v9 diff --git a/account_banking_pain_base/models/account_payment_mode.py b/account_banking_pain_base/models/account_payment_mode.py index 431ebc490..6797b4704 100644 --- a/account_banking_pain_base/models/account_payment_mode.py +++ b/account_banking_pain_base/models/account_payment_mode.py @@ -27,3 +27,9 @@ class AccountPaymentMode(models.Model): "- Country code (2, optional)\n" "- Company idenfier (N, VAT)\n" "- Service suffix (N, issued by bank)") + initiating_party_scheme = fields.Char( + string='Initiating Party Scheme', size=35, + help="This will be used as the 'Initiating Party Scheme Name' in " + "the PAIN files generated by Odoo. This value is determined by the " + "financial institution that will process the file. If not defined, " + "no scheme will be used.\n") diff --git a/account_banking_pain_base/models/account_payment_order.py b/account_banking_pain_base/models/account_payment_order.py index 21e6f81f4..3d563021e 100644 --- a/account_banking_pain_base/models/account_payment_order.py +++ b/account_banking_pain_base/models/account_payment_order.py @@ -284,6 +284,9 @@ class AccountPaymentOrder(models.Model): initiating_party_issuer = ( self.payment_mode_id.initiating_party_issuer or self.payment_mode_id.company_id.initiating_party_issuer) + initiating_party_scheme = ( + self.payment_mode_id.initiating_party_scheme or + self.payment_mode_id.company_id.initiating_party_scheme) # in pain.008.001.02.ch.01.xsd files they use # initiating_party_identifier but not initiating_party_issuer if initiating_party_identifier: @@ -292,6 +295,12 @@ class AccountPaymentOrder(models.Model): iniparty_org_other = etree.SubElement(iniparty_org_id, 'Othr') iniparty_org_other_id = etree.SubElement(iniparty_org_other, 'Id') iniparty_org_other_id.text = initiating_party_identifier + if initiating_party_scheme: + iniparty_org_other_scheme = etree.SubElement( + iniparty_org_other, 'SchmeNm') + iniparty_org_other_scheme_name = etree.SubElement( + iniparty_org_other_scheme, 'Prtry') + iniparty_org_other_scheme_name.text = initiating_party_scheme if initiating_party_issuer: iniparty_org_other_issuer = etree.SubElement( iniparty_org_other, 'Issr') @@ -341,6 +350,16 @@ class AccountPaymentOrder(models.Model): # as per the guidelines of the EPC return True + @api.model + def generate_party_id( + self, parent_node, party_type, partner): + """Generate an Id element for partner inside the parent node. + party_type can currently be Cdtr or Dbtr. Notably, the initiating + party orgid is generated with another mechanism and configured + at the company or payment mode level. + """ + return + @api.model def generate_party_acc_number( self, parent_node, party_type, order, partner_bank, gen_args, @@ -390,6 +409,19 @@ class AccountPaymentOrder(models.Model): partner = partner_bank.partner_id if partner.country_id: postal_address = etree.SubElement(party, 'PstlAdr') + if gen_args.get('pain_flavor').startswith( + 'pain.001.001.') or gen_args.get('pain_flavor').startswith( + 'pain.008.001.'): + if partner.zip: + pstcd = etree.SubElement(postal_address, 'PstCd') + pstcd.text = self._prepare_field( + 'Postal Code', 'partner.zip', + {'partner': partner}, 16, gen_args=gen_args) + if partner.city: + twnnm = etree.SubElement(postal_address, 'TwnNm') + twnnm.text = self._prepare_field( + 'Town Name', 'partner.city', + {'partner': partner}, 35, gen_args=gen_args) country = etree.SubElement(postal_address, 'Ctry') country.text = self._prepare_field( 'Country', 'partner.country_id.code', @@ -399,11 +431,8 @@ class AccountPaymentOrder(models.Model): adrline1.text = self._prepare_field( 'Adress Line1', 'partner.street', {'partner': partner}, 70, gen_args=gen_args) - if partner.city and partner.zip: - adrline2 = etree.SubElement(postal_address, 'AdrLine') - adrline2.text = self._prepare_field( - 'Address Line2', "partner.zip + ' ' + partner.city", - {'partner': partner}, 70, gen_args=gen_args) + + self.generate_party_id(party, party_type, partner) self.generate_party_acc_number( parent_node, party_type, order, partner_bank, gen_args, diff --git a/account_banking_pain_base/models/bank_payment_line.py b/account_banking_pain_base/models/bank_payment_line.py index 809f75dbb..f08ddcdd3 100644 --- a/account_banking_pain_base/models/bank_payment_line.py +++ b/account_banking_pain_base/models/bank_payment_line.py @@ -14,10 +14,12 @@ class BankPaymentLine(models.Model): string='Local Instrument') category_purpose = fields.Selection( related='payment_line_ids.category_purpose', string='Category Purpose') + purpose = fields.Selection( + related='payment_line_ids.purpose') @api.model def same_fields_payment_line_and_bank_payment_line(self): res = super(BankPaymentLine, self).\ same_fields_payment_line_and_bank_payment_line() - res += ['priority', 'local_instrument', 'category_purpose'] + res += ['priority', 'local_instrument', 'category_purpose', 'purpose'] return res diff --git a/account_banking_pain_base/models/res_company.py b/account_banking_pain_base/models/res_company.py index 127d8e179..c2711a0a3 100644 --- a/account_banking_pain_base/models/res_company.py +++ b/account_banking_pain_base/models/res_company.py @@ -20,6 +20,10 @@ class ResCompany(models.Model): string='Initiating Party Identifier', size=35, help="This will be used as the 'Initiating Party Identifier' in " "the PAIN files generated by Odoo.") + initiating_party_scheme = fields.Char( + string='Initiating Party Scheme', size=35, + help="This will be used as the 'Initiating Party Scheme Name' in " + "the PAIN files generated by Odoo.") def _default_initiating_party(self): '''This method is called from post_install.py''' diff --git a/account_banking_pain_base/models/res_config_settings.py b/account_banking_pain_base/models/res_config_settings.py index c59e4569d..60d804969 100644 --- a/account_banking_pain_base/models/res_config_settings.py +++ b/account_banking_pain_base/models/res_config_settings.py @@ -12,6 +12,8 @@ class ResConfigSettings(models.TransientModel): related='company_id.initiating_party_issuer') initiating_party_identifier = fields.Char( related='company_id.initiating_party_identifier') + initiating_party_scheme = fields.Char( + related='company_id.initiating_party_scheme') group_pain_multiple_identifier = fields.Boolean( string='Multiple identifiers', implied_group='account_banking_pain_base.' diff --git a/account_banking_pain_base/views/account_payment_line.xml b/account_banking_pain_base/views/account_payment_line.xml index 8f8b154df..88104f1a7 100644 --- a/account_banking_pain_base/views/account_payment_line.xml +++ b/account_banking_pain_base/views/account_payment_line.xml @@ -15,6 +15,7 @@ + diff --git a/account_banking_pain_base/views/account_payment_mode.xml b/account_banking_pain_base/views/account_payment_mode.xml index 5401bde00..2334125fb 100644 --- a/account_banking_pain_base/views/account_payment_mode.xml +++ b/account_banking_pain_base/views/account_payment_mode.xml @@ -15,6 +15,7 @@ + diff --git a/account_banking_pain_base/views/bank_payment_line_view.xml b/account_banking_pain_base/views/bank_payment_line_view.xml index 5ed993f0e..85e85e310 100644 --- a/account_banking_pain_base/views/bank_payment_line_view.xml +++ b/account_banking_pain_base/views/bank_payment_line_view.xml @@ -15,6 +15,7 @@ + diff --git a/account_banking_sepa_credit_transfer/models/account_payment_order.py b/account_banking_sepa_credit_transfer/models/account_payment_order.py index f39f046e3..df6ccba36 100644 --- a/account_banking_sepa_credit_transfer/models/account_payment_order.py +++ b/account_banking_sepa_credit_transfer/models/account_payment_order.py @@ -130,6 +130,11 @@ class AccountPaymentOrder(models.Model): payment_info, 'CdtTrfTxInf') payment_identification = etree.SubElement( credit_transfer_transaction_info, 'PmtId') + instruction_identification = etree.SubElement( + payment_identification, 'InstrId') + instruction_identification.text = self._prepare_field( + 'Instruction Identification', 'line.name', + {'line': line}, 35, gen_args=gen_args) end2end_identification = etree.SubElement( payment_identification, 'EndToEndId') end2end_identification.text = self._prepare_field( @@ -153,6 +158,10 @@ class AccountPaymentOrder(models.Model): self.generate_party_block( credit_transfer_transaction_info, 'Cdtr', 'C', line.partner_bank_id, gen_args, line) + if line.purpose: + purpose = etree.SubElement( + credit_transfer_transaction_info, 'Purp') + etree.SubElement(purpose, 'Cd').text = line.purpose self.generate_remittance_info_block( credit_transfer_transaction_info, line, gen_args) if not pain_flavor.startswith('pain.001.001.02'): diff --git a/account_banking_sepa_direct_debit/models/account_payment_order.py b/account_banking_sepa_direct_debit/models/account_payment_order.py index e8d3b97ad..58b142302 100644 --- a/account_banking_sepa_direct_debit/models/account_payment_order.py +++ b/account_banking_sepa_direct_debit/models/account_payment_order.py @@ -156,12 +156,11 @@ class AccountPaymentOrder(models.Model): payment_info, 'DrctDbtTxInf') payment_identification = etree.SubElement( dd_transaction_info, 'PmtId') - if pain_flavor == 'pain.008.001.02.ch.01': - instruction_identification = etree.SubElement( - payment_identification, 'InstrId') - instruction_identification.text = self._prepare_field( - 'Intruction Identification', 'line.name', - {'line': line}, 35, gen_args=gen_args) + instruction_identification = etree.SubElement( + payment_identification, 'InstrId') + instruction_identification.text = self._prepare_field( + 'Instruction Identification', 'line.name', + {'line': line}, 35, gen_args=gen_args) end2end_identification = etree.SubElement( payment_identification, 'EndToEndId') end2end_identification.text = self._prepare_field( @@ -215,6 +214,11 @@ class AccountPaymentOrder(models.Model): dd_transaction_info, 'Dbtr', 'C', line.partner_bank_id, gen_args, line) + if line.purpose: + purpose = etree.SubElement( + dd_transaction_info, 'Purp') + etree.SubElement(purpose, 'Cd').text = line.purpose + self.generate_remittance_info_block( dd_transaction_info, line, gen_args)