trytonpsk-account_voucher/invoice.py
2020-12-17 17:55:25 -05:00

580 lines
20 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from datetime import date
from decimal import Decimal
from trytond.model import ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.wizard import Wizard, StateTransition, StateView, Button
from trytond.transaction import Transaction
from trytond.pyson import Eval
__all__ = [
'Invoice', 'AdvancePaymentAsk', 'AdvancePayment', 'CrossPaymentAsk',
'CrossPayment'
]
_ZERO = Decimal('0.0')
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
@classmethod
def post(cls, invoices):
super(Invoice, cls).post(invoices)
for invoice in invoices:
for line in invoice.lines:
if line.origin and hasattr(line.origin, 'sale'):
if line.origin.sale.vouchers:
invoice.create_move_advance(line.origin.sale.vouchers)
break
def create_move_advance(self, vouchers):
pool = Pool()
Note = pool.get('account.note')
Invoice = pool.get('account.invoice')
MoveLine = pool.get('account.move.line')
Config = pool.get('account.voucher_configuration')
config = Config.get_configuration()
for voucher in vouchers:
if self.invoice.type == 'in':
kind = 'payable'
else:
kind = 'receivable'
if voucher.state in ['posted'] and voucher.move:
move_lines = [
line.id for line in voucher.move.lines
if l.account.kind == kind
]
lines_to_create = []
accounts_to_reconcile = []
reconcile_invoice = []
sum_debit = 0
sum_credit = 0
for line in move_lines:
lines_to_create.append({
'debit': line.credit,
'credit': line.debit,
'party': line.party.id,
'account': line.account.id,
'description': line.description,
})
sum_debit += line.debit
sum_credit += line.credit
accounts_to_reconcile.append(line.account.id)
total_advance = abs(sum_debit - sum_credit)
if total_advance == self.amount_to_pay:
for mline in self.move.lines:
if mline.account.id == self.account.id:
reconcile_invoice.append(mline)
for pl in self.payment_lines:
if not pl.reconciliation:
reconcile_invoice.append(pl)
lines_to_create.append({
'debit': sum_debit,
'credit': sum_credit,
'party': self.party.id,
'account': self.account.id,
'description': self.description,
})
note, = Note.create([{
'description': '',
'journal': config.default_journal_note.id,
'date': date.today(),
'state': 'draft',
'lines': [('create', lines_to_create)],
}])
Note.post([note])
lines_to_reconcile = list(move_lines)
payment_lines = []
for nm_line in note.move.lines:
if nm_line.account.id == self.account.id:
payment_lines.append(nm_line)
if total_advance == self.amount_to_pay:
reconcile_invoice.append(nm_line)
if nm_line.account.id in accounts_to_reconcile:
lines_to_reconcile.append(nm_line)
Invoice.write([self], {
'payment_lines': [('add', payment_lines)],
})
if lines_to_reconcile:
MoveLine.reconcile(lines_to_reconcile)
pending_to_pay = sum([ri.debit - ri.credit for ri in reconcile_invoice])
if reconcile_invoice and not pending_to_pay:
for x in reconcile_invoice:
MoveLine.reconcile(reconcile_invoice)
@classmethod
def __setup__(cls):
super(Invoice, cls).__setup__()
cls._buttons.update({
'pay_with_voucher': {
'invisible': Eval('state') != 'posted',
'depends': ['state'],
}
},)
@classmethod
@ModelView.button_action('account_voucher.act_pay_with_voucher')
def pay_with_voucher(cls, invoices):
cls.create_voucher(invoices)
@classmethod
def create_voucher(cls, invoices):
pool = Pool()
Voucher = pool.get('account.voucher')
Config = pool.get('account.voucher_configuration')
config = Config.get_configuration()
PayMode = pool.get('account.voucher.paymode')
Model = pool.get('ir.model')
today = date.today()
payment_mode = None
model, = Model.search([
('model', '=', cls.__name__)
])
if config.default_payment_mode:
payment_mode = config.default_payment_mode
else:
values = PayMode.search([
('company', '=', Transaction().context.get('company'))
])
if values:
payment_mode = values[0]
if not payment_mode:
return Voucher.raise_user_error('missing_paymode')
total_amount_to_pay = sum([i.amount_to_pay for i in invoices])
if not invoices:
return
party, type_ = invoices[0].party, invoices[0].type
if type_ == 'in':
if total_amount_to_pay < _ZERO:
voucher_type = 'receipt'
else:
voucher_type = 'payment'
else:
if total_amount_to_pay > _ZERO:
voucher_type = 'receipt'
else:
voucher_type = 'payment'
account_id = Voucher.get_account(voucher_type, payment_mode)
voucher_to_create = {
'party': party.id,
'voucher_type': voucher_type,
'date': today,
'description': '',
'payment_mode': payment_mode.id,
'state': 'draft',
'account': account_id,
'journal': payment_mode.journal.id,
'lines': [('create', [])],
'method_counterpart': 'one_line',
}
if payment_mode.bank_account and payment_mode.bank_account:
voucher_to_create['bank'] = payment_mode.bank_account.bank.id
for invoice in invoices:
if invoice.party.id != party.id:
continue
if invoice.state != 'posted' or not invoice.move or invoice.reconciled:
continue
sign = 1
if total_amount_to_pay < _ZERO and invoice.amount_to_pay < _ZERO:
sign = (-1)
for move_line in invoice.move.lines:
if move_line.account.id != invoice.account.id or move_line.reconciliation:
continue
detail = (model.name + ' ' + invoice.number)
voucher_to_create['lines'][0][1].append({
'detail': detail,
'amount': invoice.amount_to_pay * sign,
'amount_original': invoice.total_amount * sign,
'move_line': move_line.id,
'account': move_line.account.id,
})
vouchers = Voucher.create([voucher_to_create])
for voucher in vouchers:
voucher.on_change_lines()
voucher.save()
return vouchers
class AdvancePaymentAsk(ModelView):
'Advance Payment Ask'
__name__ = 'account.invoice.advance_payment.ask'
lines = fields.Many2Many('account.move.line', None, None,
'Account Moves Lines', required=True)
balance_amount = fields.Function(fields.Numeric('Balance Amount',
depends=['lines']), 'get_balance')
def get_balance(self, name=None):
pool = Pool()
Line = pool.get('account.move.line')
advance_account_ids = [l.account.id for l in self.lines]
balance = 0
lines = []
if self.lines:
party_id = self.lines[0].party.id
lines = Line.search([
('party', '=', party_id),
('account', 'in', advance_account_ids),
('reconciliation', '=', None),
])
balance = sum([abs(l.debit - l.credit) for l in lines])
return (balance, lines)
def get_previous_cross_moves(self):
pool = Pool()
Line = pool.get('account.move.line')
advance_account_ids = [l.account.id for l in self.lines]
lines = []
if self.lines:
account = self.lines[0].account
party_id = self.lines[0].party.id
dom_lines = [
('party', '=', party_id),
('account', 'in', advance_account_ids),
('reconciliation', '=', None),
('account.kind', '=', account.kind),
]
if account.kind == 'payable':
debit_credit = ('debit', '>', 0)
else:
debit_credit = ('credit', '>', 0)
dom_lines.append(debit_credit)
lines = Line.search(dom_lines)
return lines
class AdvancePayment(Wizard):
'Advance Payment'
__name__ = 'account.invoice.advance_payment'
start_state = 'search_advance'
search_advance = StateTransition()
start = StateView('account.invoice.advance_payment.ask',
'account_voucher.view_search_advance_payment_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add_link', 'tryton-ok', default=True),
])
add_link = StateTransition()
@classmethod
def __setup__(cls):
super(AdvancePayment, cls).__setup__()
cls._error_messages.update({
'invalid_total_advance': 'Total payment advances can not '
'be greater than Amount to Pay on invoice!',
})
def transition_search_advance(self):
pool = Pool()
Invoice = pool.get('account.invoice')
Advance = pool.get('account.invoice.advance_payment.ask')
invoice = Invoice(Transaction().context.get('active_id'))
if invoice.type == 'in':
account_types = ['receivable']
debit_credit = ('debit', '>', 0)
elif invoice.type == 'out':
account_types = ['payable']
debit_credit = ('credit', '>', 0)
else:
return 'end'
domain = [
('reconciliation', '=', None),
('move.state', '=', 'posted'),
('party', '=', invoice.party.id),
('state', '=', 'valid'),
('account.kind', 'in', account_types),
]
domain.append(debit_credit)
Advance.lines.domain = domain
return 'start'
def transition_add_link(self):
pool = Pool()
Note = pool.get('account.note')
Invoice = pool.get('account.invoice')
MoveLine = pool.get('account.move.line')
invoice = Invoice(Transaction().context.get('active_id'))
Config = pool.get('account.voucher_configuration')
config = Config.get_configuration()
if not self.start.lines:
return 'end'
if not config.default_journal_note:
Note.raise_user_error('missing_journal_note')
lines_to_create = []
reconcile_advance = []
reconcile_invoice = []
# recon: reconciliation
do_recon_invoice = False
do_recon_advance = False
balance, lines_advance_recon = self.start.get_balance()
def _set_to_reconcile():
res = []
for mline in invoice.move.lines:
if mline.account.id == invoice.account.id:
res.append(mline)
for pl in invoice.payment_lines:
if not pl.reconciliation:
res.append(pl)
return res
# Find previous cross payments
line_advance = self.start.lines[0]
previous_discounts = self.start.get_previous_cross_moves()
sum_last_advances = sum([(l.credit - l.debit) for l in previous_discounts])
sum_start_advances = sum(
[(line.debit - line.credit) for line in self.start.lines]
)
amount_balance = sum_start_advances - sum_last_advances
ignore_previous_discounts = False
if amount_balance < 0:
amount_balance = sum_start_advances
ignore_previous_discounts = True
if amount_balance > invoice.amount_to_pay:
do_recon_invoice = True
credit = 0
debit = 0
if invoice.type == 'in':
credit = invoice.amount_to_pay
else:
debit = invoice.amount_to_pay
lines_to_create.append({
'debit': debit,
'credit': credit,
'party': line_advance.party.id,
'account': line_advance.account.id,
'description': line_advance.description,
})
lines_to_create.append({
'debit': credit,
'credit': debit,
'party': line_advance.party.id,
'account': invoice.account.id,
'description': invoice.description,
})
else:
sum_debit = 0
sum_credit = 0
do_recon_advance = True
# Cofigure reconciliation
if not ignore_previous_discounts:
reconcile_advance.extend(previous_discounts)
for line in self.start.lines:
reconcile_advance.append(line)
if invoice.type == 'in':
sum_credit = abs(amount_balance)
else:
sum_debit = abs(amount_balance)
lines_to_create.append({
'debit': sum_debit,
'credit': sum_credit,
'party': line_advance.party.id,
'account': line_advance.account.id,
'description': line_advance.description,
})
pending_advance = abs(sum_debit - sum_credit)
if pending_advance == invoice.amount_to_pay:
do_recon_invoice = True
lines_to_create.append({
'debit': sum_credit,
'credit': sum_debit,
'party': invoice.party.id,
'account': invoice.account.id,
'description': invoice.description,
})
note, = Note.create([{
'description': '',
'journal': config.default_journal_note.id,
'date': date.today(),
'state': 'draft',
'lines': [('create', lines_to_create)],
}])
Note.post([note])
note.save()
payment_lines = []
adv_accounts_ids = [l.account.id for l in reconcile_advance]
if do_recon_invoice:
reconcile_invoice = _set_to_reconcile()
for nm_line in note.move.lines:
if do_recon_advance and nm_line.account.id in adv_accounts_ids:
reconcile_advance.append(nm_line)
if nm_line.account.id == invoice.account.id:
payment_lines.append(nm_line)
if do_recon_invoice:
print('adding ', nm_line)
reconcile_invoice.append(nm_line)
Invoice.write([invoice], {
'payment_lines': [('add', payment_lines)],
})
if reconcile_advance:
MoveLine.reconcile(reconcile_advance)
pending_to_pay = sum([ri.debit - ri.credit for ri in reconcile_invoice])
if reconcile_invoice and not pending_to_pay:
MoveLine.reconcile(reconcile_invoice)
return 'end'
class CrossPaymentAsk(ModelView):
'Cross Payment Ask'
__name__ = 'account.invoice.cross_payment.ask'
account = fields.Many2One('account.account', 'Account', required=True)
amount = fields.Numeric('Amount', digits=(16, 2), required=True)
date = fields.Date('Date', required=True)
description = fields.Char('Description')
class CrossPayment(Wizard):
'Advance Payment'
__name__ = 'account.invoice.cross_payment'
start = StateView('account.invoice.cross_payment.ask',
'account_voucher.view_search_cross_payment_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Add', 'add_payment', 'tryton-ok', default=True),
])
add_payment = StateTransition()
@classmethod
def __setup__(cls):
super(CrossPayment, cls).__setup__()
cls._error_messages.update({
'invalid_total_cross': 'Total payment crosss can not '
'be greater than Amount to Pay on invoice!',
})
def do_add_payment(self, action):
data = {
'account': self.start.account.id,
'amount': self.start.amount,
'date': self.start.date,
'description': self.start.description,
}
return action, data
def transition_add_payment(self):
pool = Pool()
Note = pool.get('account.note')
Invoice = pool.get('account.invoice')
Period = pool.get('account.period')
Move = pool.get('account.move')
MoveLine = pool.get('account.move.line')
invoice = Invoice(Transaction().context.get('active_id'))
Config = pool.get('account.voucher_configuration')
config = Config.get_configuration()
if not config.default_journal_note:
Note.raise_user_error('missing_journal_note')
lines_to_create = []
accounts_to_reconcile = []
company_id = Transaction().context.get('company')
period_id = Period.find(company_id, date=self.start.date)
move_to_create = {
'date': self.start.date,
'period': period_id,
'description': self.start.description,
'journal': config.default_journal_note.id,
'state': 'draft',
}
move, = Move.create([move_to_create])
lines_to_create.append({
'move': move.id,
'debit': _ZERO,
'credit': self.start.amount,
'party': invoice.party.id,
'account': self.start.account.id,
'description': self.start.description,
})
lines_to_create.append({
'move': move.id,
'debit': self.start.amount,
'credit': _ZERO,
'party': invoice.party.id,
'account': invoice.account.id,
'description': self.start.description,
})
pay_lines = MoveLine.create(lines_to_create)
accounts_to_reconcile.append(invoice.account.id)
"""
total_cross = abs(sum_debit - sum_credit)
if total_cross > invoice.amount_to_pay:
self.raise_user_error('invalid_total_cross')
elif total_cross == invoice.amount_to_pay:
for mline in invoice.move.lines:
if mline.account.id == invoice.account.id:
reconcile_invoice.append(mline)
else:
pass
"""
Move.post([move])
lines_to_reconcile = []
payment_lines = []
for move_line in invoice.move.lines:
if move_line.account.id == invoice.account.id:
lines_to_reconcile.append(move_line)
for pay_line in pay_lines:
if pay_line.account.id in accounts_to_reconcile:
payment_lines.append(pay_line)
Invoice.write([invoice], {
'payment_lines': [('add', payment_lines)],
})
"""
if lines_to_reconcile:
MoveLine.reconcile(lines_to_reconcile)
if reconcile_invoice:
MoveLine.reconcile(reconcile_invoice)
"""
return 'end'