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)