mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
First implementation of mandates, but I still have a lot of hesitation about the data model so it may change.
Manage different sequence types in the same file ; we just have to separate them in different payment info blocks.
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import company
|
||||
import wizard
|
||||
import account_banking_sdd
|
||||
from . import company
|
||||
from . import wizard
|
||||
from . import account_banking_sdd
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
'depends': ['account_direct_debit'],
|
||||
'data': [
|
||||
'account_banking_sdd_view.xml',
|
||||
'account_payment_view.xml',
|
||||
'company_view.xml',
|
||||
'wizard/export_sdd_view.xml',
|
||||
'data/payment_type_sdd.xml',
|
||||
'data/mandate_reference_sequence.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'description': '''
|
||||
|
||||
@@ -75,3 +75,88 @@ class banking_export_sdd(orm.Model):
|
||||
'generation_date': fields.date.context_today,
|
||||
'state': 'draft',
|
||||
}
|
||||
|
||||
|
||||
class sdd_mandate(orm.Model):
|
||||
'''SEPA Direct Debit Mandate'''
|
||||
_name = 'sdd.mandate'
|
||||
_description = __doc__
|
||||
_rec_name = 'unique_mandate_reference'
|
||||
_order = 'signature_date desc'
|
||||
|
||||
_columns = {
|
||||
'partner_bank_id': fields.many2one('res.partner.bank', 'Bank Account'),
|
||||
'partner_id': fields.related(
|
||||
'partner_bank_id', 'partner_id', type='many2one',
|
||||
relation='res.partner', string='Partner', readonly=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
'unique_mandate_reference': fields.char(
|
||||
'Unique Mandate Reference', size=35, readonly=True),
|
||||
'type': fields.selection([
|
||||
('recurrent', 'Recurrent'),
|
||||
('oneoff', 'One-Off'),
|
||||
], 'Type of Mandate', required=True),
|
||||
'signature_date': fields.date('Date of Signature of the Mandate'),
|
||||
'scan': fields.binary('Scan of the mandate'),
|
||||
'last_debit_date': fields.date('Date of the Last Debit',
|
||||
help="For recurrent mandates, this field is used to know if the SDD will be of type 'First' or 'Recurring'. For one-off mandates, this field is used to know if the SDD has already been used or not."),
|
||||
'state': fields.selection([
|
||||
('valid', 'Valid'),
|
||||
('expired', 'Expired'),
|
||||
], 'Mandate Status',
|
||||
help="For a recurrent mandate, this field indicate if the mandate is still valid or if it has expired (a recurrent mandate expires if it's not used during 36 months). For a one-off mandate, it expires after its first use."),
|
||||
}
|
||||
|
||||
_sql_constraints = [(
|
||||
'mandate_ref_company_uniq',
|
||||
'unique(unique_mandate_reference, company_id)',
|
||||
'A Mandate with the same reference already exists for this company !'
|
||||
)]
|
||||
|
||||
_defaults = {
|
||||
'company_id': lambda self, cr, uid, context: \
|
||||
self.pool['res.users'].browse(cr, uid, uid, context=context).\
|
||||
company_id.id,
|
||||
'unique_mandate_reference': lambda self, cr, uid, context: \
|
||||
self.pool['ir.sequence'].get(cr, uid, 'sdd.mandate.reference'),
|
||||
'state': 'valid',
|
||||
}
|
||||
|
||||
|
||||
class res_partner_bank(orm.Model):
|
||||
_inherit = 'res.partner.bank'
|
||||
|
||||
_columns = {
|
||||
'sdd_mandate_ids': fields.one2many(
|
||||
'sdd.mandate', 'partner_bank_id', 'SEPA Direct Debit Mandates'),
|
||||
}
|
||||
|
||||
|
||||
class payment_line(orm.Model):
|
||||
_inherit = 'payment.line'
|
||||
|
||||
_columns = {
|
||||
'sdd_mandate_id': fields.many2one(
|
||||
'sdd.mandate', 'SEPA Direct Debit Mandate'),
|
||||
}
|
||||
|
||||
def _check_sdd_mandate(self, cr, uid, ids):
|
||||
for payline in self.browse(cr, uid, ids):
|
||||
if payline.sdd_mandate_id and not payline.bank_id:
|
||||
raise orm.except_orm(
|
||||
_('Error :'),
|
||||
_("Missing bank account on the payment line with SEPA\
|
||||
Direct Debit Mandate '%s'."
|
||||
% payline.sdd_mandate_id.unique_mandate_reference))
|
||||
elif payline.sdd_mandate_id and payline.bank_id and payline.sdd_mandate_id.partner_bank_id != payline.bank_id.id:
|
||||
raise orm.except_orm(
|
||||
_('Error :'),
|
||||
_("The SEPA Direct Debit Mandate '%s' is not related??"))
|
||||
|
||||
return True
|
||||
|
||||
# _constraints = [
|
||||
# (_check_sdd_mandate, "Mandate must be attached to bank account", ['bank_id', 'sdd_mandate_id']),
|
||||
# ]
|
||||
|
||||
# TODO inherit create to select the first mandate ??
|
||||
|
||||
@@ -79,5 +79,107 @@
|
||||
view_mode="tree,form"
|
||||
/>
|
||||
|
||||
<record id="sdd_mandate_form" model="ir.ui.view">
|
||||
<field name="name">sdd.mandate.form</field>
|
||||
<field name="model">sdd.mandate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="SEPA Direct Debit Mandate" version="7.0">
|
||||
<header>
|
||||
<field name="state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<label string="SEPA Direct Debit Mandate"/>
|
||||
<field name="unique_mandate_reference" class="oe_inline"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="partner_id" invisible="not context.get('sdd_mandate_main_view', False)"/>
|
||||
<field name="type"/>
|
||||
<field name="signature_date"/>
|
||||
<field name="scan"/>
|
||||
<field name="last_debit_date"/>
|
||||
<field name="partner_bank_id"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sdd_mandate_tree" model="ir.ui.view">
|
||||
<field name="name">sdd.mandate.tree</field>
|
||||
<field name="model">sdd.mandate</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="SEPA Direct Debit Mandate" colors="blue:state=='expired'">
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="partner_id" invisible="not context.get('sdd_mandate_main_view', False)"/>
|
||||
<field name="unique_mandate_reference" string="Reference"/>
|
||||
<field name="type" string="Type"/>
|
||||
<field name="signature_date" string="Signature Date"/>
|
||||
<field name="last_debit_date"/>
|
||||
<field name="state" string="Status"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sdd_mandate_action" model="ir.actions.act_window">
|
||||
<field name="name">SEPA Direct Debit Mandate</field>
|
||||
<field name="res_model">sdd.mandate</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'sdd_mandate_main_view': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a new SEPA Direct Debit Mandate.
|
||||
</p><p>
|
||||
The SEPA Direct Debit Mandate is a document signed by your customer that gives you the autorization to do one or several direct debits on his bank account.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="sdd_mandate_menu"
|
||||
parent="account_banking.menu_finance_banking_actions"
|
||||
action="sdd_mandate_action"
|
||||
sequence="20"
|
||||
/>
|
||||
|
||||
<record id="sdd_mandate_partner_bank_form" model="ir.ui.view">
|
||||
<field name="name">sdd.mandate.res.partner.bank.form</field>
|
||||
<field name="model">res.partner.bank</field>
|
||||
<field name="inherit_id" ref="base.view_partner_bank_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<group name="bank" position="after">
|
||||
<group name="sdd_mandates" string="SEPA Direct Debit Mandates" colspan="4">
|
||||
<field name="sdd_mandate_ids" context="{'default_partner_bank_id': active_id}" nolabel="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sdd_mandate_partner_bank_tree" model="ir.ui.view">
|
||||
<field name="name">sdd.mandate.res.partner.bank.tree</field>
|
||||
<field name="model">res.partner.bank</field>
|
||||
<field name="inherit_id" ref="base.view_partner_bank_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="partner_id" position="after">
|
||||
<field name="sdd_mandate_ids" string="SDD Mandates"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- also add number of mandates on partner form -->
|
||||
<record id="sdd_mandate_partner_form" model="ir.ui.view">
|
||||
<field name="name">sdd.mandate.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='bank_ids']/tree/field[@name='owner_name']" position="after">
|
||||
<field name="sdd_mandate_ids" string="SDD Mandates"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
26
account_banking_sepa_direct_debit/account_payment_view.xml
Normal file
26
account_banking_sepa_direct_debit/account_payment_view.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2013 Akretion (http://www.akretion.com)
|
||||
@author: Alexis de Lattre <alexis.delattre@akretion.com>
|
||||
The licence is in the file __openerp__.py
|
||||
-->
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="sdd_view_payment_order_form" model="ir.ui.view">
|
||||
<field name="name">sdd.payment.order.form</field>
|
||||
<field name="model">payment.order</field>
|
||||
<field name="inherit_id" ref="account_payment.view_payment_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='line_ids']/form/notebook/page/group/field[@name='bank_id']" position="after">
|
||||
<field name="sdd_mandate_id" domain="[('partner_bank_id', '=', bank_id), ('state', '=', 'valid')]" invisible="context.get('default_payment_order_type')!='debit'" />
|
||||
<newline />
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='line_ids']/tree/field[@name='bank_id']" position="after">
|
||||
<field name="sdd_mandate_id" string="SDD Mandate" invisible="context.get('default_payment_order_type')!='debit'"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -24,15 +24,16 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class res_company(orm.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
_columns = {
|
||||
'sepa_creditor_identifier': fields.char('SEPA Creditor Identifier', size=35,
|
||||
'sepa_creditor_identifier': fields.char(
|
||||
'SEPA Creditor Identifier', size=35,
|
||||
help="Enter the Creditor Identifier that has been attributed to your company to make SEPA Direct Debits. This identifier is composed of :\n- your country ISO code (2 letters)\n- a 2-digits checkum\n- a 3-letters business code\n- a country-specific identifier"),
|
||||
}
|
||||
|
||||
|
||||
def is_sepa_creditor_identifier_valid(self, cr, uid, sepa_creditor_identifier, context=None):
|
||||
"""Check if SEPA Creditor Identifier is valid
|
||||
@param sepa_creditor_identifier: SEPA Creditor Identifier as str or unicode
|
||||
@@ -62,7 +63,6 @@ class res_company(orm.Model):
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def _check_sepa_creditor_identifier(self, cr, uid, ids):
|
||||
for company in self.browse(cr, uid, ids):
|
||||
if company.sepa_creditor_identifier:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="0"> <!-- TODO : put to 1 when dev of the module is finished -->
|
||||
|
||||
|
||||
<record id="sdd_mandate_seq_type" model="ir.sequence.type">
|
||||
<field name="name">SDD Mandate Reference</field>
|
||||
<field name="code">sdd.mandate.reference</field>
|
||||
</record>
|
||||
|
||||
<record id="sdd_mandate_seq" model="ir.sequence">
|
||||
<field name="name">SDD Mandate Reference</field>
|
||||
<field name="code">sdd.mandate.reference</field>
|
||||
<field name="prefix">RUM</field>
|
||||
<field name="padding" eval="7"/>
|
||||
<!-- remember that max size for the mandate ref is 35 -->
|
||||
</record>
|
||||
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,2 +1,4 @@
|
||||
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
|
||||
"access_banking_export_sdd","Full access on banking.export.sdd","model_banking_export_sdd","account_payment.group_account_payment",1,1,1,1
|
||||
"access_sdd_mandate","Full access on sdd.mandate","model_sdd_mandate","account_payment.group_account_payment",1,1,1,1
|
||||
"access_sdd_mandate_read","Read access on sdd.mandate","model_sdd_mandate","base.group_user",1,0,0,0
|
||||
|
||||
|
@@ -20,4 +20,4 @@
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import export_sdd
|
||||
from . import export_sdd
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
|
||||
|
||||
from openerp.osv import orm, fields
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools, netsvc
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from lxml import etree
|
||||
import logging
|
||||
|
||||
@@ -115,7 +115,7 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
else:
|
||||
raise orm.except_orm(_('Error :'), _("Cannot compute the '%s'.") % field_name)
|
||||
if not isinstance(value, (str, unicode)):
|
||||
raise orm.except_orm(_('Field type error :'), _("The '%s' is a(n) %s. It should be a string or unicode.") % (field_name, type(value)))
|
||||
raise orm.except_orm(_('Field type error :'), _("The type of the field '%s' is %s. It should be a string or unicode.") % (field_name, type(value)))
|
||||
if not value:
|
||||
raise orm.except_orm(_('Error :'), _("The '%s' is empty or 0. It should have a non-null value.") % field_name)
|
||||
if max_size and len(value) > max_size:
|
||||
@@ -175,90 +175,140 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
initiating_party_name = etree.SubElement(initiating_party_1_8, 'Nm')
|
||||
initiating_party_name.text = my_company_name
|
||||
|
||||
# B. Payment info
|
||||
payment_info_2_0 = etree.SubElement(pain_root, 'PmtInf')
|
||||
payment_info_identification_2_1 = etree.SubElement(payment_info_2_0, 'PmtInfId')
|
||||
payment_info_identification_2_1.text = sepa_export.msg_identification
|
||||
payment_method_2_2 = etree.SubElement(payment_info_2_0, 'PmtMtd')
|
||||
payment_method_2_2.text = 'DD'
|
||||
if pain_flavor in ['pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04']:
|
||||
# batch_booking is in "Payment Info" with pain.008.001.02/03
|
||||
batch_booking_2_3 = etree.SubElement(payment_info_2_0, 'BtchBookg')
|
||||
batch_booking_2_3.text = str(sepa_export.batch_booking).lower()
|
||||
# It may seem surprising, but the
|
||||
# "SEPA Core Direct Debit Scheme Customer-to-bank Implementation guidelines"
|
||||
# v6.0 says that control sum and nb_of_transactions should be present
|
||||
# at both "group header" level and "payment info" level
|
||||
if pain_flavor in ['pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04']:
|
||||
nb_of_transactions_2_4 = etree.SubElement(payment_info_2_0, 'NbOfTxs')
|
||||
control_sum_2_5 = etree.SubElement(payment_info_2_0, 'CtrlSum')
|
||||
payment_type_info_2_6 = etree.SubElement(payment_info_2_0, 'PmtTpInf')
|
||||
service_level_2_8 = etree.SubElement(payment_type_info_2_6, 'SvcLvl')
|
||||
service_level_code_2_9 = etree.SubElement(service_level_2_8, 'Cd')
|
||||
service_level_code_2_9.text = 'SEPA'
|
||||
local_instrument_2_11 = etree.SubElement(payment_type_info_2_6, 'LclInstrm')
|
||||
local_instr_code_2_12 = etree.SubElement(local_instrument_2_11, 'Cd')
|
||||
local_instr_code_2_12.text = 'CORE'
|
||||
# TODO : 2.14 Sequence Type MANDATORY => I set it in section C (2.40)
|
||||
# not B (2.14) so that we can have several different Sequence Types
|
||||
# in the same XML file
|
||||
# the Sample XML files show Seq type at C level
|
||||
# BUT it may not be possible,
|
||||
# 1. extract from CIC documentation :
|
||||
# "Attention, les remises présentées devront être scindées par le créancier
|
||||
# par type de séquence"
|
||||
# In the guidelines, they only talk about B level
|
||||
# If ‘Amendment Indicator’ is ‘true’,
|
||||
# and ‘Original Debtor Agent’ is set to ‘SMNDA’,
|
||||
# this message element must indicate ‘FRST
|
||||
# 'FRST' = First ; 'OOFF' = One Off ; 'RCUR' : Recurring
|
||||
# 'FNAL' = Final
|
||||
requested_collec_date_2_18 = etree.SubElement(payment_info_2_0, 'ReqdColltnDt')
|
||||
requested_collec_date_2_18.text = my_requested_collec_date
|
||||
creditor_2_19 = etree.SubElement(payment_info_2_0, 'Cdtr')
|
||||
creditor_name = etree.SubElement(creditor_2_19, 'Nm')
|
||||
creditor_name.text = my_company_name
|
||||
creditor_account_2_20 = etree.SubElement(payment_info_2_0, 'CdtrAcct')
|
||||
creditor_account_id = etree.SubElement(creditor_account_2_20, 'Id')
|
||||
creditor_account_iban = etree.SubElement(creditor_account_id, 'IBAN')
|
||||
creditor_account_iban.text = self._validate_iban(cr, uid,
|
||||
self._prepare_field(cr, uid, 'Company IBAN',
|
||||
'sepa_export.payment_order_ids[0].mode.bank_id.acc_number',
|
||||
sepa_export=sepa_export, context=context),
|
||||
context=context)
|
||||
|
||||
creditor_agent_2_21 = etree.SubElement(payment_info_2_0, 'CdtrAgt')
|
||||
creditor_agent_institution = etree.SubElement(creditor_agent_2_21, 'FinInstnId')
|
||||
creditor_agent_bic = etree.SubElement(creditor_agent_institution, bic_xml_tag)
|
||||
creditor_agent_bic.text = self._prepare_field(cr, uid, 'Company BIC',
|
||||
'sepa_export.payment_order_ids[0].mode.bank_id.bank.bic',
|
||||
sepa_export=sepa_export, context=context)
|
||||
|
||||
charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr')
|
||||
charge_bearer_2_24.text = sepa_export.charge_bearer
|
||||
|
||||
creditor_scheme_identification_2_27 = etree.SubElement(payment_info_2_0, 'CdtrSchmeId')
|
||||
csi_id = etree.SubElement(creditor_scheme_identification_2_27, 'Id')
|
||||
csi_orgid = csi_id = etree.SubElement(csi_id, 'OrgId')
|
||||
csi_other = etree.SubElement(csi_orgid, 'Othr')
|
||||
csi_other_id = etree.SubElement(csi_other, 'Id')
|
||||
csi_other_id.text = self._prepare_field(cr, uid,
|
||||
'SEPA Creditor Identifier',
|
||||
'sepa_export.payment_order_ids[0].company_id.sepa_creditor_identifier',
|
||||
sepa_export=sepa_export, context=context)
|
||||
csi_scheme_name = etree.SubElement(csi_other, 'SchmeNm')
|
||||
csi_scheme_name_proprietary = etree.SubElement(csi_scheme_name, 'Prtry')
|
||||
csi_scheme_name_proprietary.text = 'SEPA'
|
||||
|
||||
transactions_count = 0
|
||||
transactions_count_1_6 = 0
|
||||
total_amount = 0.0
|
||||
amount_control_sum = 0.0
|
||||
amount_control_sum_1_7 = 0.0
|
||||
first_recur_lines = {}
|
||||
# key = sequence type ; value = list of lines as objects
|
||||
# Iterate on payment orders
|
||||
for payment_order in sepa_export.payment_order_ids:
|
||||
total_amount = total_amount + payment_order.total
|
||||
# Iterate each payment lines
|
||||
for line in payment_order.line_ids:
|
||||
transactions_count += 1
|
||||
transactions_count_1_6 += 1
|
||||
if not line.sdd_mandate_id:
|
||||
raise orm.except_orm(
|
||||
_('Error:'),
|
||||
_("Missing SEPA Direct Debit mandate on the payment line with partner '%s' and Invoice ref '%s'.")
|
||||
% (line.partner_id.name,
|
||||
line.ml_inv_ref.number))
|
||||
if line.sdd_mandate_id.state != 'valid':
|
||||
raise orm.except_orm(
|
||||
_('Error:'),
|
||||
_("The SEPA Direct Debit mandate with reference '%s' for partner '%s' has expired.")
|
||||
% (line.sdd_mandate_id.unique_mandate_reference,
|
||||
line.sdd_mandate_id.partner_id.name))
|
||||
|
||||
if not line.sdd_mandate_id.signature_date:
|
||||
raise orm.except_orm(
|
||||
_('Error:'),
|
||||
_("Missing signature date on SEPA Direct Debit mandate with reference '%s' for partner '%s'.")
|
||||
% (line.sdd_mandate_id.unique_mandate_reference,
|
||||
line.sdd_mandate_id.partner_id.name))
|
||||
elif line.sdd_mandate_id.signature_date > datetime.today().strftime('%Y-%m-%d'):
|
||||
raise orm.except_orm(
|
||||
_('Error:'),
|
||||
_("The signature date on SEPA Direct Debit mandate with reference '%s' for partner '%s' is '%s', which is in the future !")
|
||||
% (line.sdd_mandate_id.unique_mandate_reference,
|
||||
line.sdd_mandate_id.partner_id.name,
|
||||
line.sdd_mandate_id.signature_date))
|
||||
|
||||
if line.sdd_mandate_id.type == 'oneoff':
|
||||
if not line.sdd_mandate_id.last_debit_date:
|
||||
if first_recur_lines.get('OOFF'):
|
||||
first_recur_lines['OOFF'].append(line)
|
||||
else:
|
||||
first_recur_lines['OOFF'] = [line]
|
||||
else:
|
||||
raise orm.except_orm(
|
||||
_('Error :'),
|
||||
_("The mandate with reference '%s' for partner '%s' has type set to 'One-Off' and it has a last debit date set to '%s', so we can't use it.")
|
||||
% (line.sdd_mandate_id.unique_mandate_reference,
|
||||
line.sdd_mandate_id.partner_id.name,
|
||||
line.sdd_mandate_id.last_debit_date))
|
||||
elif line.sdd_mandate_id.type == 'recurrent':
|
||||
if line.sdd_mandate_id.last_debit_date:
|
||||
if first_recur_lines.get('RCUR'):
|
||||
first_recur_lines['RCUR'].append(line)
|
||||
else:
|
||||
first_recur_lines['RCUR'] = [line]
|
||||
else:
|
||||
if first_recur_lines.get('FRST'):
|
||||
first_recur_lines['FRST'].append(line)
|
||||
else:
|
||||
first_recur_lines['FRST'] = [line]
|
||||
|
||||
for sequence_type, lines in first_recur_lines.items():
|
||||
# B. Payment info
|
||||
payment_info_2_0 = etree.SubElement(pain_root, 'PmtInf')
|
||||
payment_info_identification_2_1 = etree.SubElement(payment_info_2_0, 'PmtInfId')
|
||||
payment_info_identification_2_1.text = sepa_export.msg_identification
|
||||
payment_method_2_2 = etree.SubElement(payment_info_2_0, 'PmtMtd')
|
||||
payment_method_2_2.text = 'DD'
|
||||
# batch_booking is in "Payment Info" with pain.008.001.02/03
|
||||
batch_booking_2_3 = etree.SubElement(payment_info_2_0, 'BtchBookg')
|
||||
batch_booking_2_3.text = str(sepa_export.batch_booking).lower()
|
||||
# The "SEPA Core Direct Debit Scheme Customer-to-bank
|
||||
# Implementation guidelines" v6.0 says that control sum
|
||||
# and nb_of_transactions should be present
|
||||
# at both "group header" level and "payment info" level
|
||||
nb_of_transactions_2_4 = etree.SubElement(payment_info_2_0, 'NbOfTxs')
|
||||
control_sum_2_5 = etree.SubElement(payment_info_2_0, 'CtrlSum')
|
||||
payment_type_info_2_6 = etree.SubElement(payment_info_2_0, 'PmtTpInf')
|
||||
service_level_2_8 = etree.SubElement(payment_type_info_2_6, 'SvcLvl')
|
||||
service_level_code_2_9 = etree.SubElement(service_level_2_8, 'Cd')
|
||||
service_level_code_2_9.text = 'SEPA'
|
||||
local_instrument_2_11 = etree.SubElement(payment_type_info_2_6, 'LclInstrm')
|
||||
local_instr_code_2_12 = etree.SubElement(local_instrument_2_11, 'Cd')
|
||||
local_instr_code_2_12.text = 'CORE'
|
||||
# 2.14 Sequence Type MANDATORY
|
||||
# this message element must indicate ‘FRST
|
||||
# 'FRST' = First ; 'OOFF' = One Off ; 'RCUR' : Recurring
|
||||
# 'FNAL' = Final
|
||||
sequence_type_2_14 = etree.SubElement(payment_type_info_2_6, 'SeqTp')
|
||||
sequence_type_2_14.text = sequence_type
|
||||
|
||||
requested_collec_date_2_18 = etree.SubElement(payment_info_2_0, 'ReqdColltnDt')
|
||||
requested_collec_date_2_18.text = my_requested_collec_date
|
||||
creditor_2_19 = etree.SubElement(payment_info_2_0, 'Cdtr')
|
||||
creditor_name = etree.SubElement(creditor_2_19, 'Nm')
|
||||
creditor_name.text = my_company_name
|
||||
creditor_account_2_20 = etree.SubElement(payment_info_2_0, 'CdtrAcct')
|
||||
creditor_account_id = etree.SubElement(creditor_account_2_20, 'Id')
|
||||
creditor_account_iban = etree.SubElement(creditor_account_id, 'IBAN')
|
||||
creditor_account_iban.text = self._validate_iban(cr, uid,
|
||||
self._prepare_field(cr, uid, 'Company IBAN',
|
||||
'sepa_export.payment_order_ids[0].mode.bank_id.acc_number',
|
||||
sepa_export=sepa_export, context=context),
|
||||
context=context)
|
||||
|
||||
creditor_agent_2_21 = etree.SubElement(payment_info_2_0, 'CdtrAgt')
|
||||
creditor_agent_institution = etree.SubElement(creditor_agent_2_21, 'FinInstnId')
|
||||
creditor_agent_bic = etree.SubElement(creditor_agent_institution, bic_xml_tag)
|
||||
creditor_agent_bic.text = self._prepare_field(cr, uid, 'Company BIC',
|
||||
'sepa_export.payment_order_ids[0].mode.bank_id.bank.bic',
|
||||
sepa_export=sepa_export, context=context)
|
||||
|
||||
charge_bearer_2_24 = etree.SubElement(payment_info_2_0, 'ChrgBr')
|
||||
charge_bearer_2_24.text = sepa_export.charge_bearer
|
||||
|
||||
creditor_scheme_identification_2_27 = etree.SubElement(payment_info_2_0, 'CdtrSchmeId')
|
||||
csi_id = etree.SubElement(creditor_scheme_identification_2_27, 'Id')
|
||||
csi_orgid = csi_id = etree.SubElement(csi_id, 'OrgId')
|
||||
csi_other = etree.SubElement(csi_orgid, 'Othr')
|
||||
csi_other_id = etree.SubElement(csi_other, 'Id')
|
||||
csi_other_id.text = self._prepare_field(cr, uid,
|
||||
'SEPA Creditor Identifier',
|
||||
'sepa_export.payment_order_ids[0].company_id.sepa_creditor_identifier',
|
||||
sepa_export=sepa_export, context=context)
|
||||
csi_scheme_name = etree.SubElement(csi_other, 'SchmeNm')
|
||||
csi_scheme_name_proprietary = etree.SubElement(csi_scheme_name, 'Prtry')
|
||||
csi_scheme_name_proprietary.text = 'SEPA'
|
||||
|
||||
transactions_count_2_4 = 0
|
||||
amount_control_sum_2_5 = 0.0
|
||||
for line in lines:
|
||||
transactions_count_2_4 += 1
|
||||
# C. Direct Debit Transaction Info
|
||||
dd_transaction_info_2_28 = etree.SubElement(payment_info_2_0, 'DrctDbtTxInf')
|
||||
payment_identification_2_29 = etree.SubElement(dd_transaction_info_2_28, 'PmtId')
|
||||
@@ -268,20 +318,26 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
'End to End Identification', 'line.communication', 35,
|
||||
line=line, context=context)
|
||||
payment_type_2_32 = etree.SubElement(dd_transaction_info_2_28, 'PmtTpInf')
|
||||
# Sequence Type : do we have to set it at Payment Info level ?
|
||||
#sequence_type_2_40 = etree.SubElement(payment_type_2_32, 'SeqTp')
|
||||
#sequence_type_2_40.text = 'FRST' # TODO
|
||||
currency_name = self._prepare_field(cr, uid, 'Currency Code',
|
||||
'line.currency.name', 3, line=line, context=context)
|
||||
instructed_amount_2_44 = etree.SubElement(dd_transaction_info_2_28, 'InstdAmt', Ccy=currency_name)
|
||||
instructed_amount_2_44.text = '%.2f' % line.amount_currency
|
||||
amount_control_sum += line.amount_currency
|
||||
amount_control_sum_1_7 += line.amount_currency
|
||||
amount_control_sum_2_5 += line.amount_currency
|
||||
dd_transaction_2_46 = etree.SubElement(dd_transaction_info_2_28, 'DrctDbtTx')
|
||||
mandate_related_info_2_47 = etree.SubElement(dd_transaction_2_46, 'MndtRltdInf')
|
||||
mandate_identification_2_48 = etree.SubElement(mandate_related_info_2_47, 'MndtId')
|
||||
mandate_identification_2_48.text = 'RUM1242' # TODO
|
||||
mandate_signature_date_2_49 = etree.SubElement(mandate_related_info_2_47, 'DtOfSgntr')
|
||||
mandate_signature_date_2_49.text = '2013-02-20' # TODO
|
||||
mandate_identification_2_48.text = self._prepare_field(
|
||||
cr, uid, 'Unique Mandate Reference',
|
||||
'line.sdd_mandate_id.unique_mandate_reference',
|
||||
35, line=line, context=context)
|
||||
mandate_signature_date_2_49 = etree.SubElement(
|
||||
mandate_related_info_2_47, 'DtOfSgntr')
|
||||
mandate_signature_date_2_49.text = self._prepare_field(
|
||||
cr, uid, 'Mandate Signature Date',
|
||||
'line.sdd_mandate_id.signature_date', 10,
|
||||
line=line, context=context)
|
||||
|
||||
# TODO look at 2.50 "Amendment Indicator
|
||||
debtor_agent_2_70 = etree.SubElement(dd_transaction_info_2_28, 'DbtrAgt')
|
||||
debtor_agent_institution = etree.SubElement(debtor_agent_2_70, 'FinInstnId')
|
||||
@@ -308,10 +364,10 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
remittance_info_unstructured_2_89.text = self._prepare_field(cr, uid,
|
||||
'Remittance Information', 'line.communication',
|
||||
140, line=line, context=context)
|
||||
|
||||
if pain_flavor in ['pain.008.001.02', 'pain.008.001.03', 'pain.008.001.04']:
|
||||
nb_of_transactions_1_6.text = nb_of_transactions_2_4.text = str(transactions_count)
|
||||
control_sum_1_7.text = control_sum_2_5.text = '%.2f' % amount_control_sum
|
||||
nb_of_transactions_2_4.text = str(transactions_count_2_4)
|
||||
control_sum_2_5.text = '%.2f' % amount_control_sum_2_5
|
||||
nb_of_transactions_1_6.text = str(transactions_count_1_6)
|
||||
control_sum_1_7.text = '%.2f' % amount_control_sum_1_7
|
||||
|
||||
|
||||
xml_string = etree.tostring(root, pretty_print=True, encoding='UTF-8', xml_declaration=True)
|
||||
@@ -345,7 +401,7 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
'charge_bearer': sepa_export.charge_bearer,
|
||||
'requested_collec_date': sepa_export.requested_collec_date,
|
||||
'total_amount': total_amount,
|
||||
'nb_transactions': transactions_count,
|
||||
'nb_transactions': transactions_count_1_6,
|
||||
'file': base64.encodestring(xml_string),
|
||||
'payment_order_ids': [
|
||||
(6, 0, [x.id for x in sepa_export.payment_order_ids])
|
||||
@@ -374,13 +430,15 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
Cancel the SEPA Direct Debit file: just drop the file
|
||||
'''
|
||||
sepa_export = self.browse(cr, uid, ids[0], context=context)
|
||||
self.pool.get('banking.export.sdd').unlink(cr, uid, sepa_export.file_id.id, context=context)
|
||||
self.pool.get('banking.export.sdd').unlink(
|
||||
cr, uid, sepa_export.file_id.id, context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
|
||||
def save_sepa(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
Save the SEPA Direct Debit file: mark all payments in the file as 'sent'.
|
||||
Write 'last debit date' on mandate and set oneoff mandate to expired
|
||||
'''
|
||||
sepa_export = self.browse(cr, uid, ids[0], context=context)
|
||||
sepa_file = self.pool.get('banking.export.sdd').write(cr, uid,
|
||||
@@ -388,4 +446,14 @@ class banking_export_sdd_wizard(orm.TransientModel):
|
||||
wf_service = netsvc.LocalService('workflow')
|
||||
for order in sepa_export.payment_order_ids:
|
||||
wf_service.trg_validate(uid, 'payment.order', order.id, 'sent', cr)
|
||||
mandate_ids = [line.sdd_mandate_id.id for line in order.line_ids]
|
||||
self.pool['sdd.mandate'].write(
|
||||
cr, uid, mandate_ids, {
|
||||
'last_debit_date': datetime.today().strftime('%Y-%m-%d')
|
||||
},
|
||||
context=context)
|
||||
oneoff_mandate_ids = [line.sdd_mandate_id.id for line in order.line_ids if line.sdd_mandate_id.type == 'oneoff']
|
||||
self.pool['sdd.mandate'].write(
|
||||
cr, uid, oneoff_mandate_ids, {'state': 'expired'},
|
||||
context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
Reference in New Issue
Block a user