forked from Mapan/odoo17e
306 lines
17 KiB
Python
306 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo import fields
|
|
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
|
from odoo.addons.account_bacs.models.account_journal import format_communication
|
|
from odoo.tests import tagged
|
|
|
|
import itertools
|
|
|
|
import datetime
|
|
|
|
import base64
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestBACS(AccountTestInvoicingCommon):
|
|
|
|
@classmethod
|
|
def setUpClass(cls, chart_template_ref=None):
|
|
super().setUpClass(chart_template_ref=chart_template_ref)
|
|
cls.env.ref('base.GBP').active = True
|
|
|
|
cls.bank_barclays = cls.env['res.bank'].create({
|
|
'name': 'BARCLAYS BANK PLC',
|
|
'bic': 'BARCGB22XXX',
|
|
})
|
|
cls.bank_hsbc = cls.env['res.bank'].create({
|
|
'name': 'HSBC',
|
|
'bic': 'HBUKGB4BXXX',
|
|
})
|
|
|
|
cls.company_data['company'].write({
|
|
'country_id': cls.env.ref('base.be').id,
|
|
'bacs_sun': '123456',
|
|
})
|
|
cls.bank_journal = cls.company_data['default_journal_bank']
|
|
cls.bank_journal.write({
|
|
'bank_id': cls.bank_barclays.id,
|
|
'bank_acc_number': 'GB11BARC20039525689371',
|
|
'currency_id': cls.env.ref('base.GBP').id,
|
|
})
|
|
|
|
cls.bacs_dc = cls.bank_journal.outbound_payment_method_line_ids.filtered(lambda l: l.code == 'bacs_dc')
|
|
cls.bacs_dc_method = cls.env.ref('account_bacs.payment_method_bacs_dc')
|
|
|
|
cls.bacs_dd = cls.bank_journal.inbound_payment_method_line_ids.filtered(lambda l: l.code == 'bacs_dd')
|
|
cls.bacs_dd_method = cls.env.ref('account_bacs.payment_method_bacs_dd')
|
|
|
|
def create_account(self, number, partner, bank):
|
|
return self.env['res.partner.bank'].create({
|
|
'acc_number': number,
|
|
'partner_id': partner.id,
|
|
'bank_id': bank.id,
|
|
'allow_out_payment': True,
|
|
})
|
|
|
|
def create_ddi(self, partner, partner_bank, company, payment_journal):
|
|
return self.env['bacs.ddi'].create({
|
|
'partner_bank_id': partner_bank.id,
|
|
'start_date': fields.Date.today(),
|
|
'partner_id': partner.id,
|
|
'company_id': company.id,
|
|
'payment_journal_id': payment_journal.id
|
|
})
|
|
|
|
def create_invoice(self, partner):
|
|
invoice = self.env['account.move'].create({
|
|
'move_type': 'out_invoice',
|
|
'partner_id': partner.id,
|
|
'currency_id': self.env.ref('base.GBP').id,
|
|
'payment_reference': 'invoice to client',
|
|
'invoice_line_ids': [(0, 0, {
|
|
'product_id': self.env['product.product'].create({'name': 'A Test Product'}).id,
|
|
'quantity': 1,
|
|
'price_unit': 42,
|
|
'name': 'something',
|
|
})],
|
|
})
|
|
invoice.action_post()
|
|
return invoice
|
|
|
|
def pay_with_mandate(self, invoice, mandate):
|
|
self.env['account.payment.register'].with_context(active_model='account.move', active_ids=invoice.ids).create({
|
|
'payment_date': invoice.invoice_date_due or invoice.invoice_date,
|
|
'journal_id': mandate.payment_journal_id.id,
|
|
'payment_method_line_id': self.bacs_dd.id,
|
|
})._create_payments()
|
|
|
|
def create_payment(self, partner, amount, payment_method, journal, date, payment_type, partner_bank=None):
|
|
return self.env['account.payment'].create({
|
|
'journal_id': journal.id,
|
|
'payment_method_line_id': payment_method.id,
|
|
'payment_type': payment_type,
|
|
'date': date,
|
|
'amount': amount,
|
|
'partner_id': partner.id,
|
|
'partner_bank_id': partner_bank.id if partner_bank else False,
|
|
})
|
|
|
|
def verify_bacs_file_headers(self, company, batch_payment, header_lines):
|
|
# test vol1
|
|
self.assertEqual(len(header_lines[0]), 80)
|
|
self.assertEqual(header_lines[0][:4], 'VOL1')
|
|
self.assertEqual(header_lines[0][4:10], batch_payment.bacs_submission_serial)
|
|
self.assertEqual(header_lines[0][10:41], ' ' * 31)
|
|
self.assertEqual(header_lines[0][41:47], company.bacs_sun)
|
|
self.assertEqual(header_lines[0][47:79], ' ' * 32)
|
|
self.assertEqual(header_lines[0][79:], '1')
|
|
# test HDR1
|
|
self.assertEqual(len(header_lines[1]), 80)
|
|
self.assertEqual(header_lines[1][:5], 'HDR1A')
|
|
self.assertEqual(header_lines[1][5:11], company.bacs_sun)
|
|
self.assertEqual(header_lines[1][11:15], 'S 1')
|
|
self.assertEqual(header_lines[1][15:21], company.bacs_sun)
|
|
self.assertEqual(header_lines[1][21:27], batch_payment.bacs_submission_serial)
|
|
self.assertEqual(header_lines[1][27:35], '00010001')
|
|
self.assertEqual(header_lines[1][35:41], ' ' * 6)
|
|
self.assertEqual(header_lines[1][41:47], batch_payment.date.strftime(' %y%j'))
|
|
self.assertEqual(header_lines[1][47:53], batch_payment.bacs_expiry_date.strftime(' %y%j'))
|
|
self.assertEqual(header_lines[1][53:], ' ' * 27)
|
|
# test HDR2
|
|
self.assertEqual(len(header_lines[2]), 80)
|
|
self.assertEqual(header_lines[2][:5], 'HDR2F')
|
|
self.assertEqual(header_lines[2][5:10], '02000')
|
|
self.assertEqual(header_lines[2][10:15], '00100')
|
|
self.assertEqual(header_lines[2][15:], ' ' * 65)
|
|
# test UHL1
|
|
self.assertEqual(len(header_lines[3]), 80)
|
|
self.assertEqual(header_lines[3][:4], 'UHL1')
|
|
self.assertEqual(header_lines[3][4:10], ' ' * 6 if batch_payment.bacs_multi_mode else batch_payment.bacs_processing_date.strftime(' %y%j'))
|
|
self.assertEqual(header_lines[3][10:20], company.bacs_sun.ljust(10))
|
|
self.assertEqual(header_lines[3][20:28], '0' * 8)
|
|
self.assertEqual(header_lines[3][28:37], '4 MULTI ' if batch_payment.bacs_multi_mode else '1 DAILY ')
|
|
self.assertEqual(header_lines[3][37:], ' ' * 43)
|
|
|
|
def verify_contra_line(self, company, payments, contra_line, is_multi, payment_method_code):
|
|
self.assertEqual(len(contra_line), 106 if is_multi else 100)
|
|
self.assertEqual(contra_line[:14], self.bank_journal.bank_account_id.sanitized_acc_number[8:])
|
|
self.assertEqual(contra_line[14:15], '0')
|
|
self.assertEqual(contra_line[15:17], '99' if payment_method_code == 'bacs_dd' else '17')
|
|
self.assertEqual(contra_line[17:31], self.bank_journal.bank_account_id.sanitized_acc_number[8:])
|
|
self.assertEqual(contra_line[31:35], ' ' * 4)
|
|
contra_amount = sum([int(payment.amount * 100) for payment in payments])
|
|
self.assertEqual(contra_line[35:46], str(contra_amount).rjust(11, '0'))
|
|
self.assertEqual(contra_line[64:82], 'CONTRA'.ljust(18))
|
|
self.assertEqual(contra_line[82:100], format_communication(company.name).ljust(18))
|
|
if is_multi:
|
|
self.assertEqual(contra_line[100:], payments[0].date.strftime(' %y%j'))
|
|
|
|
|
|
def verify_payment_line(self, company, payment, line, is_multi, payment_method_code):
|
|
self.assertEqual(len(line), 106 if is_multi else 100)
|
|
self.assertEqual(line[:14], payment.partner_bank_id.sanitized_acc_number[8:] if payment_method_code == 'bacs_dc' else payment.bacs_ddi_id.partner_bank_id.sanitized_acc_number[8:])
|
|
self.assertEqual(line[14:15], '0')
|
|
self.assertIn(line[15:17], ['01', '17', '18', '19'] if payment_method_code == 'bacs_dd' else ['99'])
|
|
self.assertEqual(line[17:31], self.bank_journal.bank_account_id.sanitized_acc_number[8:])
|
|
self.assertEqual(line[31:35], ' ' * 4)
|
|
self.assertEqual(line[35:46], str(int(payment.amount * 100)).rjust(11, '0'))
|
|
self.assertEqual(line[46:64], format_communication(company.name[:18]).ljust(18))
|
|
self.assertEqual(line[82:100], format_communication(payment.partner_id.name[:18]).ljust(18))
|
|
if is_multi:
|
|
self.assertEqual(line[100:], payment.date.strftime(' %y%j'))
|
|
|
|
def verify_bacs_file_payments(self, company, batch_payment, payment_lines):
|
|
if not batch_payment.bacs_multi_mode:
|
|
self.assertEqual(len(payment_lines), len(batch_payment.payment_ids) + 1)
|
|
for i, payment_line in enumerate(payment_lines):
|
|
if i == len(payment_lines) - 1:
|
|
self.verify_contra_line(company, batch_payment.payment_ids, payment_line, False, batch_payment.payment_method_id.code)
|
|
else:
|
|
self.verify_payment_line(company, batch_payment.payment_ids[i], payment_line, False, batch_payment.payment_method_id.code)
|
|
else:
|
|
# group payments by date and each date should have tranaction reccords of the date followed by a contra line
|
|
payments_by_date = itertools.groupby(batch_payment.payment_ids.sorted(key=lambda p: p.date), key=lambda p: p.date)
|
|
self.assertEqual(len(payment_lines), len(batch_payment.payment_ids) + len(list(payments_by_date)))
|
|
start = 0
|
|
for _, payments in payments_by_date:
|
|
payments = list(payments)
|
|
for i, payment in enumerate(payments):
|
|
self.verify_payment_line(company, payment, payment_lines[i + start], True, batch_payment.payment_method_id.code)
|
|
self.verify_contra_line(company, payments, payment_lines[len(payments) + start], True, batch_payment.payment_method_id.code)
|
|
start += len(payments) + 1
|
|
|
|
def verify_bacs_file_footers(self, company, batch_payment, footer_lines):
|
|
# test EOF1
|
|
self.assertEqual(len(footer_lines[0]), 80)
|
|
self.assertEqual(footer_lines[0][:5], 'EOF1A')
|
|
self.assertEqual(footer_lines[0][5:11], company.bacs_sun)
|
|
self.assertEqual(footer_lines[0][11:15], 'S 1')
|
|
self.assertEqual(footer_lines[0][15:21], company.bacs_sun)
|
|
self.assertEqual(footer_lines[0][21:27], batch_payment.bacs_submission_serial)
|
|
self.assertEqual(footer_lines[0][27:35], '00010001')
|
|
self.assertEqual(footer_lines[0][35:41], ' ' * 6)
|
|
self.assertEqual(footer_lines[0][41:47], batch_payment.date.strftime(' %y%j'))
|
|
self.assertEqual(footer_lines[0][47:53], batch_payment.bacs_expiry_date.strftime(' %y%j'))
|
|
self.assertEqual(footer_lines[0][53:], ' ' * 27)
|
|
# test EOF2
|
|
self.assertEqual(len(footer_lines[1]), 80)
|
|
self.assertEqual(footer_lines[1][:5], 'EOF2F')
|
|
self.assertEqual(footer_lines[1][5:10], '02000')
|
|
self.assertEqual(footer_lines[1][10:15], '00100')
|
|
self.assertEqual(footer_lines[1][15:], ' ' * 65)
|
|
# test UTL1
|
|
self.assertEqual(len(footer_lines[2]), 80)
|
|
self.assertEqual(footer_lines[2][:4], 'UTL1')
|
|
total = sum([int(payment.amount * 100) for payment in batch_payment.payment_ids])
|
|
self.assertEqual(footer_lines[2][4:17], str(total).rjust(13, '0'))
|
|
self.assertEqual(footer_lines[2][17:30], footer_lines[2][4:17])
|
|
transaction_record_count = str(len(batch_payment.payment_ids)).rjust(7, '0')
|
|
contra_record_count = str(len(set(payment.date for payment in batch_payment.payment_ids))).rjust(7, '0') if batch_payment.bacs_multi_mode else '0000001'
|
|
code = batch_payment.payment_method_id.code
|
|
self.assertEqual(footer_lines[2][30:37], transaction_record_count if code == 'bacs_dd' else contra_record_count)
|
|
self.assertEqual(footer_lines[2][37:44], contra_record_count if code == 'bacs_dd' else transaction_record_count)
|
|
self.assertEqual(footer_lines[2][44:], ' ' * 36)
|
|
|
|
def verify_bacs_file(self, company, batch):
|
|
binary_data = base64.b64decode(batch.export_file)
|
|
file_string = binary_data.decode('utf-8')
|
|
split_file = file_string.rstrip('\n').split('\n')
|
|
header_lines = split_file[:4]
|
|
payment_lines = split_file[4:-3]
|
|
footer_lines = split_file[-3:]
|
|
self.assertEqual(len(header_lines), 4, "There should be 4 header lines in the file")
|
|
self.assertEqual(len(footer_lines), 3, "There should be 3 footer lines in the file")
|
|
self.verify_bacs_file_headers(company, batch, header_lines)
|
|
self.verify_bacs_file_payments(company, batch, payment_lines)
|
|
self.verify_bacs_file_footers(company, batch, footer_lines)
|
|
|
|
def verify_multi_payments(self, company, partner, multi_mode, payment_method, payment_method_line, payment_type, partner_bank=None):
|
|
payment_1 = self.create_payment(partner, 42, payment_method_line, self.bank_journal, datetime.date.today() + datetime.timedelta(days=10), payment_type, partner_bank)
|
|
payment_1.action_post()
|
|
payment_2 = self.create_payment(partner, 1337, payment_method_line, self.bank_journal, datetime.date.today() + datetime.timedelta(days=15), payment_type, partner_bank)
|
|
payment_2.action_post()
|
|
payment_3 = self.create_payment(partner, 21, payment_method_line, self.bank_journal, datetime.date.today() + datetime.timedelta(days=20), payment_type, partner_bank)
|
|
payment_3.action_post()
|
|
payment_4 = self.create_payment(partner, 1916, payment_method_line, self.bank_journal, datetime.date.today() + datetime.timedelta(days=20), payment_type, partner_bank)
|
|
payment_4.action_post()
|
|
|
|
batch = self.env['account.batch.payment'].create({
|
|
'batch_type': payment_type,
|
|
'bacs_processing_date': fields.Date.today(),
|
|
'bacs_multi_mode': multi_mode,
|
|
'payment_ids': [(4, payment.id, None) for payment in [payment_1, payment_2, payment_3, payment_4]],
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_method_id': payment_method.id,
|
|
})
|
|
wizard_action = batch.validate_batch()
|
|
self.assertFalse(wizard_action, "Validation wizard should not have returned an action")
|
|
self.verify_bacs_file(company, batch)
|
|
|
|
def testBacsDirectDebit(self):
|
|
company = self.env.company
|
|
partner_boundagani = self.env['res.partner'].create({'name': 'Boundagani'})
|
|
partner_bank_boundagani = self.create_account('GB03BARC20031819726663', partner_boundagani, self.bank_hsbc)
|
|
ddi_boundagani = self.create_ddi(partner_boundagani, partner_bank_boundagani, self.env.company, self.bank_journal)
|
|
ddi_boundagani.action_validate_ddi()
|
|
invoice_boundagani = self.create_invoice(partner_boundagani)
|
|
self.pay_with_mandate(invoice_boundagani, ddi_boundagani)
|
|
payment_boundagani = invoice_boundagani.line_ids.mapped('matched_credit_ids.credit_move_id.payment_id')
|
|
self.assertEqual(invoice_boundagani.payment_state, self.env['account.move']._get_invoice_in_payment_state(), 'This invoice should have been paid thanks to the mandate')
|
|
self.assertEqual(invoice_boundagani.bacs_ddi_id, ddi_boundagani, 'The invoice should have the right mandate')
|
|
|
|
# test single payment
|
|
batch = self.env['account.batch.payment'].create({
|
|
'bacs_processing_date': fields.Date.today(),
|
|
'bacs_multi_mode': False,
|
|
'payment_ids': [(4, payment_boundagani.id, None)],
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_method_id': self.bacs_dd_method.id,
|
|
'batch_type': 'inbound',
|
|
})
|
|
wizard_action = batch.validate_batch()
|
|
self.assertFalse(wizard_action, "Validation wizard should not have returned an action")
|
|
self.verify_bacs_file(company, batch)
|
|
|
|
# test multi payment with single mode
|
|
self.verify_multi_payments(company, partner_boundagani, False, self.bacs_dd_method, self.bacs_dd, 'inbound')
|
|
|
|
# test multi payment with multi mode
|
|
self.verify_multi_payments(company, partner_boundagani, True, self.bacs_dd_method, self.bacs_dd, 'inbound')
|
|
|
|
def testBacsDirectCredit(self):
|
|
company = self.env.company
|
|
vendor_superlux = self.env['res.partner'].create({'name': 'Superlux'})
|
|
vendor_bank_superlux = self.create_account('GB70BARC20038066716256', vendor_superlux, self.bank_hsbc)
|
|
payment_superlux = self.create_payment(vendor_superlux, 42, self.bacs_dc, self.bank_journal, datetime.date.today() + datetime.timedelta(days=10), 'outbound', vendor_bank_superlux)
|
|
payment_superlux.action_post()
|
|
|
|
# test single payment
|
|
batch = self.env['account.batch.payment'].create({
|
|
'bacs_processing_date': fields.Date.today(),
|
|
'bacs_multi_mode': False,
|
|
'payment_ids': [(4, payment_superlux.id, None)],
|
|
'journal_id': self.bank_journal.id,
|
|
'payment_method_id': self.bacs_dc_method.id,
|
|
'batch_type': 'outbound',
|
|
})
|
|
wizard_action = batch.validate_batch()
|
|
self.assertFalse(wizard_action, "Validation wizard should not have returned an action")
|
|
self.verify_bacs_file(company, batch)
|
|
|
|
# test multi payment with single mode
|
|
self.verify_multi_payments(company, vendor_superlux, False, self.bacs_dc_method, self.bacs_dc, 'outbound', vendor_bank_superlux)
|
|
|
|
# test multi payment with multi mode
|
|
self.verify_multi_payments(company, vendor_superlux, True, self.bacs_dc_method, self.bacs_dc, 'outbound', vendor_bank_superlux)
|