mirror of
https://bitbucket.org/presik/trytonpsk-account_voucher.git
synced 2023-12-14 06:03:15 +01:00
580 lines
20 KiB
Python
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'
|