forked from Mapan/odoo17e
501 lines
25 KiB
Python
501 lines
25 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import base64
|
|
import csv
|
|
import io
|
|
from datetime import datetime
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools import float_repr, float_round
|
|
from odoo.tools.misc import format_date
|
|
|
|
|
|
class AccountBatchPayment(models.Model):
|
|
_inherit = 'account.batch.payment'
|
|
|
|
# ------------------
|
|
# Fields declaration
|
|
# ------------------
|
|
|
|
l10n_nz_file_format = fields.Selection(
|
|
string='EFT file format',
|
|
selection=[
|
|
('anz', 'ANZ'),
|
|
('bnz', 'BNZ'),
|
|
('asb', 'ASB'),
|
|
('westpac', 'DeskBank'),
|
|
],
|
|
)
|
|
l10n_nz_batch_reference = fields.Char(
|
|
string='Payments Reference',
|
|
help='If sent in bulk, this will be used instead of the payments references.'
|
|
)
|
|
l10n_nz_batch_code = fields.Char(
|
|
string='Analysis Code',
|
|
help='If sent in bulk, this will be used instead of the payments code.'
|
|
)
|
|
l10n_nz_batch_particulars = fields.Char(
|
|
string='Payments Particulars',
|
|
help='If sent in bulk, this will be used instead of the payments particulars.'
|
|
)
|
|
l10n_nz_dd_info = fields.Char(
|
|
string='Direct Debit Information',
|
|
help='For BNZ it should be the Direct Debit Authority\n'
|
|
'For ASB it should be the Registration ID assigned by ASB MSL'
|
|
)
|
|
l10n_nz_company_partner_id = fields.Many2one(related='journal_id.company_id.partner_id')
|
|
l10n_nz_dishonour_account_id = fields.Many2one(
|
|
comodel_name='res.partner.bank',
|
|
string="Dishonour Account",
|
|
help="Used by ANZ as a fallback account in case of dishonored payment.",
|
|
tracking=True,
|
|
domain="[('partner_id','=', l10n_nz_company_partner_id)]",
|
|
check_company=True,
|
|
ondelete='restrict',
|
|
)
|
|
|
|
# ----------------
|
|
# Business methods
|
|
# ----------------
|
|
|
|
def _get_methods_generating_files(self):
|
|
# EXTENDS account_batch_payment
|
|
res = super()._get_methods_generating_files()
|
|
res.extend(['l10n_nz_eft_in', 'l10n_nz_eft_out'])
|
|
return res
|
|
|
|
@api.constrains('batch_type', 'journal_id', 'payment_ids')
|
|
def _check_payments_constrains(self):
|
|
# EXTENDS account_batch_payment
|
|
super()._check_payments_constrains()
|
|
|
|
# We store the payment listing indicator on the payment method line, in order to allow more flexibility.
|
|
# For example, the user could set multiple payment method (EFT bulk, EFT Individual,...) and use them to control
|
|
# How payments are made. This means that this indicator must be the same for the whole bulk to avoid needing
|
|
# to generate multiple files.
|
|
for record in self.filtered(lambda batch: batch.payment_method_code in {'l10n_nz_eft_in', 'l10n_nz_eft_out'}):
|
|
if record.payment_ids and len(record.payment_ids.payment_method_line_id.mapped('l10n_nz_payment_listing_indicator')) != 1:
|
|
raise UserError(_("All payments batched using the New Zealand EFT format must have the same payment listing indicator."))
|
|
|
|
def validate_batch(self):
|
|
""" Verifies the content of a batch and proceeds to its sending if possible.
|
|
If not, opens a wizard listing the errors and/or warnings encountered.
|
|
"""
|
|
# EXTENDS account_batch_payment
|
|
self.ensure_one()
|
|
errors = []
|
|
if self.payment_method_code in {'l10n_nz_eft_in', 'l10n_nz_eft_out'}:
|
|
if not self.l10n_nz_file_format:
|
|
errors.append(_("Please specify a file format before validation."))
|
|
# ANZ does not use this field.
|
|
if self.payment_method_code == 'l10n_nz_eft_in' and self.l10n_nz_file_format in {'bnz', 'asb', 'anz'} and not self.l10n_nz_dd_info:
|
|
errors.append(_("Please specify the Direct Debit Information."))
|
|
if self.l10n_nz_file_format == 'anz' and not self.l10n_nz_dishonour_account_id:
|
|
errors.append(_("For ANZ, you need to specify the Dishonour Account."))
|
|
if not self.journal_id.bank_account_id.acc_number:
|
|
errors.append(_("The bank account of journal %(journal_name)s is missing or the account number is missing.",
|
|
journal_name=self.journal_id.display_name))
|
|
|
|
if errors:
|
|
raise UserError('\n'.join(errors))
|
|
|
|
return super().validate_batch()
|
|
|
|
@api.model
|
|
def _get_data_export_method(self):
|
|
""" Returns a dict which define which method should be used to export which format.
|
|
Additionally, it also defines if we need to export as csv or text for each format.
|
|
"""
|
|
return {
|
|
'anz': (self._export_anz_eft_data, 'csv'),
|
|
'bnz': (self._export_bnz_eft_data, 'txt'),
|
|
'asb': (self._export_asb_eft_data, 'csv'),
|
|
'westpac': (self._export_westpac_eft_data, 'csv'),
|
|
}
|
|
|
|
def _generate_export_file(self):
|
|
# EXTENDS account_batch_payment
|
|
self.ensure_one()
|
|
if self.payment_method_code not in {'l10n_nz_eft_in', 'l10n_nz_eft_out'}:
|
|
return super()._generate_export_file()
|
|
|
|
# Easier than many ifs, and also provides better support for potential extension/customization.
|
|
data_export_function, file_type = self._get_data_export_method().get(self.l10n_nz_file_format, (None, None))
|
|
|
|
if not data_export_function:
|
|
raise UserError(_("The selected file format is not supported."))
|
|
|
|
# Export the values
|
|
header, transactions, control = data_export_function()
|
|
|
|
# Then write them in a csv or an ASCII txt file (csv is preferred unless not supported)
|
|
if file_type == 'csv':
|
|
output = io.StringIO(newline='') # https://docs.python.org/3/library/csv.html#id4
|
|
writer = csv.writer(output)
|
|
|
|
if header:
|
|
writer.writerow(header)
|
|
|
|
for transaction in transactions:
|
|
writer.writerow(transaction)
|
|
|
|
if control:
|
|
writer.writerow(control)
|
|
file = base64.encodebytes(output.getvalue().encode())
|
|
else:
|
|
# Note: Only used by BNZ. The doc demands \r\n at the end of each record.
|
|
file_bytes = b''
|
|
|
|
if header:
|
|
file_bytes += (','.join(header) + '\r\n').encode('ascii')
|
|
|
|
for transaction in transactions:
|
|
file_bytes += (','.join(transaction) + '\r\n').encode('ascii')
|
|
|
|
if control:
|
|
file_bytes += (','.join(control) + '\r\n').encode('ascii')
|
|
file = base64.encodebytes(file_bytes)
|
|
|
|
file_date = fields.Datetime.context_timestamp(self, datetime.now()).strftime("%Y%m%d%H%M")
|
|
return {
|
|
'filename': f"EFT-{self.journal_id.code}-{file_date}.{file_type}",
|
|
'file': file,
|
|
}
|
|
|
|
# ----------------
|
|
# Business methods
|
|
# ----------------
|
|
|
|
# -- ANZ format - CSV with Control Record -- #
|
|
|
|
def _export_anz_eft_data(self):
|
|
"""
|
|
Exports the data of the batch payment in the format expected by the ANZ bank.
|
|
The data returned is used to be written in a CSV file.
|
|
:return: CSV-like data for the header row, transactions rows, and control row.
|
|
"""
|
|
is_inbound = self.batch_type == 'inbound'
|
|
is_bulk = not self.payment_ids.payment_method_line_id[0].l10n_nz_payment_listing_indicator
|
|
|
|
header = [
|
|
'1', # Record type, 1 for header
|
|
'D' if is_inbound else 'C', # Batch type
|
|
format_date(self.env, self.date, date_format='YYYYMMdd'), # Payment Date
|
|
'', # Payment Time - Unsupported in odoo
|
|
format_date(self.env, fields.Date.context_today(self), date_format='YYYYMMdd'), # Batch creation date
|
|
self.journal_id.bank_account_id.acc_number.replace('-', '').replace(' ', ''), # Funds Account
|
|
self._get_string_within_limit(self.l10n_nz_dd_info, 7) if is_inbound else '', # DD Code
|
|
'S' if is_bulk else 'M', # Reporting Method
|
|
self.l10n_nz_dishonour_account_id.acc_number.replace('-', '').replace(' ', ''), # Dishonour Account
|
|
self._get_string_within_limit(self.name, 12), # Batch name
|
|
self._get_string_within_limit(self.l10n_nz_batch_particulars, 12), # Originator Particulars
|
|
self._get_string_within_limit(self.l10n_nz_batch_code, 12), # Originator Analysis Code
|
|
self._get_string_within_limit(self.l10n_nz_batch_reference, 12, from_left=False), # Originator Reference
|
|
'', # Spare
|
|
'', # Spare
|
|
'', # Spare
|
|
]
|
|
|
|
transactions = []
|
|
for payment in self.payment_ids.sorted(reverse=True):
|
|
payment_parties_info = self._get_payment_parties_info(payment)
|
|
transactions.append([
|
|
'2', # Record type, 2 for transaction record
|
|
payment_parties_info['other_party']['account_number'], # Other party's bank account
|
|
'00' if is_inbound else '50', # Transaction code, 50 is credit, 00 is debit. 52 can be used for payroll.
|
|
self._get_amount_in_cents_string(payment.amount), # Transaction amount, in cents.
|
|
self._get_string_within_limit(payment_parties_info['other_party']['name'], 32), # Other party's name
|
|
payment_parties_info['other_party']['particular'], # Other party's Particulars
|
|
payment_parties_info['other_party']['code'], # Other party's code
|
|
payment_parties_info['other_party']['ref'], # Other party's Reference
|
|
# These three fields are only applicable if Reporting Method is M. Can be left empty to use the batch one.
|
|
payment_parties_info['company_party']['particular'], # Originator's Particulars.
|
|
payment_parties_info['company_party']['code'], # Originator's Analysis Code
|
|
payment_parties_info['company_party']['ref'], # Originator's Reference.
|
|
'', # reserved
|
|
'', # reserved
|
|
'', # reserved
|
|
])
|
|
|
|
control = [
|
|
'3', # Record type, 3 for Control Record
|
|
self._get_amount_in_cents_string(sum(self.payment_ids.mapped('amount'))) if is_inbound else '', # Total amount, in cents, for debits.
|
|
self._get_amount_in_cents_string(sum(self.payment_ids.mapped('amount'))) if not is_inbound else '', # Total amount, in cents, for credits.
|
|
str(len(self.payment_ids)), # Transaction record count
|
|
self._generate_control_hash(), # Control hash
|
|
'', # Spare
|
|
'', # Spare
|
|
'', # Spare
|
|
]
|
|
|
|
return header, transactions, control
|
|
|
|
# -- BNZ format - ASCII TXT file -- #
|
|
|
|
def _export_bnz_eft_data(self):
|
|
"""
|
|
Exports the data of the batch payment in the format expected by the ANZ bank.
|
|
The data returned is used to be written in a CSV file.
|
|
|
|
The Bulk or Individual Listing Indicator here is the exact one from the field, so check the
|
|
description to see the differences.
|
|
|
|
:return: CSV-like data for the header row, transactions rows, and control row.
|
|
"""
|
|
is_inbound = self.batch_type == 'inbound'
|
|
|
|
header = [
|
|
'1', # Record type, 1 for header
|
|
self.l10n_nz_dd_info if is_inbound else '', # Direct Debit Authority Number
|
|
'', # Spare
|
|
'', # Spare
|
|
self.journal_id.bank_account_id.acc_number.replace('-', '').replace(' ', ''), # Bank number / Credit card number.
|
|
'6' if is_inbound else '7', # File type. 6 for direct debit, 7 for direct credit
|
|
format_date(self.env, self.date, date_format='YYMMdd'), # Due date
|
|
format_date(self.env, fields.Date.context_today(self), date_format='YYMMdd'), # File creation date. This date cannot be less than today's date.
|
|
# As this value should be the same for the whole batch, we can take any payment method lines.
|
|
self.payment_ids.payment_method_line_id[0].l10n_nz_payment_listing_indicator or '', # Bulk or Individual Listing Indicator
|
|
]
|
|
|
|
transactions = []
|
|
for payment in self.payment_ids.sorted(reverse=True):
|
|
payment_parties_info = self._get_payment_parties_info(payment)
|
|
transactions.append([
|
|
'2', # Record type, 2 for transaction record
|
|
payment_parties_info['other_party']['account_number'], # Other party's bank account
|
|
'00' if is_inbound else '50', # Transaction code, 50 is credit, 00 is debit. 52 can be used for payroll.
|
|
self._get_amount_in_cents_string(payment.amount), # Transaction amount, in cents.
|
|
self._get_string_within_limit(payment_parties_info['other_party']['name'], 20), # Other party's name
|
|
payment_parties_info['other_party']['ref'], # Other party's reference
|
|
payment_parties_info['other_party']['code'], # Other party's code
|
|
'', # Other Party Alpha Reference - Spare
|
|
payment_parties_info['other_party']['particular'], # Other party's particular
|
|
self._get_string_within_limit(payment_parties_info['company_party']['name'], 20), # Company's name
|
|
payment_parties_info['company_party']['code'], # Company's code
|
|
payment_parties_info['company_party']['ref'], # Company's reference
|
|
payment_parties_info['company_party']['particular'], # Company's particular
|
|
])
|
|
|
|
control = [
|
|
'3', # Record type, 3 for Control Record
|
|
self._get_amount_in_cents_string(sum(self.payment_ids.mapped('amount'))), # Total amount
|
|
str(len(self.payment_ids)), # Transaction record count
|
|
self._generate_control_hash(), # Control hash
|
|
]
|
|
|
|
return header, transactions, control
|
|
|
|
# -- ASB format -- #
|
|
|
|
def _export_asb_eft_data(self):
|
|
is_inbound = self.batch_type == 'inbound'
|
|
header = None
|
|
|
|
# We split dd and dc because the format of the transactions are different.
|
|
transactions = []
|
|
if is_inbound:
|
|
header = [
|
|
20, # Direct debit identifier
|
|
self._get_string_within_limit(self.l10n_nz_dd_info.replace('-', ''), 15), # Registration ID assigned by ASB MSL, without hyphens.
|
|
format_date(self.env, self.date, date_format='YYYYMMdd'), # Due date
|
|
'', # Client short name, optional and not displayed.
|
|
self._generate_control_hash(), # Check
|
|
float_repr(sum(self.payment_ids.mapped('amount')), precision_digits=2), # Total amount, including decimals.
|
|
str(len(self.payment_ids)), # Transaction record count
|
|
]
|
|
|
|
for payment in self.payment_ids.sorted(reverse=True):
|
|
payment_parties_info = self._get_payment_parties_info(payment)
|
|
transactions.append([
|
|
payment_parties_info['other_party']['account_number'], # Deduction Account
|
|
'000', # Transaction code, 000 is debit
|
|
float_repr(payment.amount, precision_digits=2), # Transaction amount
|
|
self._get_string_within_limit(payment_parties_info['other_party']['name'], 20), # Payer name
|
|
'', # Party num ref. Should be left empty or filled with 0.
|
|
payment_parties_info['other_party']['code'], # Payer Code
|
|
payment_parties_info['other_party']['ref'], # Payer ref
|
|
payment_parties_info['other_party']['particular'], # Payer particular
|
|
'', # Other party name, optional and not displayed.
|
|
payment_parties_info['company_party']['code'], # Payee Code
|
|
payment_parties_info['company_party']['ref'], # Payee reference
|
|
payment_parties_info['company_party']['particular'], # Payee Particulars
|
|
])
|
|
else:
|
|
for payment in self.payment_ids.sorted(reverse=True):
|
|
payment_parties_info = self._get_payment_parties_info(payment)
|
|
transactions.append([
|
|
self._get_string_within_limit(self.name, 20), # Payment name, must be the same for all payments in a single batch.
|
|
format_date(self.env, self.date, date_format='YYYY/MM/dd'), # Due date. The use of / is recommended
|
|
payment_parties_info['company_party']['account_number'], # Deduction Account
|
|
float_repr(payment.amount, precision_digits=2), # Transaction amount.
|
|
payment_parties_info['other_party']['particular'], # Payee Particulars
|
|
payment_parties_info['other_party']['code'], # Payee Code
|
|
payment_parties_info['other_party']['ref'], # Payee Reference
|
|
payment_parties_info['other_party']['account_number'], # Destination account
|
|
payment_parties_info['company_party']['particular'], # Payer Particulars
|
|
payment_parties_info['company_party']['code'], # Payer Code
|
|
payment_parties_info['company_party']['ref'], # Payer reference
|
|
self._get_string_within_limit(payment_parties_info['other_party']['name'], 32), # Payee name
|
|
])
|
|
|
|
return header, transactions, None
|
|
|
|
# -- WESTPAC format -- #
|
|
|
|
def _export_westpac_eft_data(self):
|
|
company_account_details = self._get_eft_account_details(self.journal_id.bank_account_id)
|
|
is_inbound = self.batch_type == 'inbound'
|
|
|
|
header = [
|
|
'A', # Record type, A for header
|
|
'1', # Sequence number
|
|
company_account_details[0], # Originating Bank. Technically should always be 03 (Westpac).
|
|
company_account_details[1], # Originating Branch
|
|
self._get_string_within_limit(self.journal_id.bank_account_id.acc_holder_name, 30), # Customer Name
|
|
'', # Customer Number - unused
|
|
self._get_string_within_limit(self.l10n_nz_batch_reference, 20), # Description
|
|
format_date(self.env, self.date, date_format='ddMMYY'), # Batch due date.
|
|
'', # Spare - unused
|
|
]
|
|
|
|
transactions = []
|
|
for sequence, payment in enumerate(self.payment_ids.sorted(reverse=True), start=2):
|
|
payment_parties_info = self._get_payment_parties_info(payment)
|
|
transactions.append([
|
|
'D', # Record type, D for transaction record
|
|
str(sequence), # Other party's bank account
|
|
# Payee/Payer Account
|
|
*self._get_eft_account_details(payment_parties_info['other_party']['account']), # Bank number, Branch number, Account number, Account suffix
|
|
'00' if is_inbound else '50', # Transaction code. 00 for Direct Debit, 50 For payments. 52 for payroll.
|
|
'DD' if is_inbound else 'DC', # MTS Source.
|
|
self._get_amount_in_cents_string(payment.amount), # Transaction amount, in cents.
|
|
self._get_string_within_limit(payment_parties_info['other_party']['name'], 20), # Payee/Payer Name
|
|
payment_parties_info['other_party']['particular'], # Payee/Payer Particulars
|
|
payment_parties_info['other_party']['code'], # Payee/Payer Analysis Code
|
|
payment_parties_info['other_party']['ref'], # Payee/Payer Reference
|
|
# Payer/Payee Account
|
|
*company_account_details, # Bank number, Branch number, Account number, Account suffix
|
|
self._get_string_within_limit(payment_parties_info['company_party']['name'], 20), # Payer/Payee Name
|
|
'', # Spare - Unused
|
|
])
|
|
|
|
return header, transactions, None
|
|
|
|
# -- Helpers -- #
|
|
|
|
@api.model
|
|
def _get_eft_account_details(self, account):
|
|
""" Split the given account into its details (bank code, branch code, account number, suffix)
|
|
Different banks may have a slightly different format, but it should be easy to parse based on the length.
|
|
|
|
The 16 digit format is the standard. Banks using only 2 digits for the suffix usually prefix that by a 0
|
|
BB-bbbb-AAAAAAA-0SS but this 0 is usually optional.
|
|
|
|
BNZ claims supporting 19 digits accounts, but I couldn't find any information on that format.
|
|
"""
|
|
sanitized_number = account.acc_number.replace('-', '').replace(' ', '')
|
|
|
|
if len(sanitized_number) == 15:
|
|
return sanitized_number[:2], sanitized_number[2:6], sanitized_number[6:13], sanitized_number[13:15]
|
|
elif len(sanitized_number) == 16:
|
|
return sanitized_number[:2], sanitized_number[2:6], sanitized_number[6:13], sanitized_number[13:16]
|
|
elif len(sanitized_number) == 17:
|
|
return sanitized_number[:2], sanitized_number[2:6], sanitized_number[6:14], sanitized_number[14:17]
|
|
else:
|
|
raise UserError(_(
|
|
'The format of the account "%s" is not recognized.\n'
|
|
'The supported formats are:\n'
|
|
'BBbbbbAAAAAAASS\n'
|
|
'BBbbbbAAAAAAASSS\n'
|
|
'BBbbbbAAAAAAAASSS', account.acc_number
|
|
))
|
|
|
|
@api.model
|
|
def _get_string_within_limit(self, string, max_length, from_left=True):
|
|
"""
|
|
This simple helper takes in a string and a max length, and returns the string formatted to fit the maximum length.
|
|
Supports removing the extra characters from the beginning or the end of the string depending on what is required.
|
|
|
|
:param string: The string to format.
|
|
:param max_length: The maximum length of the string as allowed by the format.
|
|
:param from_left: if true extra characters are removed from the end, otherwise from the start.
|
|
:return: The formatted string.
|
|
"""
|
|
if not string:
|
|
return ''
|
|
|
|
string = string.strip()
|
|
if from_left:
|
|
string = string[:max_length]
|
|
else:
|
|
string = string[-max_length:]
|
|
# And once more at the end to avoid keeping any that could have been added by the previous operation.
|
|
return string.strip()
|
|
|
|
def _get_payment_parties_info(self, payment):
|
|
"""
|
|
Depending on the context (inbound, outbound, bulk, ...) we need to get the payment information from different
|
|
places. This helper will help with that by returning a dict with the information of both the company and the
|
|
other party.
|
|
"""
|
|
self.ensure_one()
|
|
is_inbound = self.batch_type == 'inbound'
|
|
|
|
if is_inbound:
|
|
company_particular = payment.l10n_nz_payee_particulars
|
|
other_particular = payment.l10n_nz_payer_particulars
|
|
company_code = payment.l10n_nz_payee_code
|
|
other_code = payment.l10n_nz_payer_code
|
|
other_party_account = payment.l10n_nz_dd_account_id
|
|
else:
|
|
company_particular = payment.l10n_nz_payer_particulars
|
|
other_particular = payment.l10n_nz_payee_particulars
|
|
company_code = payment.l10n_nz_payer_code
|
|
other_code = payment.l10n_nz_payee_code
|
|
other_party_account = payment.partner_bank_id
|
|
|
|
# Particulars, codes and refs are always using the standard format, so we can format here right away.
|
|
return {
|
|
'company_party': {
|
|
'name': self.journal_id.bank_account_id.acc_holder_name,
|
|
'particular': self._get_string_within_limit(company_particular, 12),
|
|
'code': self._get_string_within_limit(company_code, 12),
|
|
'ref': self._get_string_within_limit(payment.ref, 12, from_left=False),
|
|
'account': self.journal_id.bank_account_id,
|
|
'account_number': self.journal_id.bank_account_id.acc_number.replace('-', '').replace(' ', ''),
|
|
},
|
|
'other_party': {
|
|
'name': other_party_account.acc_holder_name,
|
|
'particular': self._get_string_within_limit(other_particular, 12),
|
|
'code': self._get_string_within_limit(other_code, 12),
|
|
'ref': self._get_string_within_limit(payment.ref, 12, from_left=False),
|
|
'account': other_party_account,
|
|
'account_number': other_party_account.acc_number.replace('-', '').replace(' ', ''),
|
|
},
|
|
}
|
|
|
|
def _generate_control_hash(self):
|
|
"""
|
|
Small helper to generate the control hash of a batch, using the MT9 algorithm, based on the payments inside it.
|
|
|
|
The hash total is calculated using the branch and account numbers in each transaction
|
|
record. The bank number and account suffix are not used when calculating the hash total. If
|
|
the account number is 8 digits then the left most digit is excluded from the calculations.
|
|
|
|
If the hash total is more than 11 characters, exclude the characters on the left.
|
|
"""
|
|
self.ensure_one()
|
|
control_hash = 0
|
|
for payment in self.payment_ids.sorted(reverse=True):
|
|
payment_parties_info = self._get_payment_parties_info(payment)
|
|
payment_account_details = self._get_eft_account_details(payment_parties_info['other_party']['account'])
|
|
# Branch code + account number, as int, are summed up for the control
|
|
control_hash += int(payment_account_details[1] + payment_account_details[2][-7:])
|
|
return str(control_hash)[-11:].zfill(11)
|
|
|
|
@api.model
|
|
def _get_amount_in_cents_string(self, amount):
|
|
""" Format the given amount to return the same amount in cents, as a string. """
|
|
return str(int(float_round(amount * 100, precision_digits=0)))
|