bank-payment/account_payment_order/models/account_payment_order.py

507 lines
21 KiB
Python
Raw Normal View History

2021-03-23 19:28:51 +00:00
# © 2009 EduSense BV (<http://www.edusense.nl>)
# © 2011-2013 Therp BV (<https://therp.nl>)
# © 2016 Serv. Tecnol. Avanzados - Pedro M. Baeza
# © 2016 Akretion (Alexis de Lattre - alexis.delattre@akretion.com)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import base64
from flectra import api, fields, models, _
from flectra.exceptions import UserError, ValidationError
class AccountPaymentOrder(models.Model):
_name = 'account.payment.order'
_description = 'Payment Order'
_inherit = ['mail.thread']
_order = 'id desc'
def domain_journal_id(self):
if not self.payment_mode_id:
return [('id', '=', False)]
if self.payment_mode_id.bank_account_link == 'fixed':
return [('id', '=', self.payment_mode_id.fixed_journal_id.id)]
elif self.payment_mode_id.bank_account_link == 'variable':
jrl_ids = self.payment_mode_id.variable_journal_ids.ids
return [('id', 'in', jrl_ids)]
name = fields.Char(
string='Number', readonly=True, copy=False) # v8 field : name
payment_mode_id = fields.Many2one(
'account.payment.mode', 'Payment Mode', required=True,
ondelete='restrict', track_visibility='onchange',
readonly=True, states={'draft': [('readonly', False)]})
payment_type = fields.Selection([
('inbound', 'Inbound'),
('outbound', 'Outbound'),
], string='Payment Type', readonly=True, required=True)
payment_method_id = fields.Many2one(
'account.payment.method', related='payment_mode_id.payment_method_id',
readonly=True, store=True)
company_id = fields.Many2one(
related='payment_mode_id.company_id', store=True, readonly=True)
company_currency_id = fields.Many2one(
related='payment_mode_id.company_id.currency_id', store=True,
readonly=True)
bank_account_link = fields.Selection(
related='payment_mode_id.bank_account_link', readonly=True)
journal_id = fields.Many2one(
'account.journal', string='Bank Journal', ondelete='restrict',
readonly=True, states={'draft': [('readonly', False)]},
domain=domain_journal_id,
track_visibility='onchange')
# The journal_id field is only required at confirm step, to
# allow auto-creation of payment order from invoice
company_partner_bank_id = fields.Many2one(
related='journal_id.bank_account_id', string='Company Bank Account',
readonly=True)
state = fields.Selection(
[
('draft', 'Draft'),
('open', 'Confirmed'),
('generated', 'File Generated'),
('uploaded', 'File Uploaded'),
('done', 'Done'),
('cancel', 'Cancel'),
], string='Status', readonly=True, copy=False, default='draft',
track_visibility='onchange')
date_prefered = fields.Selection([
('now', 'Immediately'),
('due', 'Due Date'),
('fixed', 'Fixed Date'),
], string='Payment Execution Date Type', required=True, default='due',
track_visibility='onchange', readonly=True,
states={'draft': [('readonly', False)]})
2021-05-05 14:47:14 +00:00
ignore_past_date = fields.Boolean(
string='Ignore Past Date',
readonly=True,
states={'draft': [('readonly', False)]},
help='the payment date will not be set to today if enabled.',
)
2021-03-23 19:28:51 +00:00
date_scheduled = fields.Date(
string='Payment Execution Date', readonly=True,
states={'draft': [('readonly', False)]}, track_visibility='onchange',
help="Select a requested date of execution if you selected 'Due Date' "
"as the Payment Execution Date Type.")
date_generated = fields.Date(string='File Generation Date', readonly=True)
date_uploaded = fields.Date(string='File Upload Date', readonly=True)
date_done = fields.Date(string='Done Date', readonly=True)
generated_user_id = fields.Many2one(
'res.users', string='Generated by', readonly=True, ondelete='restrict',
copy=False)
payment_line_ids = fields.One2many(
'account.payment.line', 'order_id', string='Transaction Lines',
readonly=True, states={'draft': [('readonly', False)]})
# v8 field : line_ids
bank_line_ids = fields.One2many(
'bank.payment.line', 'order_id', string="Bank Payment Lines",
readonly=True,
help="The bank payment lines are used to generate the payment file. "
"They are automatically created from transaction lines upon "
"confirmation of the payment order: one bank payment line can "
"group several transaction lines if the option "
"'Group Transactions in Payment Orders' is active on the payment "
"mode.")
total_company_currency = fields.Monetary(
compute='_compute_total', store=True, readonly=True,
currency_field='company_currency_id')
bank_line_count = fields.Integer(
compute='_compute_bank_line_count', string='Number of Bank Lines',
readonly=True)
move_ids = fields.One2many(
'account.move', 'payment_order_id', string='Journal Entries',
readonly=True)
description = fields.Char()
@api.multi
def unlink(self):
for order in self:
if order.state == 'uploaded':
raise UserError(_(
"You cannot delete an uploaded payment order. You can "
"cancel it in order to do so."))
return super(AccountPaymentOrder, self).unlink()
@api.multi
@api.constrains('payment_type', 'payment_mode_id')
def payment_order_constraints(self):
for order in self:
if (
order.payment_mode_id.payment_type and
order.payment_mode_id.payment_type != order.payment_type):
raise ValidationError(_(
"The payment type (%s) is not the same as the payment "
"type of the payment mode (%s)") % (
order.payment_type,
order.payment_mode_id.payment_type))
@api.multi
@api.constrains('date_scheduled')
def check_date_scheduled(self):
today = fields.Date.context_today(self)
for order in self:
if order.date_scheduled:
if order.date_scheduled < today:
raise ValidationError(_(
"On payment order %s, the Payment Execution Date "
"is in the past (%s).")
% (order.name, order.date_scheduled))
@api.multi
@api.depends(
'payment_line_ids', 'payment_line_ids.amount_company_currency')
def _compute_total(self):
for rec in self:
rec.total_company_currency = sum(
rec.mapped('payment_line_ids.amount_company_currency') or
[0.0])
@api.multi
@api.depends('bank_line_ids')
def _compute_bank_line_count(self):
for order in self:
order.bank_line_count = len(order.bank_line_ids)
@api.model
def create(self, vals):
if vals.get('name', 'New') == 'New':
vals['name'] = self.env['ir.sequence'].next_by_code(
'account.payment.order') or 'New'
if vals.get('payment_mode_id'):
payment_mode = self.env['account.payment.mode'].browse(
vals['payment_mode_id'])
vals['payment_type'] = payment_mode.payment_type
if payment_mode.bank_account_link == 'fixed':
vals['journal_id'] = payment_mode.fixed_journal_id.id
if (
not vals.get('date_prefered') and
payment_mode.default_date_prefered):
vals['date_prefered'] = payment_mode.default_date_prefered
return super(AccountPaymentOrder, self).create(vals)
@api.onchange('payment_mode_id')
def payment_mode_id_change(self):
domain = self.domain_journal_id()
res = {'domain': {
'journal_id': domain
}}
journals = self.env['account.journal'].search(domain)
if len(journals) == 1:
self.journal_id = journals
if self.payment_mode_id.default_date_prefered:
self.date_prefered = self.payment_mode_id.default_date_prefered
return res
@api.multi
def action_done(self):
self.write({
'date_done': fields.Date.context_today(self),
'state': 'done',
})
return True
@api.multi
def action_done_cancel(self):
for move in self.move_ids:
move.button_cancel()
for move_line in move.line_ids:
move_line.remove_move_reconcile()
move.unlink()
self.action_cancel()
return True
@api.multi
def cancel2draft(self):
self.write({'state': 'draft'})
return True
@api.multi
def action_cancel(self):
for order in self:
order.write({'state': 'cancel'})
order.bank_line_ids.unlink()
return True
@api.model
def _prepare_bank_payment_line(self, paylines):
return {
'order_id': paylines[0].order_id.id,
'payment_line_ids': [(6, 0, paylines.ids)],
'communication': '-'.join(
[line.communication for line in paylines]),
}
@api.multi
def draft2open(self):
"""
Called when you click on the 'Confirm' button
Set the 'date' on payment line depending on the 'date_prefered'
setting of the payment.order
Re-generate the bank payment lines
"""
bplo = self.env['bank.payment.line']
today = fields.Date.context_today(self)
for order in self:
if not order.journal_id:
raise UserError(_(
'Missing Bank Journal on payment order %s.') % order.name)
if (
order.payment_method_id.bank_account_required and
not order.journal_id.bank_account_id):
raise UserError(_(
"Missing bank account on bank journal '%s'.")
% order.journal_id.display_name)
if not order.payment_line_ids:
raise UserError(_(
'There are no transactions on payment order %s.')
% order.name)
# Delete existing bank payment lines
order.bank_line_ids.unlink()
# Create the bank payment lines from the payment lines
group_paylines = {} # key = hashcode
for payline in order.payment_line_ids:
payline.draft2open_payment_line_check()
# Compute requested payment date
if order.date_prefered == 'due':
requested_date = payline.ml_maturity_date or today
elif order.date_prefered == 'fixed':
requested_date = order.date_scheduled or today
else:
requested_date = today
# No payment date in the past
2021-05-05 14:47:14 +00:00
if requested_date < today and not order.ignore_past_date:
2021-03-23 19:28:51 +00:00
requested_date = today
# inbound: check option no_debit_before_maturity
if (
order.payment_type == 'inbound' and
order.payment_mode_id.no_debit_before_maturity and
payline.ml_maturity_date and
requested_date < payline.ml_maturity_date):
raise UserError(_(
"The payment mode '%s' has the option "
"'Disallow Debit Before Maturity Date'. The "
"payment line %s has a maturity date %s "
"which is after the computed payment date %s.") % (
order.payment_mode_id.name,
payline.name,
payline.ml_maturity_date,
requested_date))
# Write requested_date on 'date' field of payment line
payline.date = requested_date
# Group options
if order.payment_mode_id.group_lines:
hashcode = payline.payment_line_hashcode()
else:
# Use line ID as hascode, which actually means no grouping
hashcode = payline.id
if hashcode in group_paylines:
group_paylines[hashcode]['paylines'] += payline
group_paylines[hashcode]['total'] +=\
payline.amount_currency
else:
group_paylines[hashcode] = {
'paylines': payline,
'total': payline.amount_currency,
}
# Create bank payment lines
for paydict in list(group_paylines.values()):
# Block if a bank payment line is <= 0
if paydict['total'] <= 0:
raise UserError(_(
"The amount for Partner '%s' is negative "
"or null (%.2f) !")
% (paydict['paylines'][0].partner_id.name,
paydict['total']))
vals = self._prepare_bank_payment_line(paydict['paylines'])
bplo.create(vals)
self.write({'state': 'open'})
return True
@api.multi
def generate_payment_file(self):
"""Returns (payment file as string, filename)"""
self.ensure_one()
if self.payment_method_id.code == 'manual':
return (False, False)
else:
raise UserError(_(
"No handler for this payment method. Maybe you haven't "
"installed the related Flectra module."))
@api.multi
def open2generated(self):
self.ensure_one()
payment_file_str, filename = self.generate_payment_file()
action = {}
if payment_file_str and filename:
attachment = self.env['ir.attachment'].create({
'res_model': 'account.payment.order',
'res_id': self.id,
'name': filename,
'datas': base64.b64encode(payment_file_str),
'datas_fname': filename,
})
simplified_form_view = self.env.ref(
'account_payment_order.view_attachment_simplified_form')
action = {
'name': _('Payment File'),
'view_mode': 'form',
'view_id': simplified_form_view.id,
'res_model': 'ir.attachment',
'type': 'ir.actions.act_window',
'target': 'current',
'res_id': attachment.id,
}
self.write({
'date_generated': fields.Date.context_today(self),
'state': 'generated',
'generated_user_id': self._uid,
})
return action
@api.multi
def generated2uploaded(self):
for order in self:
if order.payment_mode_id.generate_move:
order.generate_move()
self.write({
'state': 'uploaded',
'date_uploaded': fields.Date.context_today(self),
})
return True
@api.multi
def _prepare_move(self, bank_lines=None):
if self.payment_type == 'outbound':
ref = _('Payment order %s') % self.name
else:
ref = _('Debit order %s') % self.name
if bank_lines and len(bank_lines) == 1:
ref += " - " + bank_lines.name
if self.payment_mode_id.offsetting_account == 'bank_account':
journal_id = self.journal_id.id
elif self.payment_mode_id.offsetting_account == 'transfer_account':
journal_id = self.payment_mode_id.transfer_journal_id.id
vals = {
'journal_id': journal_id,
'ref': ref,
'payment_order_id': self.id,
'line_ids': [],
}
return vals
@api.multi
def _prepare_move_line_offsetting_account(
self, amount_company_currency, amount_payment_currency,
bank_lines):
vals = {}
if self.payment_type == 'outbound':
name = _('Payment order %s') % self.name
else:
name = _('Debit order %s') % self.name
if self.payment_mode_id.offsetting_account == 'bank_account':
vals.update({'date': bank_lines[0].date})
else:
vals.update({'date_maturity': bank_lines[0].date})
if self.payment_mode_id.offsetting_account == 'bank_account':
account_id = self.journal_id.default_debit_account_id.id
elif self.payment_mode_id.offsetting_account == 'transfer_account':
account_id = self.payment_mode_id.transfer_account_id.id
partner_id = False
for index, bank_line in enumerate(bank_lines):
if index == 0:
partner_id = bank_line.payment_line_ids[0].partner_id.id
elif bank_line.payment_line_ids[0].partner_id.id != partner_id:
# we have different partners in the grouped move
partner_id = False
break
vals.update({
'name': name,
'partner_id': partner_id,
'account_id': account_id,
'credit': (self.payment_type == 'outbound' and
amount_company_currency or 0.0),
'debit': (self.payment_type == 'inbound' and
amount_company_currency or 0.0),
})
if bank_lines[0].currency_id != bank_lines[0].company_currency_id:
sign = self.payment_type == 'outbound' and -1 or 1
vals.update({
'currency_id': bank_lines[0].currency_id.id,
'amount_currency': amount_payment_currency * sign,
})
return vals
@api.multi
def _prepare_move_line_partner_account(self, bank_line):
if bank_line.payment_line_ids[0].move_line_id:
account_id =\
bank_line.payment_line_ids[0].move_line_id.account_id.id
else:
if self.payment_type == 'inbound':
account_id =\
bank_line.partner_id.property_account_receivable_id.id
else:
account_id =\
bank_line.partner_id.property_account_payable_id.id
if self.payment_type == 'outbound':
name = _('Payment bank line %s') % bank_line.name
else:
name = _('Debit bank line %s') % bank_line.name
vals = {
'name': name,
'bank_payment_line_id': bank_line.id,
'partner_id': bank_line.partner_id.id,
'account_id': account_id,
'credit': (self.payment_type == 'inbound' and
bank_line.amount_company_currency or 0.0),
'debit': (self.payment_type == 'outbound' and
bank_line.amount_company_currency or 0.0),
}
if bank_line.currency_id != bank_line.company_currency_id:
sign = self.payment_type == 'inbound' and -1 or 1
vals.update({
'currency_id': bank_line.currency_id.id,
'amount_currency': bank_line.amount_currency * sign,
})
return vals
@api.multi
def generate_move(self):
"""
Create the moves that pay off the move lines from
the payment/debit order.
"""
self.ensure_one()
am_obj = self.env['account.move']
post_move = self.payment_mode_id.post_move
# prepare a dict "trfmoves" that can be used when
# self.payment_mode_id.move_option = date or line
# key = unique identifier (date or True or line.id)
# value = bank_pay_lines (recordset that can have several entries)
trfmoves = {}
for bline in self.bank_line_ids:
hashcode = bline.move_line_offsetting_account_hashcode()
if hashcode in trfmoves:
trfmoves[hashcode] += bline
else:
trfmoves[hashcode] = bline
for hashcode, blines in trfmoves.items():
mvals = self._prepare_move(blines)
total_company_currency = total_payment_currency = 0
for bline in blines:
total_company_currency += bline.amount_company_currency
total_payment_currency += bline.amount_currency
partner_ml_vals = self._prepare_move_line_partner_account(
bline)
mvals['line_ids'].append((0, 0, partner_ml_vals))
trf_ml_vals = self._prepare_move_line_offsetting_account(
total_company_currency, total_payment_currency, blines)
mvals['line_ids'].append((0, 0, trf_ml_vals))
move = am_obj.create(mvals)
blines.reconcile_payment_lines()
if post_move:
move.post()