[IMP] account_banking_pain_base: black, isort, prettier

This commit is contained in:
Valentin Vinagre Urteaga
2020-04-21 18:36:00 +02:00
committed by Pedro M. Baeza
parent d132f7365f
commit 530b6b6cf0
17 changed files with 726 additions and 633 deletions

View File

@@ -4,29 +4,24 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
'name': 'Account Banking PAIN Base Module',
'summary': 'Base module for PAIN file generation',
'version': '12.0.1.0.2',
'license': 'AGPL-3',
'author': "Akretion, "
"Noviat, "
"Tecnativa, "
"Odoo Community Association (OCA)",
'website': 'https://github.com/OCA/bank-payment',
'category': 'Hidden',
'depends': ['account_payment_order'],
'external_dependencies': {
'python': ['unidecode', 'lxml'],
},
'data': [
'security/security.xml',
'views/account_payment_line.xml',
'views/account_payment_order.xml',
'views/bank_payment_line_view.xml',
'views/account_payment_mode.xml',
'views/res_config_settings.xml',
'views/account_payment_method.xml',
"name": "Account Banking PAIN Base Module",
"summary": "Base module for PAIN file generation",
"version": "12.0.1.0.2",
"license": "AGPL-3",
"author": "Akretion, " "Noviat, " "Tecnativa, " "Odoo Community Association (OCA)",
"website": "https://github.com/OCA/bank-payment",
"category": "Hidden",
"depends": ["account_payment_order"],
"external_dependencies": {"python": ["unidecode", "lxml"],},
"data": [
"security/security.xml",
"views/account_payment_line.xml",
"views/account_payment_order.xml",
"views/bank_payment_line_view.xml",
"views/account_payment_mode.xml",
"views/res_config_settings.xml",
"views/account_payment_method.xml",
],
'post_init_hook': 'set_default_initiating_party',
'installable': True,
"post_init_hook": "set_default_initiating_party",
"installable": True,
}

View File

@@ -2,167 +2,171 @@
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
from odoo import fields, models
class AccountPaymentLine(models.Model):
_inherit = 'account.payment.line'
_inherit = "account.payment.line"
priority = fields.Selection([
('NORM', 'Normal'),
('HIGH', 'High')],
string='Priority', default='NORM',
priority = fields.Selection(
[("NORM", "Normal"), ("HIGH", "High")],
string="Priority",
default="NORM",
help="This field will be used as 'Instruction Priority' in "
"the generated PAIN file.")
"the generated PAIN file.",
)
# local_instrument is used for instant credit transfers which
# will begin on November 2017, cf account_banking_sepa_credit_transfer
# It is also used in some countries such as switzerland,
# cf l10n_ch_pain_base that adds some entries in the selection field
local_instrument = fields.Selection([], string='Local Instrument')
category_purpose = fields.Selection([
# Full category purpose list found on:
# https://www.iso20022.org/external_code_list.page
# Document "External Code Sets spreadsheet" version Feb 8th 2017
('BONU', 'Bonus Payment'),
('CASH', 'Cash Management Transfer'),
('CBLK', 'Card Bulk Clearing'),
('CCRD', 'Credit Card Payment'),
('CORT', 'Trade Settlement Payment'),
('DCRD', 'Debit Card Payment'),
('DIVI', 'Dividend'),
('DVPM', 'Deliver Against Payment'),
('EPAY', 'ePayment'),
('FCOL', 'Fee Collection'),
('GOVT', 'Government Payment'),
('HEDG', 'Hedging'),
('ICCP', 'Irrevocable Credit Card Payment'),
('IDCP', 'Irrevocable Debit Card Payment'),
('INTC', 'Intra-Company Payment'),
('INTE', 'Interest'),
('LOAN', 'Loan'),
('OTHR', 'Other Payment'),
('PENS', 'Pension Payment'),
('RVPM', 'Receive Against Payment'),
('SALA', 'Salary Payment'),
('SECU', 'Securities'),
('SSBE', 'Social Security Benefit'),
('SUPP', 'Supplier Payment'),
('TAXS', 'Tax Payment'),
('TRAD', 'Trade'),
('TREA', 'Treasury Payment'),
('VATX', 'VAT Payment'),
('WHLD', 'WithHolding'),
], string="Category Purpose",
local_instrument = fields.Selection([], string="Local Instrument")
category_purpose = fields.Selection(
[
# Full category purpose list found on:
# https://www.iso20022.org/external_code_list.page
# Document "External Code Sets spreadsheet" version Feb 8th 2017
("BONU", "Bonus Payment"),
("CASH", "Cash Management Transfer"),
("CBLK", "Card Bulk Clearing"),
("CCRD", "Credit Card Payment"),
("CORT", "Trade Settlement Payment"),
("DCRD", "Debit Card Payment"),
("DIVI", "Dividend"),
("DVPM", "Deliver Against Payment"),
("EPAY", "ePayment"),
("FCOL", "Fee Collection"),
("GOVT", "Government Payment"),
("HEDG", "Hedging"),
("ICCP", "Irrevocable Credit Card Payment"),
("IDCP", "Irrevocable Debit Card Payment"),
("INTC", "Intra-Company Payment"),
("INTE", "Interest"),
("LOAN", "Loan"),
("OTHR", "Other Payment"),
("PENS", "Pension Payment"),
("RVPM", "Receive Against Payment"),
("SALA", "Salary Payment"),
("SECU", "Securities"),
("SSBE", "Social Security Benefit"),
("SUPP", "Supplier Payment"),
("TAXS", "Tax Payment"),
("TRAD", "Trade"),
("TREA", "Treasury Payment"),
("VATX", "VAT Payment"),
("WHLD", "WithHolding"),
],
string="Category Purpose",
help="If neither your bank nor your local regulations oblige you to "
"set the category purpose, leave the field empty.")
"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'),
("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.",
"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
# We now use communication_type ; you should add an option
# in communication_type with selection_add=[]
communication_type = fields.Selection(selection_add=[('ISO', 'ISO')])
communication_type = fields.Selection(selection_add=[("ISO", "ISO")])

View File

@@ -1,31 +1,34 @@
# © 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountPaymentMethod(models.Model):
_inherit = 'account.payment.method'
_inherit = "account.payment.method"
pain_version = fields.Selection([], string='PAIN Version')
pain_version = fields.Selection([], string="PAIN Version")
convert_to_ascii = fields.Boolean(
string='Convert to ASCII', default=True,
string="Convert to ASCII",
default=True,
help="If active, Odoo will convert each accented character to "
"the corresponding unaccented character, so that only ASCII "
"characters are used in the generated PAIN file.")
"characters are used in the generated PAIN file.",
)
@api.multi
def get_xsd_file_path(self):
"""This method is designed to be inherited in the SEPA modules"""
self.ensure_one()
raise UserError(_(
"No XSD file path found for payment method '%s'") % self.name)
raise UserError(_("No XSD file path found for payment method '%s'") % self.name)
_sql_constraints = [(
# Extending this constraint from account_payment_mode
'code_payment_type_unique',
'unique(code, payment_type, pain_version)',
'A payment method of the same type already exists with this code'
' and PAIN version'
)]
_sql_constraints = [
(
# Extending this constraint from account_payment_mode
"code_payment_type_unique",
"unique(code, payment_type, pain_version)",
"A payment method of the same type already exists with this code"
" and PAIN version",
)
]

View File

@@ -3,33 +3,39 @@
# © 2016 Antiun Ingenieria S.L. - Antonio Espinosa
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
from odoo import fields, models
class AccountPaymentMode(models.Model):
_inherit = 'account.payment.mode'
_inherit = "account.payment.mode"
initiating_party_issuer = fields.Char(
string='Initiating Party Issuer', size=35,
string="Initiating Party Issuer",
size=35,
help="This will be used as the 'Initiating Party Issuer' in the "
"PAIN files generated by Odoo. If not defined, Initiating Party "
"Issuer from company will be used.\n"
"Common format (13): \n"
"- Country code (2, optional)\n"
"- Company idenfier (N, VAT)\n"
"- Service suffix (N, issued by bank)")
"- Service suffix (N, issued by bank)",
)
initiating_party_identifier = fields.Char(
string='Initiating Party Identifier', size=35,
string="Initiating Party Identifier",
size=35,
help="This will be used as the 'Initiating Party Identifier' in "
"the PAIN files generated by Odoo. If not defined, Initiating Party "
"Identifier from company will be used.\n"
"Common format (13): \n"
"- Country code (2, optional)\n"
"- Company idenfier (N, VAT)\n"
"- Service suffix (N, issued by bank)")
"- Service suffix (N, issued by bank)",
)
initiating_party_scheme = fields.Char(
string='Initiating Party Scheme', size=35,
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")
"no scheme will be used.\n",
)

