trytonpsk-account_voucher/invoice.py
2023-06-10 12:04:19 -05:00

726 lines
27 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
import re
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
from trytond.exceptions import UserError
from trytond.i18n import gettext
_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
@classmethod
def get_amount_to_pay(cls, invoices, name):
with Transaction().set_context({'with_payment': False}):
return super(Invoice, cls).get_amount_to_pay(invoices, name)
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:
account_type_payable = False
account_type_receivable = False
if self.type == 'in':
account_type_payable = True
else:
account_type_receivable = True
if voucher.state == 'posted' and voucher.move:
move_lines = []
for line in voucher.move.lines:
if account_type_payable and line.account.type.payable:
move_lines.append(line)
elif account_type_receivable and line.account.type.receivable:
move_lines.append(line)
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)
def create_cross_advance(self, lines):
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()
previous_cross_moves = Invoice.get_previous_cross_moves(lines)
lines_to_create = []
reconcile_advance = {}
sum_debit = 0
sum_credit = 0
amount_to_pay = self.amount_to_pay
party = self.party.id
for line_advance in lines:
line_advance_id = line_advance.id
cross_moves = previous_cross_moves.get(line_advance_id, None)
last_advance = sum(cross_moves['amount']) if cross_moves else 0
amount_balance = abs(line_advance.debit - line_advance.credit) - last_advance
value = {
'debit': 0,
'credit': 0,
'party': party,
'account': line_advance.account.id,
'description': line_advance.description,
'move_line': line_advance,
}
if hasattr(line_advance, 'operation_center') and line_advance.operation_center:
value['operation_center'] = line_advance.operation_center.id
if amount_balance > amount_to_pay:
amount = amount_to_pay
else:
lines_ = []
if cross_moves:
origins = cross_moves['origins']
lines_ = list(MoveLine.search([('origin', 'in', origins)]))
reconcile_advance[line_advance_id]= [line_advance] + lines_
amount = amount_balance
if line_advance.debit > 0:
value['credit'] = amount
sum_debit += amount
else:
value['debit'] = amount
sum_credit += amount
lines_to_create.append(value)
amount_to_pay -= amount
if amount_to_pay == 0:
break
line_ = {
'debit': sum_debit,
'credit': sum_credit,
'party': self.party.id,
'account': self.account.id,
'description': self.description,
}
line_invoice = [l for l in self.move.lines if l.account == self.account][0]
if hasattr(line_invoice, 'operation_center') and line_invoice.operation_center:
line_['operation_center'] = line_invoice.operation_center
lines_to_create.append(line_)
note, = Note.create([{
'description': '',
'journal': config.default_journal_note.id,
'date': date.today(),
'state': 'draft',
'lines': [('create', lines_to_create)],
}])
Note.post([note])
for ml in note.move.lines:
if ml.account.id == self.account.id:
Invoice.write([self], {
'payment_lines': [('add', [ml])],
})
if not ml.reconciliation and ml.origin and ml.origin.move_line and ml.origin.move_line.id in reconcile_advance.keys():
reconcile_advance[ml.origin.move_line.id].append(ml)
if reconcile_advance:
for v in reconcile_advance.values():
reconcile_ad = [l for l in v if not l.reconciliation]
if reconcile_ad:
MoveLine.reconcile(reconcile_ad)
if amount_to_pay == 0:
reconcile_invoice = [l for l in self.payment_lines if not l.reconciliation]
reconcile_invoice.extend([l for l in self.lines_to_pay if not l.reconciliation])
MoveLine.reconcile(reconcile_invoice)
@classmethod
def get_previous_cross_moves(cls, lines):
"""Return dict for each line with amount and list if lines cross previous
{amount: [], lines: [], origins: []}"""
pool = Pool()
NoteLine = pool.get('account.note.line')
advance_ids = [l.id for l in lines]
advance_account_ids = [l.account.id for l in lines]
if advance_ids:
account = lines[0].account
party_id = lines[0].party.id
dom_lines = [
('party', '=', party_id),
('account', 'in', advance_account_ids),
('move_line', 'in', advance_ids),
('note.state', '=', 'posted')
]
if account.type.payable:
debit_credit = ('debit', '>', 0)
else:
debit_credit = ('credit', '>', 0)
dom_lines.append(debit_credit)
lines_ = NoteLine.search(dom_lines)
cross_lines = {}
for l in lines_:
move_line_id = l.move_line.id
try:
cross_lines[move_line_id]['amount'].append(abs(l.debit-l.credit))
cross_lines[move_line_id]['lines'].append(l)
cross_lines[move_line_id]['origins'].append('account.note.line,' + str(l.id))
except:
cross_lines[move_line_id] = {
'amount':[abs(l.debit-l.credit)],
'lines':[l],
'origins':['account.note.line,' + str(l.id)]
}
return cross_lines
@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:
raise UserError(
gettext('account_voucher.msg_missing_paymode'))
total_amount_to_pay = sum([i.amount_to_pay for i in invoices])
if not invoices:
return
party, type_, currency = invoices[0].party, invoices[0].type, invoices[0].currency
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',
'currency': currency,
'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()
# NoteLine = pool.get('account.note.line')
# # Line = pool.get('account.move.line')
# advance_ids = [l.id for l in self.lines]
# advance_account_ids = [l.account.id for l in self.lines]
# lines = []
# if advance_ids:
# account = self.lines[0].account
# party_id = self.lines[0].party.id
# dom_lines = [
# ('party', '=', party_id),
# ('account', 'in', advance_account_ids),
# ('move_line', 'in', advance_ids),
# ('note.state', '=', 'posted')
# ]
# if account.type.payable:
# debit_credit = ('debit', '>', 0)
# else:
# debit_credit = ('credit', '>', 0)
# dom_lines.append(debit_credit)
# lines = NoteLine.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__()
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.state != 'posted':
return UserError(
gettext('account_voucher.msg_invoice_dont_posted'))
if invoice.type == 'in':
account_types = ('account.type.receivable', '=', True)
debit_credit = ('debit', '>', 0)
elif invoice.type == 'out':
account_types = ('account.type.payable', '=', True)
debit_credit = ('credit', '>', 0)
else:
return 'end'
domain = [
('reconciliation', '=', None),
('move.state', '=', 'posted'),
('party', '=', invoice.party.id),
('state', '=', 'valid'),
]
domain.append(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'
invoice.create_cross_advance(self.start.lines)
return 'end'
# if not config.default_journal_note:
# raise UserError(
# gettext('account_voucher.msg_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()
# # previous_discounts = invoice.payment_lines
# sum_last_advances = sum([abs(l.credit - l.debit) for l in previous_discounts])
# sum_start_advances = sum(
# [abs(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,
# 'move_line': line_advance,
# })
# 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:
# move_lines_ad = MoveLine.search([
# ('origin', 'in', ['account.note.line,' + str(l.id) for l in previous_discounts])
# ])
# reconcile_advance.extend(move_lines_ad)
# 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,
# 'move_line': line_advance,
# })
# 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,
# })
# if hasattr(line_advance, 'operation_center'):
# operation_center = line_advance.operation_center.id
# for l in lines_to_create:
# l['operation_center'] = operation_center
# 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:
# reconcile_invoice.append(nm_line)
# Invoice.write([invoice], {
# 'payment_lines': [('add', payment_lines)],
# })
# reconcile_advance = [r for r in reconcile_advance if not r.reconciliation]
# if [r for r in reconcile_advance if not r.reconciliation]:
# 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__()
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:
raise UserError(
gettext('account_voucher.msg_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'