View File

@@ -3,13 +3,14 @@
# © 2016 Antiun Ingenieria S.L. - Antonio Espinosa
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api, _, tools
import logging
from datetime import datetime
from lxml import etree
from odoo import _, api, fields, models, tools
from odoo.exceptions import UserError
from odoo.tools.safe_eval import safe_eval
from datetime import datetime
from lxml import etree
import logging
try:
from unidecode import unidecode
@@ -20,18 +21,21 @@ logger = logging.getLogger(__name__)
class AccountPaymentOrder(models.Model):
_inherit = 'account.payment.order'
_inherit = "account.payment.order"
sepa = fields.Boolean(
compute='compute_sepa', readonly=True, string="SEPA Payment")
charge_bearer = fields.Selection([
('SLEV', 'Following Service Level'),
('SHAR', 'Shared'),
('CRED', 'Borne by Creditor'),
('DEBT', 'Borne by Debtor')], string='Charge Bearer',
default='SLEV', readonly=True,
states={'draft': [('readonly', False)], 'open': [('readonly', False)]},
track_visibility='onchange',
sepa = fields.Boolean(compute="compute_sepa", readonly=True, string="SEPA Payment")
charge_bearer = fields.Selection(
[
("SLEV", "Following Service Level"),
("SHAR", "Shared"),
("CRED", "Borne by Creditor"),
("DEBT", "Borne by Debtor"),
],
string="Charge Bearer",
default="SLEV",
readonly=True,
states={"draft": [("readonly", False)], "open": [("readonly", False)]},
track_visibility="onchange",
help="Following service level : transaction charges are to be "
"applied following the rules agreed in the service level "
"and/or scheme (SEPA Core messages must use this). Shared : "
@@ -40,32 +44,36 @@ class AccountPaymentOrder(models.Model):
"be borne by the creditor. Borne by creditor : all "
"transaction charges are to be borne by the creditor. Borne "
"by debtor : all transaction charges are to be borne by the "
"debtor.")
"debtor.",
)
batch_booking = fields.Boolean(
string='Batch Booking', readonly=True,
states={'draft': [('readonly', False)], 'open': [('readonly', False)]},
track_visibility='onchange',
string="Batch Booking",
readonly=True,
states={"draft": [("readonly", False)], "open": [("readonly", False)]},
track_visibility="onchange",
help="If true, the bank statement will display only one debit "
"line for all the wire transfers of the SEPA XML file ; if "
"false, the bank statement will display one debit line per wire "
"transfer of the SEPA XML file.")
"transfer of the SEPA XML file.",
)
@api.multi
@api.depends(
'company_partner_bank_id.acc_type',
'payment_line_ids.currency_id',
'payment_line_ids.partner_bank_id.acc_type')
"company_partner_bank_id.acc_type",
"payment_line_ids.currency_id",
"payment_line_ids.partner_bank_id.acc_type",
)
def compute_sepa(self):
eur = self.env.ref('base.EUR')
eur = self.env.ref("base.EUR")
for order in self:
sepa = True
if order.company_partner_bank_id.acc_type != 'iban':
if order.company_partner_bank_id.acc_type != "iban":
sepa = False
for pline in order.payment_line_ids:
if pline.currency_id != eur:
sepa = False
break
if pline.partner_bank_id.acc_type != 'iban':
if pline.partner_bank_id.acc_type != "iban":
sepa = False
break
sepa = order.compute_sepa_final_hook(sepa)
@@ -77,12 +85,13 @@ class AccountPaymentOrder(models.Model):
return sepa
@api.model
def _prepare_field(self, field_name, field_value, eval_ctx,
max_size=0, gen_args=None):
def _prepare_field(
self, field_name, field_value, eval_ctx, max_size=0, gen_args=None
):
"""This function is designed to be inherited !"""
if gen_args is None:
gen_args = {}
assert isinstance(eval_ctx, dict), 'eval_ctx must contain a dict'
assert isinstance(eval_ctx, dict), "eval_ctx must contain a dict"
try:
value = safe_eval(field_value, eval_ctx)
# SEPA uses XML ; XML = UTF-8 ; UTF-8 = support for all characters
@@ -90,40 +99,66 @@ class AccountPaymentOrder(models.Model):
# and many banks don't want non-ASCCI characters !
# cf section 1.4 "Character set" of the SEPA Credit Transfer
# Scheme Customer-to-bank guidelines
if gen_args.get('convert_to_ascii'):
if gen_args.get("convert_to_ascii"):
value = unidecode(value)
unallowed_ascii_chars = [
'"', '#', '$', '%', '&', '*', ';', '<', '>', '=', '@',
'[', ']', '^', '_', '`', '{', '}', '|', '~', '\\', '!']
'"',
"#",
"$",
"%",
"&",
"*",
";",
"<",
">",
"=",
"@",
"[",
"]",
"^",
"_",
"`",
"{",
"}",
"|",
"~",
"\\",
"!",
]
for unallowed_ascii_char in unallowed_ascii_chars:
value = value.replace(unallowed_ascii_char, '-')
value = value.replace(unallowed_ascii_char, "-")
except Exception:
error_msg_prefix = _("Cannot compute the field '{field_name}'.") \
.format(
field_name=field_name)
error_msg_prefix = _("Cannot compute the field '{field_name}'.").format(
field_name=field_name
)
error_msg_details_list = self.except_messages_prepare_field(
eval_ctx, field_name)
error_msg_data = _("Data for evaluation:\n"
"\tcontext: {eval_ctx}\n"
"\tfield path: {field_value}") \
.format(
eval_ctx=eval_ctx,
field_value=field_value)
raise UserError('\n'.join(
[error_msg_prefix]
+ error_msg_details_list
+ [error_msg_data]))
eval_ctx, field_name
)
error_msg_data = _(
"Data for evaluation:\n"
"\tcontext: {eval_ctx}\n"
"\tfield path: {field_value}"
).format(eval_ctx=eval_ctx, field_value=field_value)
raise UserError(
"\n".join(
[error_msg_prefix] + error_msg_details_list + [error_msg_data]
)
)
if not isinstance(value, str):
raise UserError(
_("The type of the field '%s' is %s. It should be a string "
"or unicode.")
% (field_name, type(value)))
_(
"The type of the field '%s' is %s. It should be a string "
"or unicode."
)
% (field_name, type(value))
)
if not value:
raise UserError(
_("The '%s' is empty or 0. It should have a non-null value.")
% field_name)
% field_name
)
if max_size and len(value) > max_size:
value = value[0:max_size]
return value
@@ -136,52 +171,52 @@ class AccountPaymentOrder(models.Model):
:return: List containing the error messages.
"""
error_messages = list()
line = eval_ctx.get('line')
line = eval_ctx.get("line")
if line:
error_messages.append(
_("Payment Line has reference '%s'.") % line.name)
partner_bank = eval_ctx.get('partner_bank')
error_messages.append(_("Payment Line has reference '%s'.") % line.name)
partner_bank = eval_ctx.get("partner_bank")
if partner_bank:
error_messages.append(
_("Partner's bank account is '%s'.")
% partner_bank.display_name)
_("Partner's bank account is '%s'.") % partner_bank.display_name
)
return error_messages
@api.model
def _validate_xml(self, xml_string, gen_args):
xsd_etree_obj = etree.parse(
tools.file_open(gen_args['pain_xsd_file']))
xsd_etree_obj = etree.parse(tools.file_open(gen_args["pain_xsd_file"]))
official_pain_schema = etree.XMLSchema(xsd_etree_obj)
try:
root_to_validate = etree.fromstring(xml_string)
official_pain_schema.assertValid(root_to_validate)
except Exception as e:
logger.warning(
"The XML file is invalid against the XML Schema Definition")
logger.warning("The XML file is invalid against the XML Schema Definition")
logger.warning(xml_string)
logger.warning(e)
raise UserError(
_("The generated XML file is not valid against the official "
_(
"The generated XML file is not valid against the official "
"XML Schema Definition. The generated XML file and the "
"full error have been written in the server logs. Here "
"is the error, which may give you an idea on the cause "
"of the problem : %s")
% str(e))
"of the problem : %s"
)
% str(e)
)
return True
@api.multi
def finalize_sepa_file_creation(self, xml_root, gen_args):
xml_string = etree.tostring(
xml_root, pretty_print=True, encoding='UTF-8',
xml_declaration=True)
xml_root, pretty_print=True, encoding="UTF-8", xml_declaration=True
)
logger.debug(
"Generated SEPA XML file in format %s below"
% gen_args['pain_flavor'])
"Generated SEPA XML file in format %s below" % gen_args["pain_flavor"]
)
logger.debug(xml_string)
self._validate_xml(xml_string, gen_args)
filename = '%s%s.xml' % (gen_args['file_prefix'], self.name)
filename = "{}{}.xml".format(gen_args["file_prefix"], self.name)
return (xml_string, filename)
@api.multi
@@ -189,8 +224,8 @@ class AccountPaymentOrder(models.Model):
self.ensure_one()
pain_flavor = self.payment_mode_id.payment_method_id.pain_version
nsmap = {
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
None: 'urn:iso:std:iso:20022:tech:xsd:%s' % pain_flavor,
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
None: "urn:iso:std:iso:20022:tech:xsd:%s" % pain_flavor,
}
return nsmap
@@ -201,149 +236,158 @@ class AccountPaymentOrder(models.Model):
@api.model
def generate_group_header_block(self, parent_node, gen_args):
group_header = etree.SubElement(parent_node, 'GrpHdr')
message_identification = etree.SubElement(
group_header, 'MsgId')
group_header = etree.SubElement(parent_node, "GrpHdr")
message_identification = etree.SubElement(group_header, "MsgId")
message_identification.text = self._prepare_field(
'Message Identification',
'self.name',
{'self': self}, 35, gen_args=gen_args)
creation_date_time = etree.SubElement(group_header, 'CreDtTm')
"Message Identification", "self.name", {"self": self}, 35, gen_args=gen_args
)
creation_date_time = etree.SubElement(group_header, "CreDtTm")
creation_date_time.text = datetime.strftime(
datetime.today(), '%Y-%m-%dT%H:%M:%S')
if gen_args.get('pain_flavor') == 'pain.001.001.02':
datetime.today(), "%Y-%m-%dT%H:%M:%S"
)
if gen_args.get("pain_flavor") == "pain.001.001.02":
# batch_booking is in "Group header" with pain.001.001.02
# and in "Payment info" in pain.001.001.03/04
batch_booking = etree.SubElement(group_header, 'BtchBookg')
batch_booking = etree.SubElement(group_header, "BtchBookg")
batch_booking.text = str(self.batch_booking).lower()
nb_of_transactions = etree.SubElement(
group_header, 'NbOfTxs')
control_sum = etree.SubElement(group_header, 'CtrlSum')
nb_of_transactions = etree.SubElement(group_header, "NbOfTxs")
control_sum = etree.SubElement(group_header, "CtrlSum")
# Grpg removed in pain.001.001.03
if gen_args.get('pain_flavor') == 'pain.001.001.02':
grouping = etree.SubElement(group_header, 'Grpg')
grouping.text = 'GRPD'
if gen_args.get("pain_flavor") == "pain.001.001.02":
grouping = etree.SubElement(group_header, "Grpg")
grouping.text = "GRPD"
self.generate_initiating_party_block(group_header, gen_args)
return group_header, nb_of_transactions, control_sum
@api.model
def generate_start_payment_info_block(
self, parent_node, payment_info_ident,
priority, local_instrument, category_purpose, sequence_type,
requested_date, eval_ctx, gen_args):
payment_info = etree.SubElement(parent_node, 'PmtInf')
payment_info_identification = etree.SubElement(
payment_info, 'PmtInfId')
self,
parent_node,
payment_info_ident,
priority,
local_instrument,
category_purpose,
sequence_type,
requested_date,
eval_ctx,
gen_args,
):
payment_info = etree.SubElement(parent_node, "PmtInf")
payment_info_identification = etree.SubElement(payment_info, "PmtInfId")
payment_info_identification.text = self._prepare_field(
'Payment Information Identification',
payment_info_ident, eval_ctx, 35, gen_args=gen_args)
payment_method = etree.SubElement(payment_info, 'PmtMtd')
payment_method.text = gen_args['payment_method']
"Payment Information Identification",
payment_info_ident,
eval_ctx,
35,
gen_args=gen_args,
)
payment_method = etree.SubElement(payment_info, "PmtMtd")
payment_method.text = gen_args["payment_method"]
nb_of_transactions = False
control_sum = False
if gen_args.get('pain_flavor') != 'pain.001.001.02':
batch_booking = etree.SubElement(payment_info, 'BtchBookg')
if gen_args.get("pain_flavor") != "pain.001.001.02":
batch_booking = etree.SubElement(payment_info, "BtchBookg")
batch_booking.text = str(self.batch_booking).lower()
# The "SEPA Customer-to-bank
# Implementation guidelines" for SCT and SDD says that control sum
# and nb_of_transactions should be present
# at both "group header" level and "payment info" level
nb_of_transactions = etree.SubElement(
payment_info, 'NbOfTxs')
control_sum = etree.SubElement(payment_info, 'CtrlSum')
payment_type_info = etree.SubElement(
payment_info, 'PmtTpInf')
if priority and gen_args['payment_method'] != 'DD':
instruction_priority = etree.SubElement(
payment_type_info, 'InstrPrty')
# The "SEPA Customer-to-bank
# Implementation guidelines" for SCT and SDD says that control sum
# and nb_of_transactions should be present
# at both "group header" level and "payment info" level
nb_of_transactions = etree.SubElement(payment_info, "NbOfTxs")
control_sum = etree.SubElement(payment_info, "CtrlSum")
payment_type_info = etree.SubElement(payment_info, "PmtTpInf")
if priority and gen_args["payment_method"] != "DD":
instruction_priority = etree.SubElement(payment_type_info, "InstrPrty")
instruction_priority.text = priority
if self.sepa:
service_level = etree.SubElement(payment_type_info, 'SvcLvl')
service_level_code = etree.SubElement(service_level, 'Cd')
service_level_code.text = 'SEPA'
service_level = etree.SubElement(payment_type_info, "SvcLvl")
service_level_code = etree.SubElement(service_level, "Cd")
service_level_code.text = "SEPA"
if local_instrument:
local_instrument_root = etree.SubElement(
payment_type_info, 'LclInstrm')
if gen_args.get('local_instrument_type') == 'proprietary':
local_instr_value = etree.SubElement(
local_instrument_root, 'Prtry')
local_instrument_root = etree.SubElement(payment_type_info, "LclInstrm")
if gen_args.get("local_instrument_type") == "proprietary":
local_instr_value = etree.SubElement(local_instrument_root, "Prtry")
else:
local_instr_value = etree.SubElement(
local_instrument_root, 'Cd')
local_instr_value = etree.SubElement(local_instrument_root, "Cd")
local_instr_value.text = local_instrument
if sequence_type:
sequence_type_node = etree.SubElement(
payment_type_info, 'SeqTp')
sequence_type_node = etree.SubElement(payment_type_info, "SeqTp")
sequence_type_node.text = sequence_type
if category_purpose:
category_purpose_node = etree.SubElement(
payment_type_info, 'CtgyPurp')
category_purpose_code = etree.SubElement(
category_purpose_node, 'Cd')
category_purpose_node = etree.SubElement(payment_type_info, "CtgyPurp")
category_purpose_code = etree.SubElement(category_purpose_node, "Cd")
category_purpose_code.text = category_purpose
if gen_args['payment_method'] == 'DD':
request_date_tag = 'ReqdColltnDt'
if gen_args["payment_method"] == "DD":
request_date_tag = "ReqdColltnDt"
else:
request_date_tag = 'ReqdExctnDt'
requested_date_node = etree.SubElement(
payment_info, request_date_tag)
request_date_tag = "ReqdExctnDt"
requested_date_node = etree.SubElement(payment_info, request_date_tag)
requested_date_node.text = requested_date
return payment_info, nb_of_transactions, control_sum
@api.model
def _must_have_initiating_party(self, gen_args):
'''This method is designed to be inherited in localization modules for
countries in which the initiating party is required'''
"""This method is designed to be inherited in localization modules for
countries in which the initiating party is required"""
return False
@api.model
def generate_initiating_party_block(self, parent_node, gen_args):
my_company_name = self._prepare_field(
'Company Name',
'self.company_partner_bank_id.partner_id.name',
{'self': self}, gen_args.get('name_maxsize'), gen_args=gen_args)
initiating_party = etree.SubElement(parent_node, 'InitgPty')
initiating_party_name = etree.SubElement(initiating_party, 'Nm')
"Company Name",
"self.company_partner_bank_id.partner_id.name",
{"self": self},
gen_args.get("name_maxsize"),
gen_args=gen_args,
)
initiating_party = etree.SubElement(parent_node, "InitgPty")
initiating_party_name = etree.SubElement(initiating_party, "Nm")
initiating_party_name.text = my_company_name
initiating_party_identifier = (
self.payment_mode_id.initiating_party_identifier or
self.payment_mode_id.company_id.initiating_party_identifier)
self.payment_mode_id.initiating_party_identifier
or self.payment_mode_id.company_id.initiating_party_identifier
)
initiating_party_issuer = (
self.payment_mode_id.initiating_party_issuer or
self.payment_mode_id.company_id.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)
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:
iniparty_id = etree.SubElement(initiating_party, 'Id')
iniparty_org_id = etree.SubElement(iniparty_id, 'OrgId')
iniparty_org_other = etree.SubElement(iniparty_org_id, 'Othr')
iniparty_org_other_id = etree.SubElement(iniparty_org_other, 'Id')
iniparty_id = etree.SubElement(initiating_party, "Id")
iniparty_org_id = etree.SubElement(iniparty_id, "OrgId")
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, "SchmeNm"
)
iniparty_org_other_scheme_name = etree.SubElement(
iniparty_org_other_scheme, 'Prtry')
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')
iniparty_org_other_issuer = etree.SubElement(iniparty_org_other, "Issr")
iniparty_org_other_issuer.text = initiating_party_issuer
elif self._must_have_initiating_party(gen_args):
raise UserError(
_("Missing 'Initiating Party Issuer' and/or "
_(
"Missing 'Initiating Party Issuer' and/or "
"'Initiating Party Identifier' for the company '%s'. "
"Both fields must have a value.")
% self.company_id.name)
"Both fields must have a value."
)
% self.company_id.name
)
return True
@api.model
def generate_party_agent(
self, parent_node, party_type, order, partner_bank, gen_args,
bank_line=None):
self, parent_node, party_type, order, partner_bank, gen_args, bank_line=None
):
"""Generate the piece of the XML file corresponding to BIC
This code is mutualized between TRF and DD
Starting from Feb 1st 2016, we should be able to do
@@ -352,34 +396,30 @@ class AccountPaymentOrder(models.Model):
sepa-credit-transfer/iban-and-bic/
In some localization (l10n_ch_sepa for example), they need the
bank_line argument"""
assert order in ('B', 'C'), "Order can be 'B' or 'C'"
assert order in ("B", "C"), "Order can be 'B' or 'C'"
if partner_bank.bank_bic:
party_agent = etree.SubElement(parent_node, '%sAgt' % party_type)
party_agent_institution = etree.SubElement(
party_agent, 'FinInstnId')
party_agent = etree.SubElement(parent_node, "%sAgt" % party_type)
party_agent_institution = etree.SubElement(party_agent, "FinInstnId")
party_agent_bic = etree.SubElement(
party_agent_institution, gen_args.get('bic_xml_tag'))
party_agent_institution, gen_args.get("bic_xml_tag")
)
party_agent_bic.text = partner_bank.bank_bic
else:
if order == 'B' or (
order == 'C' and gen_args['payment_method'] == 'DD'):
party_agent = etree.SubElement(
parent_node, '%sAgt' % party_type)
party_agent_institution = etree.SubElement(
party_agent, 'FinInstnId')
party_agent_other = etree.SubElement(
party_agent_institution, 'Othr')
if order == "B" or (order == "C" and gen_args["payment_method"] == "DD"):
party_agent = etree.SubElement(parent_node, "%sAgt" % party_type)
party_agent_institution = etree.SubElement(party_agent, "FinInstnId")
party_agent_other = etree.SubElement(party_agent_institution, "Othr")
party_agent_other_identification = etree.SubElement(
party_agent_other, 'Id')
party_agent_other_identification.text = 'NOTPROVIDED'
party_agent_other, "Id"
)
party_agent_other_identification.text = "NOTPROVIDED"
# for Credit Transfers, in the 'C' block, if BIC is not provided,
# we should not put the 'Creditor Agent' block at all,
# as per the guidelines of the EPC
return True
@api.model
def generate_party_id(
self, parent_node, party_type, partner):
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
@@ -389,81 +429,101 @@ class AccountPaymentOrder(models.Model):
@api.model
def generate_party_acc_number(
self, parent_node, party_type, order, partner_bank, gen_args,
bank_line=None):
party_account = etree.SubElement(
parent_node, '%sAcct' % party_type)
party_account_id = etree.SubElement(party_account, 'Id')
if partner_bank.acc_type == 'iban':
party_account_iban = etree.SubElement(
party_account_id, 'IBAN')
self, parent_node, party_type, order, partner_bank, gen_args, bank_line=None
):
party_account = etree.SubElement(parent_node, "%sAcct" % party_type)
party_account_id = etree.SubElement(party_account, "Id")
if partner_bank.acc_type == "iban":
party_account_iban = etree.SubElement(party_account_id, "IBAN")
party_account_iban.text = partner_bank.sanitized_acc_number
else:
party_account_other = etree.SubElement(
party_account_id, 'Othr')
party_account_other_id = etree.SubElement(
party_account_other, 'Id')
party_account_other = etree.SubElement(party_account_id, "Othr")
party_account_other_id = etree.SubElement(party_account_other, "Id")
party_account_other_id.text = partner_bank.sanitized_acc_number
return True
@api.model
def generate_address_block(
self, parent_node, partner, gen_args):
def generate_address_block(self, parent_node, partner, gen_args):
"""Generate the piece of the XML corresponding to PstlAdr"""
if partner.country_id:
postal_address = etree.SubElement(parent_node, 'PstlAdr')
if gen_args.get('pain_flavor').startswith(
'pain.001.001.') or gen_args.get('pain_flavor').startswith(
'pain.008.001.'):
postal_address = etree.SubElement(parent_node, "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 = etree.SubElement(postal_address, "PstCd")
pstcd.text = self._prepare_field(
'Postal Code', 'partner.zip',
{'partner': partner}, 16, gen_args=gen_args)
"Postal Code",
"partner.zip",
{"partner": partner},
16,
gen_args=gen_args,
)
if partner.city:
twnnm = etree.SubElement(postal_address, 'TwnNm')
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')
"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',
{'partner': partner}, 2, gen_args=gen_args)
"Country",
"partner.country_id.code",
{"partner": partner},
2,
gen_args=gen_args,
)
if partner.street:
adrline1 = etree.SubElement(postal_address, 'AdrLine')
adrline1 = etree.SubElement(postal_address, "AdrLine")
adrline1.text = self._prepare_field(
'Adress Line1', 'partner.street',
{'partner': partner}, 70, gen_args=gen_args)
"Adress Line1",
"partner.street",
{"partner": partner},
70,
gen_args=gen_args,
)
return True
@api.model
def generate_party_block(
self, parent_node, party_type, order, partner_bank, gen_args,
bank_line=None):
self, parent_node, party_type, order, partner_bank, gen_args, bank_line=None
):
"""Generate the piece of the XML file corresponding to Name+IBAN+BIC
This code is mutualized between TRF and DD
In some localization (l10n_ch_sepa for example), they need the
bank_line argument"""
assert order in ('B', 'C'), "Order can be 'B' or 'C'"
assert order in ("B", "C"), "Order can be 'B' or 'C'"
party_type_label = _("Partner name")
if party_type == 'Cdtr':
if party_type == "Cdtr":
party_type_label = _("Creditor name")
elif party_type == 'Dbtr':
elif party_type == "Dbtr":
party_type_label = _("Debtor name")
name = 'partner_bank.partner_id.name'
eval_ctx = {'partner_bank': partner_bank}
name = "partner_bank.partner_id.name"
eval_ctx = {"partner_bank": partner_bank}
party_name = self._prepare_field(
party_type_label, name, eval_ctx, gen_args.get('name_maxsize'),
gen_args=gen_args)
party_type_label,
name,
eval_ctx,
gen_args.get("name_maxsize"),
gen_args=gen_args,
)
# At C level, the order is : BIC, Name, IBAN
# At B level, the order is : Name, IBAN, BIC
if order == 'C':
if order == "C":
self.generate_party_agent(
parent_node, party_type, order, partner_bank, gen_args,
bank_line=bank_line)
parent_node,
party_type,
order,
partner_bank,
gen_args,
bank_line=bank_line,
)
party = etree.SubElement(parent_node, party_type)
party_nm = etree.SubElement(party, 'Nm')
party_nm = etree.SubElement(party, "Nm")
party_nm.text = party_name
partner = partner_bank.partner_id
@@ -472,81 +532,99 @@ class AccountPaymentOrder(models.Model):
self.generate_party_id(party, party_type, partner)
self.generate_party_acc_number(
parent_node, party_type, order, partner_bank, gen_args,
bank_line=bank_line)
parent_node, party_type, order, partner_bank, gen_args, bank_line=bank_line
)
if order == 'B':
if order == "B":
self.generate_party_agent(
parent_node, party_type, order, partner_bank, gen_args,
bank_line=bank_line)
parent_node,
party_type,
order,
partner_bank,
gen_args,
bank_line=bank_line,
)
return True
@api.model
def generate_remittance_info_block(self, parent_node, line, gen_args):
remittance_info = etree.SubElement(
parent_node, 'RmtInf')
if line.communication_type == 'normal':
remittance_info_unstructured = etree.SubElement(
remittance_info, 'Ustrd')
remittance_info_unstructured.text = \
self._prepare_field(
'Remittance Unstructured Information',
'line.communication', {'line': line}, 140,
gen_args=gen_args)
remittance_info = etree.SubElement(parent_node, "RmtInf")
if line.communication_type == "normal":
remittance_info_unstructured = etree.SubElement(remittance_info, "Ustrd")
remittance_info_unstructured.text = self._prepare_field(
"Remittance Unstructured Information",
"line.communication",
{"line": line},
140,
gen_args=gen_args,
)
else:
remittance_info_structured = etree.SubElement(
remittance_info, 'Strd')
remittance_info_structured = etree.SubElement(remittance_info, "Strd")
creditor_ref_information = etree.SubElement(
remittance_info_structured, 'CdtrRefInf')
if gen_args.get('pain_flavor') == 'pain.001.001.02':
remittance_info_structured, "CdtrRefInf"
)
if gen_args.get("pain_flavor") == "pain.001.001.02":
creditor_ref_info_type = etree.SubElement(
creditor_ref_information, 'CdtrRefTp')
creditor_ref_information, "CdtrRefTp"
)
creditor_ref_info_type_code = etree.SubElement(
creditor_ref_info_type, 'Cd')
creditor_ref_info_type_code.text = 'SCOR'
creditor_ref_info_type, "Cd"
)
creditor_ref_info_type_code.text = "SCOR"
# SCOR means "Structured Communication Reference"
creditor_ref_info_type_issuer = etree.SubElement(
creditor_ref_info_type, 'Issr')
creditor_ref_info_type_issuer.text = \
line.communication_type
creditor_ref_info_type, "Issr"
)
creditor_ref_info_type_issuer.text = line.communication_type
creditor_reference = etree.SubElement(
creditor_ref_information, 'CdtrRef')
creditor_ref_information, "CdtrRef"
)
else:
if gen_args.get('structured_remittance_issuer', True):
if gen_args.get("structured_remittance_issuer", True):
creditor_ref_info_type = etree.SubElement(
creditor_ref_information, 'Tp')
creditor_ref_information, "Tp"
)
creditor_ref_info_type_or = etree.SubElement(
creditor_ref_info_type, 'CdOrPrtry')
creditor_ref_info_type, "CdOrPrtry"
)
creditor_ref_info_type_code = etree.SubElement(
creditor_ref_info_type_or, 'Cd')
creditor_ref_info_type_code.text = 'SCOR'
creditor_ref_info_type_or, "Cd"
)
creditor_ref_info_type_code.text = "SCOR"
creditor_ref_info_type_issuer = etree.SubElement(
creditor_ref_info_type, 'Issr')
creditor_ref_info_type_issuer.text = \
line.communication_type
creditor_ref_info_type, "Issr"
)
creditor_ref_info_type_issuer.text = line.communication_type
creditor_reference = etree.SubElement(
creditor_ref_information, 'Ref')
creditor_reference = etree.SubElement(creditor_ref_information, "Ref")
creditor_reference.text = \
self._prepare_field(
'Creditor Structured Reference',
'line.communication', {'line': line}, 35,
gen_args=gen_args)
creditor_reference.text = self._prepare_field(
"Creditor Structured Reference",
"line.communication",
{"line": line},
35,
gen_args=gen_args,
)
return True
@api.model
def generate_creditor_scheme_identification(
self, parent_node, identification, identification_label,
eval_ctx, scheme_name_proprietary, gen_args):
csi_id = etree.SubElement(parent_node, 'Id')
csi_privateid = etree.SubElement(csi_id, 'PrvtId')
csi_other = etree.SubElement(csi_privateid, 'Othr')
csi_other_id = etree.SubElement(csi_other, 'Id')
self,
parent_node,
identification,
identification_label,
eval_ctx,
scheme_name_proprietary,
gen_args,
):
csi_id = etree.SubElement(parent_node, "Id")
csi_privateid = etree.SubElement(csi_id, "PrvtId")
csi_other = etree.SubElement(csi_privateid, "Othr")
csi_other_id = etree.SubElement(csi_other, "Id")
csi_other_id.text = self._prepare_field(
identification_label, identification, eval_ctx, gen_args=gen_args)
csi_scheme_name = etree.SubElement(csi_other, 'SchmeNm')
csi_scheme_name_proprietary = etree.SubElement(
csi_scheme_name, 'Prtry')
identification_label, identification, eval_ctx, gen_args=gen_args
)
csi_scheme_name = etree.SubElement(csi_other, "SchmeNm")
csi_scheme_name_proprietary = etree.SubElement(csi_scheme_name, "Prtry")
csi_scheme_name_proprietary.text = scheme_name_proprietary
return True

View File

@@ -1,25 +1,25 @@
# © 2013-2016 Akretion - Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields, api
from odoo import api, fields, models
class BankPaymentLine(models.Model):
_inherit = 'bank.payment.line'
_inherit = "bank.payment.line"
priority = fields.Selection(
related='payment_line_ids.priority', string='Priority')
priority = fields.Selection(related="payment_line_ids.priority", string="Priority")
local_instrument = fields.Selection(
related='payment_line_ids.local_instrument',
string='Local Instrument')
related="payment_line_ids.local_instrument", string="Local Instrument"
)
category_purpose = fields.Selection(
related='payment_line_ids.category_purpose', string='Category Purpose')
purpose = fields.Selection(
related='payment_line_ids.purpose')
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', 'purpose']
res = super(
BankPaymentLine, self
).same_fields_payment_line_and_bank_payment_line()
res += ["priority", "local_instrument", "category_purpose", "purpose"]
return res

View File

@@ -3,17 +3,17 @@
import re
from odoo import api, models, _
from odoo import _, api, models
from odoo.exceptions import ValidationError
BIC_REGEX = re.compile(r"[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$")
class ResBank(models.Model):
_inherit = 'res.bank'
_inherit = "res.bank"
@api.multi
@api.constrains('bic')
@api.constrains("bic")
def _check_bic(self):
"""
This method strengthens the constraint on the BIC of the bank account
@@ -22,14 +22,15 @@ class ResBank(models.Model):
:raise: ValidationError if the BIC doesn't respect the regex of the
SEPA pain schemas.
"""
invalid_banks = self.filtered(
lambda r: r.bic and not BIC_REGEX.match(r.bic)
)
invalid_banks = self.filtered(lambda r: r.bic and not BIC_REGEX.match(r.bic))
if invalid_banks:
raise ValidationError(_(
"The following Bank Identifier Codes (BIC) do not respect "
"the SEPA pattern:\n{bic_list}\n\nSEPA pattern: "
"{sepa_pattern}").format(
sepa_pattern=BIC_REGEX.pattern,
bic_list="\n".join(invalid_banks.mapped('bic'))
))
raise ValidationError(
_(
"The following Bank Identifier Codes (BIC) do not respect "
"the SEPA pattern:\n{bic_list}\n\nSEPA pattern: "
"{sepa_pattern}"
).format(
sepa_pattern=BIC_REGEX.pattern,
bic_list="\n".join(invalid_banks.mapped("bic")),
)
)

View File

@@ -3,53 +3,56 @@
# © 2014 Serv. Tecnol. Avanzados - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import models, fields
import logging
from odoo import fields, models
logger = logging.getLogger(__name__)
class ResCompany(models.Model):
_inherit = 'res.company'
_inherit = "res.company"
initiating_party_issuer = fields.Char(
string='Initiating Party Issuer', size=35,
string="Initiating Party Issuer",
size=35,
help="This will be used as the 'Initiating Party Issuer' in the "
"PAIN files generated by Odoo.")
"PAIN files generated by Odoo.",
)
initiating_party_identifier = fields.Char(
string='Initiating Party Identifier', size=35,
string="Initiating Party Identifier",
size=35,
help="This will be used as the 'Initiating Party Identifier' in "
"the PAIN files generated by Odoo.")
"the PAIN files generated by Odoo.",
)
initiating_party_scheme = fields.Char(
string='Initiating Party Scheme', size=35,
string="Initiating Party Scheme",
size=35,
help="This will be used as the 'Initiating Party Scheme Name' in "
"the PAIN files generated by Odoo.")
"the PAIN files generated by Odoo.",
)
def _default_initiating_party(self):
'''This method is called from post_install.py'''
"""This method is called from post_install.py"""
self.ensure_one()
party_issuer_per_country = {
'BE': 'KBO-BCE', # KBO-BCE = the registry of companies in Belgium
"BE": "KBO-BCE", # KBO-BCE = the registry of companies in Belgium
}
logger.debug(
'Calling _default_initiating_party on company %s', self.name)
logger.debug("Calling _default_initiating_party on company %s", self.name)
country_code = self.country_id.code
if not self.initiating_party_issuer:
if country_code and country_code in party_issuer_per_country:
self.write({
'initiating_party_issuer':
party_issuer_per_country[country_code]})
logger.info(
'Updated initiating_party_issuer on company %s',
self.name)
self.write(
{"initiating_party_issuer": party_issuer_per_country[country_code]}
)
logger.info("Updated initiating_party_issuer on company %s", self.name)
party_identifier = False
if not self.initiating_party_identifier:
if self.vat and country_code:
if country_code == 'BE':
party_identifier = self.vat[2:].replace(' ', '')
if country_code == "BE":
party_identifier = self.vat[2:].replace(" ", "")
if party_identifier:
self.write({
'initiating_party_identifier': party_identifier})
self.write({"initiating_party_identifier": party_identifier})
logger.info(
'Updated initiating_party_identifier on company %s',
self.name)
"Updated initiating_party_identifier on company %s", self.name
)

View File

@@ -6,24 +6,20 @@ from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
_inherit = "res.config.settings"
initiating_party_issuer = fields.Char(
related='company_id.initiating_party_issuer',
readonly=False,
related="company_id.initiating_party_issuer", readonly=False,
)
initiating_party_identifier = fields.Char(
related='company_id.initiating_party_identifier',
readonly=False,
related="company_id.initiating_party_identifier", readonly=False,
)
initiating_party_scheme = fields.Char(
related='company_id.initiating_party_scheme',
readonly=False,
related="company_id.initiating_party_scheme", readonly=False,
)
group_pain_multiple_identifier = fields.Boolean(
string='Multiple identifiers',
implied_group='account_banking_pain_base.'
'group_pain_multiple_identifier',
string="Multiple identifiers",
implied_group="account_banking_pain_base." "group_pain_multiple_identifier",
help="Enable this option if your country requires several SEPA/PAIN "
"identifiers like in Spain.",
"identifiers like in Spain.",
)

View File

@@ -2,12 +2,12 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import api, SUPERUSER_ID
from odoo import SUPERUSER_ID, api
def set_default_initiating_party(cr, registry):
with api.Environment.manage():
env = api.Environment(cr, SUPERUSER_ID, {})
for company in env['res.company'].search([]):
for company in env["res.company"].search([]):
company._default_initiating_party()
return

View File

@@ -1,10 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="group_pain_multiple_identifier" model="res.groups">
<field name="name">SEPA/PAIN Identifiers on Payment Modes</field>
<field name="category_id" ref="base.module_category_hidden"/>
<field name="category_id" ref="base.module_category_hidden" />
</record>
</odoo>

View File

@@ -1,24 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2013-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_payment_line_form" model="ir.ui.view">
<field name="name">pain.base.account.payment.line</field>
<field name="model">account.payment.line</field>
<field name="inherit_id" ref="account_payment_order.account_payment_line_form"/>
<field name="arch" type="xml">
<field name="communication_type" position="before">
<field name="priority"/>
<field name="local_instrument"/>
<field name="category_purpose"/>
<field name="purpose"/>
<record id="account_payment_line_form" model="ir.ui.view">
<field name="name">pain.base.account.payment.line</field>
<field name="model">account.payment.line</field>
<field
name="inherit_id"
ref="account_payment_order.account_payment_line_form"
/>
<field name="arch" type="xml">
<field name="communication_type" position="before">
<field name="priority" />
<field name="local_instrument" />
<field name="category_purpose" />
<field name="purpose" />
</field>
</field>
</field>
</record>
</record>
</odoo>

View File

@@ -1,30 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="account_payment_method_form" model="ir.ui.view">
<field name="name">pain_base.account_payment_method.form</field>
<field name="model">account.payment.method</field>
<field name="inherit_id" ref="account_payment_mode.account_payment_method_form"/>
<field name="arch" type="xml">
<field name="payment_type" position="after">
<field name="pain_version"/>
<field name="convert_to_ascii"
attrs="{'invisible': [('pain_version', '=', False)]}"/>
<record id="account_payment_method_form" model="ir.ui.view">
<field name="name">pain_base.account_payment_method.form</field>
<field name="model">account.payment.method</field>
<field
name="inherit_id"
ref="account_payment_mode.account_payment_method_form"
/>
<field name="arch" type="xml">
<field name="payment_type" position="after">
<field name="pain_version" />
<field
name="convert_to_ascii"
attrs="{'invisible': [('pain_version', '=', False)]}"
/>
</field>
</field>
</field>
</record>
<record id="account_payment_method_tree" model="ir.ui.view">
<field name="name">pain_base.account_payment_method.tree</field>
<field name="model">account.payment.method</field>
<field name="inherit_id" ref="account_payment_mode.account_payment_method_tree"/>
<field name="arch" type="xml">
<field name="payment_type" position="after">
<field name="pain_version"/>
</record>
<record id="account_payment_method_tree" model="ir.ui.view">
<field name="name">pain_base.account_payment_method.tree</field>
<field name="model">account.payment.method</field>
<field
name="inherit_id"
ref="account_payment_mode.account_payment_method_tree"
/>
<field name="arch" type="xml">
<field name="payment_type" position="after">
<field name="pain_version" />
</field>
</field>
</field>
</record>
</record>
</odoo>

View File

@@ -1,24 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright (C) 2013-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
Copyright 2015-2017 Tecnativa
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_payment_mode_form" model="ir.ui.view">
<field name="name">pain_base.account.payment.mode.form</field>
<field name="model">account.payment.mode</field>
<field name="inherit_id" ref="account_payment_order.account_payment_mode_form"/>
<field name="arch" type="xml">
<group name="main" position="inside">
<field name="initiating_party_identifier" groups="account_banking_pain_base.group_pain_multiple_identifier"/>
<field name="initiating_party_issuer" groups="account_banking_pain_base.group_pain_multiple_identifier"/>
<field name="initiating_party_scheme" groups="account_banking_pain_base.group_pain_multiple_identifier"/>
</group>
</field>
</record>
<record id="account_payment_mode_form" model="ir.ui.view">
<field name="name">pain_base.account.payment.mode.form</field>
<field name="model">account.payment.mode</field>
<field
name="inherit_id"
ref="account_payment_order.account_payment_mode_form"
/>
<field name="arch" type="xml">
<group name="main" position="inside">
<field
name="initiating_party_identifier"
groups="account_banking_pain_base.group_pain_multiple_identifier"
/>
<field
name="initiating_party_issuer"
groups="account_banking_pain_base.group_pain_multiple_identifier"
/>
<field
name="initiating_party_scheme"
groups="account_banking_pain_base.group_pain_multiple_identifier"
/>
</group>
</field>
</record>
</odoo>

View File

@@ -1,23 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="account_payment_order_form" model="ir.ui.view">
<field name="name">pain.base.account.payment.order.form</field>
<field name="model">account.payment.order</field>
<field name="inherit_id" ref="account_payment_order.account_payment_order_form"/>
<field name="arch" type="xml">
<field name="company_partner_bank_id" position="after">
<field name="sepa"/>
<field name="batch_booking"/>
<field name="charge_bearer" attrs="{'invisible': [('sepa', '=', True)]}"/>
<record id="account_payment_order_form" model="ir.ui.view">
<field name="name">pain.base.account.payment.order.form</field>
<field name="model">account.payment.order</field>
<field
name="inherit_id"
ref="account_payment_order.account_payment_order_form"
/>
<field name="arch" type="xml">
<field name="company_partner_bank_id" position="after">
<field name="sepa" />
<field name="batch_booking" />
<field
name="charge_bearer"
attrs="{'invisible': [('sepa', '=', True)]}"
/>
</field>
</field>
</field>
</record>
</record>
</odoo>

View File

@@ -1,24 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2015-2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="bank_payment_line_form" model="ir.ui.view">
<field name="name">pain.base.bank.payment.line.form</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_form"/>
<field name="arch" type="xml">
<field name="partner_bank_id" position="after">
<field name="priority"/>
<field name="local_instrument"/>
<field name="category_purpose"/>
<field name="purpose"/>
<record id="bank_payment_line_form" model="ir.ui.view">
<field name="name">pain.base.bank.payment.line.form</field>
<field name="model">bank.payment.line</field>
<field name="inherit_id" ref="account_payment_order.bank_payment_line_form" />
<field name="arch" type="xml">
<field name="partner_bank_id" position="after">
<field name="priority" />
<field name="local_instrument" />
<field name="category_purpose" />
<field name="purpose" />
</field>
</field>
</field>
</record>
</record>
</odoo>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
© 2016 Akretion (Alexis de Lattre <alexis.delattre@akretion.com>)
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
@@ -7,38 +7,40 @@
<record id="view_account_config_settings" model="ir.ui.view">
<field name="name">pain.group.on.account.config.settings</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="inherit_id" ref="account.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//div[@id='analytic']" position="after">
<h2>SEPA/PAIN</h2>
<div class="row mt16 o_settings_container"
id="pain">
<div class="row mt16 o_settings_container" id="pain">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane">
<div class="content-group">
<div class="row mt16">
<label for="initiating_party_identifier"
class="col-md-3 o_light_label"/>
<field name="initiating_party_identifier"/>
<label
for="initiating_party_identifier"
class="col-md-3 o_light_label"
/>
<field name="initiating_party_identifier" />
</div>
<div class="row mt16">
<label for="initiating_party_issuer"
class="col-md-3 o_light_label"/>
<field name="initiating_party_issuer"/>
<label
for="initiating_party_issuer"
class="col-md-3 o_light_label"
/>
<field name="initiating_party_issuer" />
</div>
<div class="row mt16">
<label for="group_pain_multiple_identifier"
class="col-md-3 o_light_label"/>
<field name="group_pain_multiple_identifier"/>
<label
for="group_pain_multiple_identifier"
class="col-md-3 o_light_label"
/>
<field name="group_pain_multiple_identifier" />
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>