2255 lines
79 KiB
Python
2255 lines
79 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 sql import Table
|
|
|
|
from trytond.model import ModelView, ModelSQL, fields, Workflow
|
|
from trytond.wizard import Wizard, StateView, StateTransition, Button, StateReport
|
|
from trytond.transaction import Transaction
|
|
from trytond.pyson import Eval, In, Or, Bool, Id
|
|
from trytond.pool import Pool
|
|
from trytond.report import Report
|
|
from trytond.exceptions import UserError
|
|
from trytond.i18n import gettext
|
|
|
|
conversor = None
|
|
try:
|
|
from numword import numword_es
|
|
conversor = numword_es.NumWordES()
|
|
except Exception:
|
|
print("Warning: Does not possible import numword module!")
|
|
print("Please install it...!")
|
|
|
|
|
|
_STATES = {
|
|
'readonly': Eval('state') != 'draft',
|
|
}
|
|
|
|
_STATES_NOTE = {
|
|
'readonly': Eval('state') != 'draft',
|
|
}
|
|
|
|
VOUCHER_TYPE = [
|
|
('payment', 'Payment'),
|
|
('receipt', 'Receipt'),
|
|
('multipayment', 'Multipayment'),
|
|
]
|
|
|
|
TYPE_PAYMENTS = [
|
|
('cash', 'Cash'),
|
|
('check', 'Check'),
|
|
('card_terminal', 'Card Terminal'),
|
|
('transfer', 'Transfer'),
|
|
]
|
|
|
|
_ZERO = Decimal('0.0')
|
|
|
|
|
|
class VoucherTemplate(ModelSQL, ModelView):
|
|
'Voucher Template'
|
|
__name__ = 'account.voucher_template'
|
|
name = fields.Char('Name', required=True)
|
|
voucher_type = fields.Selection(VOUCHER_TYPE, 'Type', required=True)
|
|
payment_mode = fields.Many2One('account.voucher.paymode',
|
|
'Payment Mode', select=True, required=True)
|
|
parties = fields.Many2Many('account.voucher_template-party',
|
|
'voucher_template', 'party', 'Voucher Template - Party')
|
|
lines = fields.One2Many('account.voucher_template.line',
|
|
'voucher_template', 'Voucher Template Line')
|
|
method_lines = fields.Selection([
|
|
('by_accrual', 'By Accrual'),
|
|
('by_amount', 'By Amount'),
|
|
], 'Method Lines', select=True, required=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(VoucherTemplate, cls).__setup__()
|
|
|
|
@staticmethod
|
|
def default_method_lines():
|
|
return 'by_accrual'
|
|
|
|
|
|
class TemplateLine(ModelSQL, ModelView):
|
|
'Template Line'
|
|
__name__ = 'account.voucher_template.line'
|
|
voucher_template = fields.Many2One('account.voucher_template',
|
|
'Voucher Template', select=True, required=True)
|
|
detail = fields.Char('Detail')
|
|
account = fields.Many2One('account.account', 'Account',
|
|
required=True, domain=[
|
|
('company', '=', Eval('context', {}).get('company', -1)),
|
|
('type', '!=', None),
|
|
])
|
|
amount = fields.Numeric('Amount', digits=(16, 2))
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TemplateLine, cls).__setup__()
|
|
|
|
|
|
class Voucher(Workflow, ModelSQL, ModelView):
|
|
'Voucher'
|
|
__name__ = 'account.voucher'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', readonly=True, help="Voucher Number")
|
|
party = fields.Many2One('party.party', 'Party', select=True,
|
|
states={
|
|
'readonly': Eval('state') != 'draft',
|
|
'required': Eval('voucher_type') != 'multipayment',
|
|
}, context={'party': Eval('party')})
|
|
bank = fields.Many2One('bank', 'Bank', states={
|
|
'readonly': Eval('state') != 'draft',
|
|
'required': In(Eval('payment_type'), ['check', 'card_terminal', 'transfer']),
|
|
}, depends=['payment_mode'])
|
|
check_number = fields.Char('Check Number', states={
|
|
'readonly': In(Eval('state'), ['posted', 'cancel']),
|
|
'required': Eval('payment_type') == 'check',
|
|
}, depends=['payment_mode'])
|
|
payment_mode = fields.Many2One('account.voucher.paymode', 'Payment Mode',
|
|
required=True, states={
|
|
'readonly': Eval('state') != 'draft',
|
|
})
|
|
payment_type = fields.Char('Payment Type', depends=['payment_mode'])
|
|
voucher_type = fields.Selection(VOUCHER_TYPE,
|
|
'Type', select=True, required=True, states={
|
|
'readonly': ((Eval('state') != 'draft')
|
|
| Eval('context', {}).get('type')
|
|
| (Eval('lines', [0]) & Eval('voucher_type'))),
|
|
}, depends=['state'])
|
|
voucher_type_string = voucher_type.translated('voucher_type')
|
|
date = fields.Date('Date', required=True, states=_STATES)
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True,
|
|
depends=['voucher_type'], states=_STATES)
|
|
currency = fields.Many2One('currency.currency', 'Currency', required=True,
|
|
states=_STATES)
|
|
company = fields.Many2One('company.company', 'Company', required=True,
|
|
states=_STATES)
|
|
lines = fields.One2Many('account.voucher.line', 'voucher', 'Lines',
|
|
states=_STATES, context={
|
|
'party': Eval('party')
|
|
}, depends=['party'])
|
|
comment = fields.Text('Comment', states=_STATES)
|
|
description = fields.Char('Description', states=_STATES)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('processed', 'Processed'),
|
|
('byapprove', 'By Approve'),
|
|
('approved', 'Approved'),
|
|
('cancel', 'Cancel'),
|
|
('posted', 'Posted'),
|
|
], 'State', select=True, readonly=True)
|
|
state_string = state.translated('state')
|
|
amount_to_pay = fields.Numeric('Amount To Pay', digits=(16, 2),
|
|
select=True, states={'readonly': True}, depends=['lines'])
|
|
move = fields.Many2One('account.move', 'Move', states={
|
|
'readonly': Or(
|
|
(Eval('state') != 'draft'),
|
|
(Eval('source') != 'move'),
|
|
),
|
|
})
|
|
origin = fields.Reference('Origin', selection='get_origin',
|
|
select=True, depends=['state'], states={
|
|
'readonly': Eval('state') != 'draft',
|
|
})
|
|
delivered = fields.Boolean('Delivered ?', states={
|
|
'readonly': In(Eval('state'), ['posted', 'cancel']),
|
|
'invisible': Eval('payment_type') != 'check',
|
|
})
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
states=_STATES, domain=[
|
|
('company', '=', Eval('context', {}).get('company', -1)),
|
|
('type', '!=', None),
|
|
])
|
|
amount_to_pay_words = fields.Char('Amount to Pay (Words)',
|
|
states={'readonly': True}, depends=['lines'])
|
|
reference = fields.Char('Reference', states=_STATES)
|
|
method_counterpart = fields.Selection([
|
|
('one_line', 'One Line'),
|
|
('grouped_party', 'Grouped Party'),
|
|
('multiple_lines', 'Multiple Lines'),
|
|
], 'Method Counterpart', required=True, states=_STATES)
|
|
target_account_bank = fields.Function(fields.Char('Target Account Bank'), 'get_target_account_bank')
|
|
bank_account_number = fields.Many2One('bank.account.number', 'Bank Account Number',
|
|
states=_STATES, domain=[
|
|
('account.owners', '=', Eval('party')),
|
|
])
|
|
sended_mail = fields.Boolean('Sended Email')
|
|
# states={
|
|
# 'invisible': Eval('voucher_type') != 'receipt',
|
|
# })
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Voucher, cls).__setup__()
|
|
cls._buttons.update({
|
|
'draft': {
|
|
'invisible': Eval('state') == 'draft',
|
|
},
|
|
'post': {
|
|
'invisible': ~Eval('state').in_(['processed']),
|
|
'help': 'Cancel the invoice',
|
|
'depends': ['state'],
|
|
},
|
|
'process': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'cancel': {
|
|
'invisible': Eval('state').in_(['cancel', 'posted'])
|
|
},
|
|
'select_lines': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
|
|
})
|
|
cls._transitions |= set((
|
|
('draft', 'processed'),
|
|
('draft', 'cancel'),
|
|
('processed', 'draft'),
|
|
('processed', 'cancel'),
|
|
('processed', 'posted'),
|
|
('posted', 'cancel'),
|
|
('posted', 'draft'),
|
|
('cancel', 'draft'),
|
|
))
|
|
cls._order.insert(0, ('date', 'DESC'))
|
|
cls._order.insert(1, ('number', 'DESC'))
|
|
|
|
def get_rec_name(self, name):
|
|
rec_name = self.number or ' '
|
|
detail = self.reference or ' '
|
|
return rec_name + '[' + detail + ']'
|
|
|
|
@classmethod
|
|
def validate(cls, vouchers):
|
|
super(Voucher, cls).validate(vouchers)
|
|
for voucher in vouchers:
|
|
if voucher.voucher_type != 'multipayment':
|
|
for line in voucher.lines:
|
|
pass
|
|
|
|
@staticmethod
|
|
def default_method_counterpart():
|
|
return 'one_line'
|
|
|
|
@fields.depends('party', 'bank')
|
|
def on_change_with_bank_account_number(self):
|
|
if self.party and self.party.bank_accounts:
|
|
return self.party.bank_accounts[0].numbers[0].id
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_currency():
|
|
Company = Pool().get('company.company')
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
return Company(company_id).currency.id
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@classmethod
|
|
def copy(cls, vouchers, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default['number'] = None
|
|
default['state'] = 'draft'
|
|
default['move'] = None
|
|
default['check_number'] = ''
|
|
default['date'] = Pool().get('ir.date').today()
|
|
new_vouchers = []
|
|
for voucher in vouchers:
|
|
new_voucher, = super(Voucher, cls).copy([voucher], default=default)
|
|
new_vouchers.append(new_voucher)
|
|
return new_vouchers
|
|
|
|
@fields.depends('payment_mode', 'account', 'journal', 'payment_type', 'voucher_type')
|
|
def on_change_payment_mode(self):
|
|
if self.payment_mode:
|
|
self.journal = self.payment_mode.journal.id
|
|
self.payment_type = self.payment_mode.payment_type
|
|
self.account = Voucher.get_account(self.voucher_type, self.payment_mode)
|
|
if self.payment_mode.payment_type != 'cash':
|
|
self.bank = self.payment_mode.bank_account.bank.id
|
|
else:
|
|
self.bank = None
|
|
|
|
@classmethod
|
|
def get_account(cls, voucher_type, payment_mode):
|
|
account = None
|
|
if payment_mode.account:
|
|
account = payment_mode.account.id
|
|
else:
|
|
raise UserError(gettext('account_voucher.msg_missing_journal_account'))
|
|
return account
|
|
|
|
def get_amount2words(self, value):
|
|
if conversor:
|
|
return (conversor.cardinal(int(value))).upper()
|
|
else:
|
|
return ''
|
|
|
|
@fields.depends('lines', 'amount_to_pay', 'amount_to_pay_words')
|
|
def on_change_lines(self):
|
|
value = Decimal(0)
|
|
value_words = None
|
|
if self.lines:
|
|
value = self._get_amount_to_pay()
|
|
value_words = self.get_amount2words(value)
|
|
self.amount_to_pay = value
|
|
self.amount_to_pay_words = value_words
|
|
|
|
def set_number(self):
|
|
if self.number:
|
|
return
|
|
number = self.get_next_number()
|
|
self.write([self], {'number': number})
|
|
|
|
def get_next_number(self, pattern=None):
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
|
|
if pattern is None:
|
|
pattern = {}
|
|
else:
|
|
pattern = pattern.copy()
|
|
|
|
accounting_date = self.date
|
|
period_id = Period.find(
|
|
self.company.id, date=accounting_date)
|
|
|
|
period = Period(period_id)
|
|
fiscalyear = period.fiscalyear
|
|
pattern.setdefault('company', self.company.id)
|
|
pattern.setdefault('fiscalyear', fiscalyear.id)
|
|
pattern.setdefault('period', period.id)
|
|
voucher_type = self.voucher_type
|
|
|
|
for voucher_sequence in fiscalyear.voucher_sequences:
|
|
if voucher_sequence.match(pattern):
|
|
sequence = getattr(
|
|
voucher_sequence, '%s_sequence' % voucher_type)
|
|
break
|
|
else:
|
|
sequence = getattr(self.payment_mode, 'sequence_%s' % voucher_type)
|
|
|
|
with Transaction().set_context(date=accounting_date):
|
|
return sequence.get()
|
|
|
|
def get_target_account_bank(self, name):
|
|
if self.party and self.party.bank_accounts:
|
|
for ac in self.party.bank_accounts:
|
|
type_account_bank = ''
|
|
numbers = ''
|
|
for n in ac.numbers:
|
|
if n.type == 'checking_account':
|
|
type_account_bank = 'Cuenta Corriente'
|
|
elif n.type == 'saving_account':
|
|
type_account_bank = 'Cuenta Ahorros'
|
|
numbers += type_account_bank + ' # ' + n.number + ' '
|
|
owners = [p.name for p in ac.owners]
|
|
str_account_bank = numbers + ' Titular: ' + owners[0]
|
|
return str_account_bank
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('processed')
|
|
def process(cls, vouchers):
|
|
for voucher in vouchers:
|
|
voucher.set_number()
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, vouchers):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancel')
|
|
def cancel(cls, vouchers):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Payment = pool.get('account.payment')
|
|
voucher_ids= []
|
|
voucher_ids_append = voucher_ids.append
|
|
for voucher in vouchers:
|
|
voucher_ids_append(voucher.id)
|
|
if voucher.move:
|
|
Move.delete([voucher.move])
|
|
payments = Payment.search([('voucher', 'in', voucher_ids)])
|
|
if payments:
|
|
Payment.write(payments, {'state': 'draft'})
|
|
Payment.delete(payments)
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('posted')
|
|
def post(cls, vouchers):
|
|
for voucher in vouchers:
|
|
to_reconcile = None
|
|
if not voucher.payment_mode.account or not voucher.payment_mode.account:
|
|
raise UserError(gettext('account_voucher.msg_missing_journal_account'))
|
|
if voucher.amount_to_pay <= Decimal("0.0"):
|
|
raise UserError(gettext('account_voucher.msg_missing_pay_lines'))
|
|
if not voucher.move:
|
|
to_reconcile = voucher.create_move()
|
|
if voucher.move.state == 'posted':
|
|
continue
|
|
voucher._post_move()
|
|
if to_reconcile:
|
|
voucher._reconcile_lines(to_reconcile)
|
|
|
|
@classmethod
|
|
@ModelView.button_action('account_voucher.wizard_select_lines')
|
|
def select_lines(cls, vouchers):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button_action('account_voucher.wizard_select_multilines')
|
|
def select_multilines(cls, vouchers):
|
|
pass
|
|
|
|
@staticmethod
|
|
def _get_origin():
|
|
'Return list of Model names for origin Reference'
|
|
return []
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Model = Pool().get('ir.model')
|
|
models = cls._get_origin()
|
|
models = Model.search([
|
|
('model', 'in', models),
|
|
])
|
|
return [(None, '')] + [(m.model, m.name) for m in models]
|
|
|
|
def send_payment_receipt_emails(self):
|
|
pool = Pool()
|
|
config = pool.get('account.voucher_configuration')(1)
|
|
Template = pool.get('email.template')
|
|
if not config.template_email_confirm:
|
|
return
|
|
response = Template.send(config.template_email_confirm, self, self.party.email)
|
|
if response.status_code == 202:
|
|
self.write([self], {'sended_mail': True})
|
|
|
|
def get_payment_type(self, name=None):
|
|
if self.payment_mode:
|
|
return self.payment_mode.payment_type
|
|
|
|
def _get_amount_to_pay(self):
|
|
res = Decimal('0.0')
|
|
if self.lines:
|
|
for line in self.lines:
|
|
if line.amount:
|
|
res += line.amount
|
|
return res
|
|
|
|
def create_move(self):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
MoveLine = pool.get('account.move.line')
|
|
Period = pool.get('account.period')
|
|
|
|
period_id = Period.find(self.company.id, date=self.date)
|
|
move, = Move.create([{
|
|
'journal': self.journal.id,
|
|
'period': period_id,
|
|
'date': self.date,
|
|
'origin': str(self),
|
|
'state': 'draft',
|
|
'description': self.description,
|
|
}])
|
|
|
|
to_reconcile = {}
|
|
move_lines = []
|
|
|
|
for line in self.lines:
|
|
if not line.amount or line.amount == _ZERO:
|
|
continue
|
|
|
|
move_line = line.get_move_line(move.id)
|
|
move_lines.append(move_line)
|
|
if line.move_line and line.amount != _ZERO:
|
|
to_reconcile[line] = [line.move_line]
|
|
|
|
amount = abs(sum(ln['credit'] - ln['debit'] for ln in move_lines))
|
|
|
|
if self.voucher_type == 'receipt':
|
|
debit = amount
|
|
credit = Decimal('0.0')
|
|
else:
|
|
debit = Decimal('0.0')
|
|
credit = amount
|
|
if self.voucher_type in ['receipt', 'payment', 'multipayment']:
|
|
to_line = self.get_move_line(move, debit, credit)
|
|
move_lines.append(to_line)
|
|
|
|
lines = MoveLine.create(move_lines)
|
|
for line in lines:
|
|
if line.origin in to_reconcile:
|
|
to_reconcile[line.origin].append(line)
|
|
self.write([self], {'move': move.id})
|
|
return to_reconcile
|
|
|
|
def get_move_line(self, move, debit, credit):
|
|
MoveLine = Pool().get('account.move.line')
|
|
to_create = {
|
|
'description': self.description,
|
|
'account': self.account.id,
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'move': move.id,
|
|
}
|
|
if hasattr(MoveLine, 'reference'):
|
|
to_create['reference'] = self.reference
|
|
|
|
amount_second_currency = None
|
|
second_currency = None
|
|
if self.currency != self.company.currency:
|
|
amount_second_currency = self.amount_to_pay
|
|
second_currency = self.currency
|
|
|
|
if amount_second_currency:
|
|
to_create['amount_second_currency'] = amount_second_currency.copy_sign(debit - credit)
|
|
to_create['second_currency'] = second_currency
|
|
|
|
if self.account.party_required:
|
|
if self.payment_mode.require_party:
|
|
to_create['party'] = self.payment_mode.party.id
|
|
elif self.party:
|
|
to_create['party'] = self.party.id
|
|
else:
|
|
raise UserError(gettext('account_voucher.msg_missing_party_voucher'))
|
|
return to_create
|
|
|
|
def _post_move(self):
|
|
Move = Pool().get('account.move')
|
|
Move.post([self.move])
|
|
|
|
def _reconcile_lines(self, to_reconcile):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
MoveLine = pool.get('account.move.line')
|
|
pool = Pool()
|
|
config = pool.get('account.configuration')(1)
|
|
writeoff = None
|
|
# reconcile check
|
|
lines_list = []
|
|
for sl, ml in to_reconcile.values():
|
|
to_reconcile_lines = None
|
|
if sl.move_origin and sl.move_origin.__name__ == 'account.invoice':
|
|
invoice = Invoice(sl.move_origin.id)
|
|
amount = abs(ml.credit - ml.debit)
|
|
if invoice.currency != invoice.company.currency:
|
|
amount = abs(ml.amount_second_currency)
|
|
writeoff = config.difference_in_exchange
|
|
if self.currency.is_zero(abs(invoice.amount_to_pay) - amount) or self.currency.is_zero(invoice.amount_to_pay):
|
|
pending_reconcile = [l for l in invoice.payment_lines + invoice.lines_to_pay
|
|
if not l.reconciliation]
|
|
to_reconcile_lines = [sl, ml]
|
|
to_reconcile_lines_ids = [sl.id, ml.id]
|
|
for pr in pending_reconcile:
|
|
if pr.id not in to_reconcile_lines_ids:
|
|
to_reconcile_lines.append(pr)
|
|
else:
|
|
Invoice.write([invoice], {
|
|
'payment_lines': [('add', [ml.id])],
|
|
})
|
|
# if self.voucher_type in ['payment', 'multipayment'] and invoice.type == 'out':
|
|
# print('')
|
|
else:
|
|
if (sl.debit - sl.credit) + (ml.debit - ml.credit) == _ZERO:
|
|
to_reconcile_lines = [sl, ml]
|
|
|
|
if not to_reconcile_lines:
|
|
VoucherLine = pool.get('account.voucher.line')
|
|
lines = VoucherLine.search([
|
|
('move_line', '=', sl.id),
|
|
('voucher.move', '!=', None),
|
|
])
|
|
lines_move = []
|
|
for mv in lines:
|
|
lines_move.extend([ms for ms in mv.voucher.move.lines if ms.account == sl.account])
|
|
|
|
payments = sum([(l.debit - l.credit) for l in lines_move])
|
|
|
|
if (sl.debit - sl.credit) + payments == _ZERO:
|
|
to_reconcile_lines = lines_move + [sl]
|
|
|
|
if to_reconcile_lines:
|
|
lines_list.append(to_reconcile_lines)
|
|
if lines_list:
|
|
print(lines_list, 'lines final reconcile')
|
|
MoveLine.reconcile(*lines_list, writeoff=writeoff)
|
|
|
|
@classmethod
|
|
def delete(cls, vouchers):
|
|
pool = Pool()
|
|
Line = pool.get('account.voucher.line')
|
|
Move = pool.get('account.move')
|
|
Payment = pool.get('account.payment')
|
|
voucher_ids = []
|
|
vouchers_append = voucher_ids.append
|
|
for voucher in vouchers:
|
|
vouchers_append(voucher.id)
|
|
if voucher.state == 'posted' or voucher.number:
|
|
raise UserError(gettext('account_voucher.msg_delete_record'))
|
|
if voucher.move:
|
|
Move.delete([voucher.move])
|
|
Line.delete([l for v in vouchers for l in v.lines])
|
|
|
|
payments = Payment.search([('voucher', 'in', voucher_ids)])
|
|
if payments:
|
|
Payment.write(payments, {'state': 'draft'})
|
|
Payment.delete(payments)
|
|
return super(Voucher, cls).delete(vouchers)
|
|
|
|
def add_lines(self, move_lines):
|
|
# Create voucher lines from a list of account move line
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
Model = Pool().get('ir.model')
|
|
Line = pool.get('account.voucher.line')
|
|
lines_to_create = []
|
|
for line in move_lines:
|
|
if line.party:
|
|
party_id = line.party.id
|
|
else:
|
|
party_id = None
|
|
|
|
amount = line.credit or line.debit
|
|
if self.voucher_type == 'receipt':
|
|
if line.credit > Decimal('0'):
|
|
amount = -amount
|
|
else:
|
|
if line.debit > Decimal('0'):
|
|
amount = -amount
|
|
|
|
model = None
|
|
amount_original = amount
|
|
if line.move_origin and hasattr(
|
|
line.move_origin, '__name__') and \
|
|
line.move_origin.__name__ == 'account.invoice':
|
|
model, = Model.search([
|
|
('model', '=', line.move_origin.__name__),
|
|
])
|
|
amount_to_pay = Invoice.get_amount_to_pay(
|
|
[line.move_origin], 'amount_to_pay'
|
|
)
|
|
amount_to_pay = amount_to_pay[line.move_origin.id]
|
|
|
|
detail = (model.name + ' ' + line.move_origin.number)
|
|
if amount_to_pay < amount:
|
|
amount = amount_to_pay
|
|
else:
|
|
# We must looking for partial payment for this line
|
|
dom = [
|
|
('move_line', '=', line.id),
|
|
('move_line.reconciliation', '=', None),
|
|
('voucher.state', '=', 'posted'),
|
|
('voucher.party', '=', self.party.id),
|
|
]
|
|
if self.party and self.voucher_type != 'multipayment':
|
|
dom.append(('voucher.party', '=', self.party.id))
|
|
voucher_lines = Line.search(dom)
|
|
previous_paid_amount = 0
|
|
for vl in voucher_lines:
|
|
previous_paid_amount += vl.amount
|
|
|
|
amount = amount - previous_paid_amount
|
|
detail = line.description
|
|
|
|
lines_to_create.append({
|
|
'voucher': self.id,
|
|
'detail': detail,
|
|
'account': line.account.id,
|
|
'amount_original': amount_original,
|
|
'amount': amount,
|
|
'move_line': line.id,
|
|
'party': party_id,
|
|
'reference': line.reference or ''
|
|
})
|
|
Line.create(lines_to_create)
|
|
self.on_change_lines()
|
|
self.save()
|
|
|
|
|
|
class VoucherLine(ModelSQL, ModelView):
|
|
'Voucher Line'
|
|
__name__ = 'account.voucher.line'
|
|
_rec_name = 'reference'
|
|
voucher = fields.Many2One('account.voucher', 'Voucher', required=True)
|
|
detail = fields.Char('Detail')
|
|
account = fields.Many2One('account.account', 'Account', required=True,
|
|
domain=[
|
|
('company', '=', Eval('context', {}).get('company', -1)),
|
|
('type', '!=', None),
|
|
])
|
|
party = fields.Many2One('party.party', 'Party')
|
|
amount = fields.Numeric('Amount', digits=(16, 2), required=True)
|
|
move_line = fields.Many2One('account.move.line', 'Move Line',
|
|
domain=[
|
|
('move.state', '=', 'posted'),
|
|
('state', '=', 'valid'),
|
|
('reconciliation', '=', None),
|
|
('account.type.statement', 'in', ['balance', 'off-balance']),
|
|
], depends=['party'])
|
|
amount_original = fields.Numeric('Original Amount', digits=(16, 2),
|
|
readonly=True)
|
|
reference = fields.Char('Reference')
|
|
type = fields.Selection([
|
|
('tax', "Tax"),
|
|
('normal', "Normal"),
|
|
], "Type", required=True)
|
|
untaxed_amount = fields.Numeric('Untaxed Amount', digits=(16, 2),
|
|
states={'invisible': Eval('type') != 'tax'}, depends=['type'])
|
|
tax = fields.Many2One('account.tax', "Tax",
|
|
states={'invisible': Eval('type') != 'tax'}, depends=['type'])
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'normal'
|
|
|
|
def get_rec_name(self, name):
|
|
rec_name = self.voucher.number or ' '
|
|
detail = self.detail or ' '
|
|
return rec_name + '['+detail+']'
|
|
|
|
@classmethod
|
|
def search_rec_name(cls, name, clause):
|
|
if clause[1].startswith('!') or clause[1].startswith('not '):
|
|
bool_op = 'AND'
|
|
else:
|
|
bool_op = 'OR'
|
|
return [bool_op,
|
|
('voucher.number',) + tuple(clause[1:]),
|
|
('reference',) + tuple(clause[1:]),
|
|
]
|
|
|
|
def get_reference(self, name):
|
|
Invoice = Pool().get('account.invoice')
|
|
if self.move_line.move:
|
|
invoices = Invoice.search(
|
|
[('move', '=', self.move_line.move.id)])
|
|
if invoices:
|
|
return invoices[0].reference
|
|
|
|
@fields.depends('move_line', 'account', 'amount', 'detail', 'party')
|
|
def on_change_move_line(self):
|
|
if not self.move_line:
|
|
return
|
|
if self.move_line.debit > 0:
|
|
amount = self.move_line.debit
|
|
elif self.move_line.credit > 0:
|
|
amount = self.move_line.credit
|
|
else:
|
|
amount = Decimal(0)
|
|
self.amount = amount
|
|
self.account = self.move_line.account.id
|
|
self.detail = self.move_line.description
|
|
self.party = self.move_line.party.id
|
|
|
|
@fields.depends('amount', 'type', 'untaxed_amount', 'tax')
|
|
def on_change_untaxed_amount(self):
|
|
if not self.untaxed_amount or not self.tax:
|
|
return
|
|
if self.tax.type == 'percentage':
|
|
self.amount = self.untaxed_amount * self.tax.rate
|
|
|
|
@fields.depends('amount', 'type', 'untaxed_amount', 'tax')
|
|
def on_change_tax(self):
|
|
account = None
|
|
amount = None
|
|
if self.tax and self.tax.type == 'percentage':
|
|
account = self.tax.invoice_account.id
|
|
if self.untaxed_amount:
|
|
amount = self.untaxed_amount * self.tax.rate
|
|
self.account = account
|
|
self.amount = amount
|
|
|
|
def get_move_line(self, move_id):
|
|
pool = Pool()
|
|
MoveLine = pool.get('account.move.line')
|
|
Currency = pool.get('currency.currency')
|
|
res = {}
|
|
currency = self.voucher.currency
|
|
company_currency = self.voucher.company.currency
|
|
|
|
with Transaction().set_context(date=self.voucher.date):
|
|
amount = Currency.compute(currency,
|
|
self.amount, company_currency)
|
|
|
|
amount_second_currency = None
|
|
second_currency = None
|
|
if currency != company_currency:
|
|
amount_second_currency = self.amount
|
|
second_currency = currency
|
|
|
|
if self.voucher.voucher_type == 'receipt':
|
|
if amount >= Decimal('0.0'):
|
|
res['debit'] = Decimal('0.0')
|
|
res['credit'] = abs(amount)
|
|
else:
|
|
res['debit'] = abs(amount)
|
|
res['credit'] = Decimal('0.0')
|
|
else:
|
|
if amount >= Decimal('0.0'):
|
|
res['debit'] = abs(amount)
|
|
res['credit'] = Decimal('0.0')
|
|
else:
|
|
res['debit'] = Decimal('0.0')
|
|
res['credit'] = abs(amount)
|
|
|
|
if amount_second_currency:
|
|
res['amount_second_currency'] = amount_second_currency.copy_sign(res['debit'] - res['credit'])
|
|
res['second_currency'] = second_currency
|
|
|
|
if self.party:
|
|
line_party_id = self.party.id
|
|
elif self.voucher.party:
|
|
line_party_id = self.voucher.party.id
|
|
|
|
if self.voucher.voucher_type == 'multipayment':
|
|
if self.party:
|
|
line_party_id = self.party.id
|
|
else:
|
|
if self.account.party_required:
|
|
raise UserError(
|
|
gettext('account_voucher.msg_missing_party_line'))
|
|
line_party_id = None
|
|
|
|
if self.account.party_required:
|
|
res['party'] = line_party_id
|
|
|
|
if hasattr(MoveLine, 'reference'):
|
|
if self.reference:
|
|
res['reference'] = self.reference
|
|
elif self.voucher.reference and hasattr(MoveLine, 'reference'):
|
|
res['reference'] = self.voucher.reference
|
|
|
|
res['account'] = self.account.id
|
|
description = self.detail
|
|
if self.move_line and self.move_line.move_origin:
|
|
if hasattr(self.move_line.origin, 'number'):
|
|
description = self.move_line.move_origin.number
|
|
else:
|
|
if hasattr(self.move_line.move_origin, 'rec_name'):
|
|
description = self.move_line.move_origin.rec_name
|
|
res['description'] = description
|
|
res['move'] = move_id
|
|
res['origin'] = str(self)
|
|
|
|
if self.tax:
|
|
with Transaction().set_context(date=self.voucher.date):
|
|
untaxed_amount = Currency.compute(currency,
|
|
self.untaxed_amount, company_currency)
|
|
value = {
|
|
'amount': untaxed_amount,
|
|
'tax': self.tax.id,
|
|
'type': 'base',
|
|
}
|
|
res['tax_lines'] = [('create', [value])]
|
|
return res
|
|
|
|
def get_move_line_multi(self):
|
|
if self.voucher.party:
|
|
party_id = self.voucher.party.id
|
|
else:
|
|
party_id = self.party.id if self.party else None
|
|
if not self.voucher.account.party_required:
|
|
party_id = None
|
|
res = {
|
|
'description': self.voucher.description,
|
|
'account': self.voucher.account.id,
|
|
'debit': Decimal('0.0'),
|
|
'credit': self.amount,
|
|
'party': party_id,
|
|
}
|
|
return [res]
|
|
|
|
|
|
class SelectLinesAsk(ModelView):
|
|
'Select Lines Assistant'
|
|
__name__ = 'account.voucher.select_lines.ask'
|
|
lines = fields.Many2Many('account.move.line', None, None,
|
|
'Account Moves Lines')
|
|
parties = fields.Many2Many('party.party', None, None,
|
|
'Parties', states={
|
|
'readonly': ~Bool(Eval('is_multipayment')),
|
|
'invisible': ~Bool(Eval('is_multipayment')),
|
|
})
|
|
is_multipayment = fields.Boolean('Is Multipayment', states={
|
|
'readonly': True,
|
|
})
|
|
include_account_kind = fields.Selection([
|
|
('payable', 'Payable'),
|
|
('receivable', 'Receivable'),
|
|
('', ''),
|
|
], 'Include Account Kind',
|
|
help='The kind account selected will be included on preview.')
|
|
|
|
@staticmethod
|
|
def default_is_multipayment():
|
|
Voucher = Pool().get('account.voucher')
|
|
voucher = Voucher(Transaction().context.get('active_id'))
|
|
if voucher.voucher_type == 'multipayment':
|
|
return True
|
|
|
|
@staticmethod
|
|
def default_parties():
|
|
Voucher = Pool().get('account.voucher')
|
|
voucher = Voucher(Transaction().context.get('active_id'))
|
|
if voucher.voucher_type != 'multipayment' and voucher.party:
|
|
return [voucher.party.id]
|
|
|
|
@staticmethod
|
|
def default_account_kind_other():
|
|
return ''
|
|
|
|
|
|
class SelectLines(Wizard):
|
|
'Select Lines'
|
|
__name__ = 'account.voucher.select_lines'
|
|
start_state = 'search_lines'
|
|
search_lines = StateTransition()
|
|
start = StateView('account.voucher.select_lines.ask',
|
|
'account_voucher.view_search_lines_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Add', 'add_lines', 'tryton-ok', default=True),
|
|
])
|
|
add_lines = StateTransition()
|
|
|
|
def transition_search_lines(self):
|
|
pool = Pool()
|
|
Voucher = pool.get('account.voucher')
|
|
Select = pool.get('account.voucher.select_lines.ask')
|
|
voucher = Voucher(Transaction().context.get('active_id'))
|
|
account_type = []
|
|
if voucher.voucher_type == 'multipayment':
|
|
account_type = [
|
|
('account.type.payable', '=', True),
|
|
]
|
|
else:
|
|
account_type = [['OR',
|
|
('account.type.receivable', '=', True),
|
|
('account.type.payable', '=', True),
|
|
|
|
]]
|
|
|
|
line_domain = [
|
|
('account.reconcile', '=', True),
|
|
('state', '=', 'valid'),
|
|
('reconciliation', '=', None),
|
|
('move.state', '=', 'posted'),
|
|
]
|
|
if voucher.voucher_type != 'multipayment':
|
|
line_domain.append(('party', '=', voucher.party.id))
|
|
else:
|
|
line_domain.append(('party', 'in', Eval('parties')))
|
|
|
|
if account_type:
|
|
line_domain.append(account_type)
|
|
|
|
Select.lines.domain = line_domain
|
|
return 'start'
|
|
|
|
def transition_add_lines(self):
|
|
pool = Pool()
|
|
Voucher = pool.get('account.voucher')
|
|
voucher = Voucher(Transaction().context.get('active_id'))
|
|
voucher.add_lines(self.start.lines)
|
|
return 'end'
|
|
|
|
|
|
class VoucherReport(Report):
|
|
'Voucher Report'
|
|
__name__ = 'account.voucher.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
Company = Pool().get('company.company')
|
|
company_id = Transaction().context.get('company')
|
|
report_context['company'] = Company(company_id)
|
|
return report_context
|
|
|
|
|
|
class VoucherMoveReport(Report):
|
|
'Voucher Report'
|
|
__name__ = 'account.voucher_move.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
Company = Pool().get('company.company')
|
|
company_id = Transaction().context.get('company')
|
|
report_context['company'] = Company(company_id)
|
|
records = cls.set_move_sums(records)
|
|
report_context['records'] = records
|
|
return report_context
|
|
|
|
@classmethod
|
|
def set_move_sums(cls, records):
|
|
vouchers = []
|
|
for voucher in records:
|
|
debits_ = []
|
|
credits_ = []
|
|
if voucher.move:
|
|
for line in voucher.move.lines:
|
|
debits_.append(line.debit)
|
|
credits_.append(line.credit)
|
|
vouchers.append([voucher, sum(debits_), sum(credits_)])
|
|
return vouchers
|
|
|
|
|
|
class VoucherPayMode(ModelSQL, ModelView):
|
|
'Voucher Pay Mode'
|
|
__name__ = 'account.voucher.paymode'
|
|
name = fields.Char('Name', required=True)
|
|
company = fields.Many2One('company.company', 'Company', required=True)
|
|
payment_type = fields.Selection(TYPE_PAYMENTS, 'Payment Type',
|
|
select=True)
|
|
bank_account = fields.Many2One('bank.account', 'Bank Account',
|
|
states={
|
|
'invisible': Eval('payment_type') == 'cash',
|
|
'required': Eval('payment_type') != 'cash'
|
|
})
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True)
|
|
kind = fields.Selection([
|
|
('payment', 'Payments'),
|
|
('receipt', 'Receipts'),
|
|
('both', 'Both'),
|
|
], 'Kind', select=True, required=True)
|
|
sequence_payment = fields.Many2One('ir.sequence',
|
|
'Voucher Sequence Payment', domain=[
|
|
('company', 'in',
|
|
[Eval('context', {}).get('company', -1), None]),
|
|
('sequence_type', '=',
|
|
Id('account_voucher', 'sequence_type_voucher')),
|
|
], required=True)
|
|
sequence_multipayment = fields.Many2One('ir.sequence',
|
|
'Voucher Sequence Multipayment', domain=[
|
|
('company', 'in',
|
|
[Eval('context', {}).get('company', -1), None]),
|
|
('sequence_type', '=',
|
|
Id('account_voucher', 'sequence_type_voucher')),
|
|
], required=True)
|
|
sequence_receipt = fields.Many2One('ir.sequence',
|
|
'Voucher Sequence Receipt', domain=[
|
|
('company', 'in',
|
|
[Eval('context', {}).get('company', -1), None]),
|
|
('sequence_type', '=',
|
|
Id('account_voucher', 'sequence_type_voucher')),
|
|
], required=True)
|
|
account = fields.Many2One('account.account',
|
|
'Account', domain=[
|
|
('company', 'in',
|
|
[Eval('context', {}).get('company', -1), None]),
|
|
])
|
|
code = fields.Char('Code')
|
|
require_party = fields.Boolean('Require Party')
|
|
party = fields.Many2One('party.party', 'Party',
|
|
states={
|
|
'required': Eval('require_party', False),
|
|
'invisible': ~Eval('require_party', False),
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(VoucherPayMode, cls).__setup__()
|
|
|
|
@staticmethod
|
|
def default_kind():
|
|
return 'both'
|
|
|
|
@staticmethod
|
|
def default_payment_type():
|
|
return 'transfer'
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company') or None
|
|
|
|
|
|
class Note(Workflow, ModelSQL, ModelView):
|
|
'Note'
|
|
__name__ = 'account.note'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', readonly=True, help="Voucher Number")
|
|
description = fields.Char('Description', states=_STATES_NOTE)
|
|
date = fields.Date('Date', required=True, states=_STATES_NOTE)
|
|
journal = fields.Many2One('account.journal', 'Journal', required=True,
|
|
states=_STATES_NOTE)
|
|
currency = fields.Many2One('currency.currency', 'Currency',
|
|
required=True, states=_STATES_NOTE)
|
|
company = fields.Many2One('company.company', 'Company',
|
|
required=True, states=_STATES_NOTE)
|
|
lines = fields.One2Many('account.note.line', 'note', 'Lines',
|
|
states=_STATES)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('cancelled', 'Cancelled'),
|
|
('posted', 'Posted'),
|
|
], 'State', select=True, readonly=True, translate=True)
|
|
move = fields.Many2One('account.move', 'Move', readonly=True)
|
|
origin = fields.Reference('Origin', selection='get_origin',
|
|
select=True, states=_STATES_NOTE, depends=['state'])
|
|
period = fields.Many2One('account.period', 'Period', domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
], states=_STATES_NOTE, select=True)
|
|
balance = fields.Function(fields.Numeric('Balance', readonly=True),
|
|
'get_balance')
|
|
method = fields.Selection([
|
|
('', ''),
|
|
('ifrs', 'Ifrs'),
|
|
('colgaap', 'Colgaap'),
|
|
], 'Method', states={
|
|
'readonly': Eval('state') == 'posted',
|
|
})
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(Note, cls).__setup__()
|
|
cls._buttons.update({
|
|
'draft': {
|
|
'invisible': Eval('state') == 'draft',
|
|
},
|
|
'post': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'cancel': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
'select_move_lines': {
|
|
'invisible': Eval('state') != 'draft',
|
|
},
|
|
})
|
|
cls._transitions |= set((
|
|
('draft', 'cancelled'),
|
|
('posted', 'cancelled'),
|
|
('posted', 'draft'),
|
|
('draft', 'posted'),
|
|
('cancelled', 'draft'),
|
|
))
|
|
cls._order.insert(0, ('date', 'DESC'))
|
|
cls._order.insert(1, ('number', 'DESC'))
|
|
|
|
@staticmethod
|
|
def default_state():
|
|
return 'draft'
|
|
|
|
@staticmethod
|
|
def default_currency():
|
|
Company = Pool().get('company.company')
|
|
company_id = Transaction().context.get('company')
|
|
if company_id:
|
|
return Company(company_id).currency.id
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_date():
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@classmethod
|
|
def copy(cls, notes, default=None):
|
|
if default is None:
|
|
default = {}
|
|
default = default.copy()
|
|
default['number'] = None
|
|
default['state'] = cls.default_state()
|
|
default['move'] = None
|
|
|
|
new_notes = []
|
|
for note in notes:
|
|
new_note, = super(Note, cls).copy(
|
|
[note], default=default
|
|
)
|
|
new_notes.append(new_note)
|
|
return new_notes
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('draft')
|
|
def draft(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('cancelled')
|
|
def cancel(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('posted')
|
|
def post(cls, records):
|
|
for note in records:
|
|
if not note.move:
|
|
cls._post_note(note)
|
|
note.set_number()
|
|
note.create_moves()
|
|
note.reconcile_lines()
|
|
|
|
@classmethod
|
|
@ModelView.button_action('account_voucher.wizard_select_move_lines')
|
|
def select_move_lines(cls, records):
|
|
pass
|
|
|
|
@classmethod
|
|
def delete(cls, notes):
|
|
for note in notes:
|
|
if note.state == 'posted' or note.number:
|
|
raise UserError(gettext('account_voucher.msg_delete_record'))
|
|
return super(Note, cls).delete(notes)
|
|
|
|
def set_number(self):
|
|
if self.number:
|
|
return
|
|
number = self.get_next_number()
|
|
self.write([self], {'number': number})
|
|
|
|
def get_balance(self, name=None):
|
|
res = []
|
|
for ln in self.lines:
|
|
res.append(ln.debit - ln.credit)
|
|
return sum(res)
|
|
|
|
def get_next_number(self, pattern=None):
|
|
pool = Pool()
|
|
Period = pool.get('account.period')
|
|
|
|
if pattern is None:
|
|
pattern = {}
|
|
else:
|
|
pattern = pattern.copy()
|
|
|
|
accounting_date = self.date
|
|
period_id = Period.find(
|
|
self.company.id, date=accounting_date)
|
|
|
|
period = Period(period_id)
|
|
fiscalyear = period.fiscalyear
|
|
pattern.setdefault('company', self.company.id)
|
|
pattern.setdefault('fiscalyear', fiscalyear.id)
|
|
pattern.setdefault('period', period.id)
|
|
|
|
for voucher_sequence in fiscalyear.voucher_sequences:
|
|
if voucher_sequence.match(pattern):
|
|
sequence = getattr(voucher_sequence, 'notes_sequence')
|
|
break
|
|
else:
|
|
Configuration = Pool().get('account.voucher_configuration')
|
|
config = Configuration.search([
|
|
('company', '=', Transaction().context.get('company'))
|
|
])
|
|
if not config or not config[0].voucher_notes_sequence:
|
|
raise UserError(gettext('account_voucher.msg_missing_voucher_configuration_company'))
|
|
sequence = getattr(config[0], 'voucher_notes_sequence')
|
|
with Transaction().set_context(date=accounting_date):
|
|
return sequence.get()
|
|
|
|
@classmethod
|
|
def _post_note(cls, note):
|
|
values = {'state': 'posted'}
|
|
amount = Decimal('0.0')
|
|
if not note.lines:
|
|
raise UserError(gettext('account_voucher.msg_post_empty_note', s=note.rec_name))
|
|
company = None
|
|
for line in note.lines:
|
|
amount += line.debit - line.credit
|
|
if not company:
|
|
company = line.account.company
|
|
if not company.currency.is_zero(amount):
|
|
raise UserError(gettext('account_voucher.msg_post_unbalanced_note'))
|
|
cls.write([note], values)
|
|
|
|
def create_moves(self):
|
|
pool = Pool()
|
|
Move = pool.get('account.move')
|
|
Period = pool.get('account.period')
|
|
move_lines_to_create = []
|
|
|
|
if not self.lines:
|
|
raise UserError(gettext('account_voucher.msg_missing_lines'))
|
|
|
|
if not self.period:
|
|
period_id = Period.find(self.company.id, date=self.date)
|
|
else:
|
|
period_id = self.period.id
|
|
|
|
for line in self.lines:
|
|
move_lines_to_create.extend(line.get_move_line())
|
|
|
|
move, = Move.create([{
|
|
'journal': self.journal.id,
|
|
'method': self.method,
|
|
'period': period_id,
|
|
'date': self.date,
|
|
'origin': str(self),
|
|
'state': 'draft',
|
|
'description': self.description,
|
|
'lines': [('create', move_lines_to_create)],
|
|
}])
|
|
|
|
self.write([self], {
|
|
'move': move.id,
|
|
})
|
|
|
|
Move.post([move])
|
|
|
|
for line in move.lines:
|
|
if line.origin and line.origin.move_line and line.origin.move_line.move.origin and\
|
|
line.origin.move_line.move.origin.__name__ == 'account.invoice':
|
|
invoice = line.origin.move_line.move.origin
|
|
if invoice.account != line.account:
|
|
continue
|
|
invoice.add_payment_lines({invoice: [line]})
|
|
|
|
def reconcile_lines(self):
|
|
MoveLine = Pool().get('account.move.line')
|
|
for ml in self.move.lines:
|
|
move_line = ml.origin.move_line
|
|
if not move_line or move_line.reconciliation or not move_line.party:
|
|
continue
|
|
if ml.reconciliation or not ml.party:
|
|
continue
|
|
if move_line.account.id == ml.account.id and \
|
|
move_line.party.id == ml.party.id and \
|
|
(move_line.debit + move_line.credit) - (ml.debit + ml.credit) == Decimal(0):
|
|
MoveLine.reconcile([move_line, ml])
|
|
|
|
@staticmethod
|
|
def _get_origin():
|
|
'Return list of Model names for origin Reference'
|
|
return []
|
|
|
|
@classmethod
|
|
def get_origin(cls):
|
|
Model = Pool().get('ir.model')
|
|
models = cls._get_origin()
|
|
models = Model.search([
|
|
('model', 'in', models),
|
|
])
|
|
return [(None, '')] + [(m.model, m.name) for m in models]
|
|
|
|
|
|
class NoteLine(ModelSQL, ModelView):
|
|
'Note Line'
|
|
__name__ = 'account.note.line'
|
|
note = fields.Many2One('account.note', 'Note', select=True, required=True)
|
|
debit = fields.Numeric('Debit', digits=(16, Eval('currency_digits', 2)),
|
|
required=True, depends=['currency_digits', 'credit'])
|
|
credit = fields.Numeric('Credit', digits=(16, Eval('currency_digits', 2)),
|
|
required=True, depends=['currency_digits', 'debit'])
|
|
account = fields.Many2One('account.account', 'Account', select=True,
|
|
domain=[('type', '!=', None)], required=True)
|
|
description = fields.Char('Description')
|
|
reference = fields.Char('Reference')
|
|
party = fields.Many2One('party.party', 'Party', select=True)
|
|
currency_digits = fields.Function(fields.Integer('Currency Digits'),
|
|
'get_currency_digits')
|
|
second_currency_digits = fields.Function(fields.Integer(
|
|
'Second Currency Digits'), 'get_currency_digits')
|
|
move_line = fields.Many2One('account.move.line', 'Move Line',
|
|
domain=[
|
|
('move.state', '=', 'posted'),
|
|
('state', '=', 'valid'),
|
|
('reconciliation', '=', None),
|
|
('account.reconcile', '=', True),
|
|
])
|
|
type = fields.Selection([
|
|
('tax', "Tax"),
|
|
('normal', "Normal"),
|
|
], "Type", required=True)
|
|
untaxed_amount = fields.Numeric('Untaxed Amount', digits=(16, 2),
|
|
states={'invisible': Eval('type') != 'tax'}, depends=['type'])
|
|
tax = fields.Many2One('account.tax', "Tax",
|
|
states={'invisible': Eval('type') != 'tax'}, depends=['type'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(NoteLine, cls).__setup__()
|
|
cls._order.insert(0, ('debit', 'DESC'))
|
|
cls._buttons.update({
|
|
'duplicate_wizard': {
|
|
'invisible': Eval('_parent_note', {}).get('state') != 'draft',
|
|
'depends': ['_parent_note.state', 'note']
|
|
}
|
|
})
|
|
|
|
@staticmethod
|
|
def default_type():
|
|
return 'normal'
|
|
|
|
@staticmethod
|
|
def default_currency_digits():
|
|
return 2
|
|
|
|
@staticmethod
|
|
def default_debit():
|
|
return Decimal(0)
|
|
|
|
@staticmethod
|
|
def default_credit():
|
|
return Decimal(0)
|
|
|
|
@classmethod
|
|
@ModelView.button_action('account_voucher.act_wizard_duplicate_line')
|
|
def duplicate_wizard(cls, lines):
|
|
pass
|
|
|
|
@fields.depends('debit', 'credit', 'type', 'untaxed_amount', 'tax')
|
|
def on_change_untaxed_amount(self):
|
|
Tax = Pool().get('account.tax')
|
|
if not self.untaxed_amount or not self.tax:
|
|
return
|
|
res, = Tax.compute([self.tax], self.untaxed_amount, 1)
|
|
amount = res['amount']
|
|
|
|
if self.tax.group == 'purchase' and amount > 0 or self.tax.group != 'purchase' and amount < 0:
|
|
self.credit = abs(amount)
|
|
else:
|
|
self.debit = abs(amount)
|
|
|
|
@fields.depends('debit', 'credit', 'type', 'untaxed_amount', 'tax')
|
|
def on_change_tax(self):
|
|
account = None
|
|
amount = None
|
|
if self.tax:
|
|
account = self.tax.invoice_account.id
|
|
if self.untaxed_amount:
|
|
Tax = Pool().get('account.tax')
|
|
res, = Tax.compute([self.tax], self.untaxed_amount, 1)
|
|
amount = res['amount']
|
|
|
|
self.account = account
|
|
if amount:
|
|
if self.tax.group == 'purchase' and amount > 0 or self.tax.group != 'purchase' and amount < 0:
|
|
self.credit = abs(amount)
|
|
else:
|
|
self.debit = abs(amount)
|
|
|
|
|
|
@fields.depends('account', 'debit', 'credit', 'note')
|
|
def on_change_debit(self):
|
|
if self.debit:
|
|
self.credit = Decimal('0.0')
|
|
|
|
@fields.depends('account', 'debit', 'credit', 'note')
|
|
def on_change_credit(self):
|
|
if self.credit:
|
|
self.debit = Decimal('0.0')
|
|
|
|
@fields.depends('account', 'debit', 'credit', 'note')
|
|
def on_change_account(self):
|
|
if self.account:
|
|
self.currency_digits = self.account.currency_digits
|
|
if self.account.second_currency:
|
|
self.second_currency_digits = \
|
|
self.account.second_currency.digits
|
|
|
|
@classmethod
|
|
def copy(cls, lines, default=None):
|
|
if default is None:
|
|
default = {}
|
|
if 'note' not in default:
|
|
default['note'] = None
|
|
return super(NoteLine, cls).copy(lines, default=default)
|
|
|
|
@classmethod
|
|
def get_currency_digits(cls, lines, names):
|
|
digits = {}
|
|
for line in lines:
|
|
for name in names:
|
|
digits.setdefault(name, {})
|
|
digits[name].setdefault(line.id, 2)
|
|
if name == 'currency_digits':
|
|
digits[name][line.id] = line.account.currency_digits
|
|
elif name == 'second_currency_digits':
|
|
second_currency = line.account.second_currency
|
|
if second_currency:
|
|
digits[name][line.id] = second_currency.digits
|
|
return digits
|
|
|
|
def get_move_line(self):
|
|
values = {}
|
|
values['description'] = self.description
|
|
values['debit'] = self.debit
|
|
values['credit'] = self.credit
|
|
values['reference'] = self.reference
|
|
values['account'] = self.account.id
|
|
values['origin'] = str(self)
|
|
if self.account.party_required and self.party:
|
|
values['party'] = self.party.id
|
|
if self.tax:
|
|
move_lines_tax = [{
|
|
'amount': self.untaxed_amount,
|
|
'tax': self.tax.id,
|
|
'type': 'base',
|
|
}]
|
|
values['tax_lines'] = [('create', move_lines_tax)]
|
|
return [values]
|
|
|
|
|
|
class NoteReport(Report):
|
|
'Account Note Report'
|
|
__name__ = 'account.note.report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
sum_credit = 0
|
|
sum_debit = 0
|
|
for obj in records:
|
|
for line in obj.lines:
|
|
sum_credit += line.credit
|
|
sum_debit += line.debit
|
|
report_context['sum_credit'] = sum_credit
|
|
report_context['sum_debit'] = sum_debit
|
|
|
|
return report_context
|
|
|
|
|
|
class FilteredVouchersReport(Report):
|
|
'Filtered Vouchers Control'
|
|
__name__ = 'account.voucher.filtered_vouchers_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
user = Pool().get('res.user')(Transaction().user)
|
|
report_context['company'] = user.company
|
|
return report_context
|
|
|
|
|
|
class VoucherFixNumberStart(ModelView):
|
|
'Voucher Fix Number Start'
|
|
__name__ = 'account_voucher.fix_number.start'
|
|
number = fields.Char('New Number', required=True)
|
|
|
|
|
|
class VoucherFixNumber(Wizard):
|
|
'Voucher Fix Number'
|
|
__name__ = 'account.voucher.fix_number'
|
|
start = StateView('account_voucher.fix_number.start',
|
|
'account_voucher.fix_number_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'accept', 'tryton-ok', default=True),
|
|
])
|
|
accept = StateTransition()
|
|
|
|
def transition_accept(self):
|
|
account_voucher = Table('account_voucher')
|
|
cursor = Transaction().connection.cursor()
|
|
id_ = Transaction().context['active_id']
|
|
if id_:
|
|
cursor.execute(*account_voucher.update(
|
|
columns=[account_voucher.number],
|
|
values=[self.start.number],
|
|
where=account_voucher.id == id_)
|
|
)
|
|
return 'end'
|
|
|
|
|
|
class NoteFixNumberStart(ModelView):
|
|
'Note Fix Number Start'
|
|
__name__ = 'account_voucher.note_fix_number.start'
|
|
number = fields.Char('New Number', required=True)
|
|
|
|
|
|
class NoteFixNumber(Wizard):
|
|
'Note Fix Number'
|
|
__name__ = 'account.note.fix_number'
|
|
start = StateView('account_voucher.note_fix_number.start',
|
|
'account_voucher.note_fix_number_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'accept', 'tryton-ok', default=True),
|
|
])
|
|
accept = StateTransition()
|
|
|
|
def transition_accept(self):
|
|
account_note = Table('account_note')
|
|
cursor = Transaction().connection.cursor()
|
|
id_ = Transaction().context['active_id']
|
|
if id_:
|
|
cursor.execute(*account_note.update(
|
|
columns=[account_note.number],
|
|
values=[self.start.number],
|
|
where=account_note.id == id_)
|
|
)
|
|
return 'end'
|
|
|
|
|
|
class SelectMoveLinesAsk(ModelView):
|
|
'Select Lines Assistant'
|
|
__name__ = 'account.voucher.select_move_lines.ask'
|
|
lines = fields.Many2Many('account.move.line', None, None,
|
|
'Account Moves Lines')
|
|
|
|
|
|
class SelectMoveLines(Wizard):
|
|
'Select Lines'
|
|
__name__ = 'account.voucher.select_move_lines'
|
|
start_state = 'search_lines'
|
|
search_lines = StateTransition()
|
|
start = StateView('account.voucher.select_move_lines.ask',
|
|
'account_voucher.view_select_move_lines_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Add', 'add_lines', 'tryton-ok', default=True),
|
|
])
|
|
add_lines = StateTransition()
|
|
|
|
def transition_search_lines(self):
|
|
Select = Pool().get('account.voucher.select_move_lines.ask')
|
|
|
|
line_domain = [
|
|
('account.reconcile', '=', True),
|
|
('state', '=', 'valid'),
|
|
('reconciliation', '=', None),
|
|
('move.state', '=', 'posted'),
|
|
]
|
|
Select.lines.domain = line_domain
|
|
return 'start'
|
|
|
|
def transition_add_lines(self):
|
|
pool = Pool()
|
|
Line = pool.get('account.note.line')
|
|
Model = pool.get(Transaction().context.get('active_model'))
|
|
note = Model(Transaction().context.get('active_id'))
|
|
|
|
lines_to_create = []
|
|
for line in self.start.lines:
|
|
debit = line.credit
|
|
credit = line.debit
|
|
|
|
lines_to_create.append({
|
|
'note': note.id,
|
|
'description': line.description,
|
|
'account': line.account.id,
|
|
'debit': debit,
|
|
'credit': credit,
|
|
'party': line.party.id if line.party else None,
|
|
'move_line': line.id,
|
|
})
|
|
|
|
Line.create(lines_to_create)
|
|
return 'end'
|
|
|
|
|
|
class VoucherTemplateParty(ModelSQL):
|
|
'Voucher Template - Party'
|
|
__name__ = 'account.voucher_template-party'
|
|
_table = 'account_voucher_template_party'
|
|
voucher_template = fields.Many2One('account.voucher_template',
|
|
'Vocuher Template', select=True, required=True, ondelete='CASCADE')
|
|
party = fields.Many2One('party.party', 'Party',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
|
|
|
|
class VoucherTemplateAccount(ModelSQL):
|
|
'Voucher Template - Account'
|
|
__name__ = 'account.voucher_template-account'
|
|
_table = 'account_voucher_template_account'
|
|
voucher_template = fields.Many2One('account.voucher_template',
|
|
'Vocuher Template', select=True, required=True, ondelete='CASCADE')
|
|
account = fields.Many2One('account.account', 'Account',
|
|
ondelete='CASCADE', select=True, required=True)
|
|
|
|
|
|
class CreateVoucherStart(ModelView):
|
|
'Create Voucher Start'
|
|
__name__ = 'account_voucher.template_create_voucher.start'
|
|
template = fields.Many2One('account.voucher_template', 'Template',
|
|
required=True)
|
|
start_date = fields.Date('Start Date')
|
|
end_date = fields.Date('End Date')
|
|
reference = fields.Char('Reference')
|
|
party = fields.Many2One('party.party', 'Party')
|
|
|
|
@fields.depends('reference', 'party')
|
|
def on_change_reference(self):
|
|
if self.reference:
|
|
Identifier = Pool().get('party.identifier')
|
|
identifiers = Identifier.search([
|
|
('code', '=', self.reference),
|
|
])
|
|
if identifiers:
|
|
self.party = identifiers[0].party.id
|
|
|
|
|
|
class CreateVoucher(Wizard):
|
|
'Create Voucher from Template'
|
|
__name__ = 'account_voucher.template_create_voucher'
|
|
start = StateView('account_voucher.template_create_voucher.start',
|
|
'account_voucher.template_create_voucher_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'accept', 'tryton-ok', default=True),
|
|
])
|
|
accept = StateTransition()
|
|
|
|
def transition_accept(self):
|
|
pool = Pool()
|
|
Template = pool.get('account.voucher_template')
|
|
Voucher = pool.get('account.voucher')
|
|
VoucherLine = pool.get('account.voucher.line')
|
|
MoveLine = pool.get('account.move.line')
|
|
template = Template(self.start.template)
|
|
today = date.today()
|
|
to_create = {
|
|
'voucher_type': template.voucher_type,
|
|
'date': today,
|
|
'description': template.name,
|
|
'payment_mode': template.payment_mode.id,
|
|
'state': 'draft',
|
|
'account': template.payment_mode.account.id,
|
|
'journal': template.payment_mode.journal.id,
|
|
'method_counterpart': 'one_line',
|
|
}
|
|
# if template.voucher_type != 'multipayment':
|
|
if self.start.party:
|
|
to_create['party'] = self.start.party
|
|
if self.start.reference:
|
|
to_create['reference'] = self.start.reference
|
|
accounts_ids = [l.account.id for l in template.lines if l.account]
|
|
if not accounts_ids:
|
|
return 'end'
|
|
|
|
if template.method_lines == 'by_accrual':
|
|
if template.voucher_type != 'multipayment':
|
|
voucher_lines = VoucherLine.search([
|
|
('move_line', '!=', None),
|
|
('voucher.date', '>=', self.start.start_date),
|
|
('voucher.date', '<=', self.start.end_date),
|
|
])
|
|
not_accounts_ids = [ln.move_line.id for ln in voucher_lines if ln.move_line]
|
|
for party in template.parties:
|
|
to_create['party'] = party.id
|
|
dom_ = [
|
|
('account.reconcile', '=', True),
|
|
('reconciliation', '=', None),
|
|
('move.state', '=', 'posted'),
|
|
('party', '=', party),
|
|
('state', '=', 'valid'),
|
|
('move.date', '>=', self.start.start_date),
|
|
('move.date', '<=', self.start.end_date),
|
|
]
|
|
if not_accounts_ids:
|
|
dom_.append(('id', 'not in', not_accounts_ids))
|
|
|
|
dom_.append(('account', 'in', accounts_ids))
|
|
move_lines = MoveLine.search(dom_)
|
|
if move_lines:
|
|
voucher, = Voucher.create([to_create])
|
|
voucher.add_lines(move_lines)
|
|
else:
|
|
voucher, = Voucher.create([to_create])
|
|
dom_ = [
|
|
('account.reconcile', '=', True),
|
|
('reconciliation', '=', None),
|
|
('move.state', '=', 'posted'),
|
|
('party', 'in', template.parties),
|
|
('state', '=', 'valid'),
|
|
('move.date', '>=', self.start.start_date),
|
|
('move.date', '<=', self.start.end_date),
|
|
]
|
|
dom_.append(('account', 'in', accounts_ids))
|
|
move_lines = MoveLine.search(dom_)
|
|
voucher.add_lines(move_lines)
|
|
else:
|
|
self.create_lines(voucher)
|
|
return 'end'
|
|
|
|
def create_lines(self, voucher):
|
|
# Create voucher lines from a list of account move line
|
|
Line = Pool().get('account.voucher.line')
|
|
lines_to_create = []
|
|
for line in self.start.template.lines:
|
|
lines_to_create.append({
|
|
'voucher': voucher.id,
|
|
'detail': line.detail,
|
|
'account': line.account.id,
|
|
'amount': line.amount,
|
|
})
|
|
Line.create(lines_to_create)
|
|
voucher.on_change_lines()
|
|
voucher.save()
|
|
|
|
|
|
class VoucherSheetStart(ModelView):
|
|
'Voucher Sheet Start'
|
|
__name__ = 'account_voucher.sheet.start'
|
|
start_date = fields.Date('Start Date', required=True)
|
|
end_date = fields.Date('End Date', required=True)
|
|
voucher_type = fields.Selection(VOUCHER_TYPE,
|
|
'Type', required=True)
|
|
payment_mode = fields.Many2One('account.voucher.paymode', 'Payment Mode')
|
|
include_draft = fields.Boolean('Include Draft')
|
|
|
|
@staticmethod
|
|
def default_start_date():
|
|
return date.today()
|
|
|
|
@staticmethod
|
|
def default_end_date():
|
|
return date.today()
|
|
|
|
@staticmethod
|
|
def default_voucher_type():
|
|
return 'receipt'
|
|
|
|
|
|
class VoucherSheet(Wizard):
|
|
'Voucher Sheet'
|
|
__name__ = 'account_voucher.sheet'
|
|
start = StateView('account_voucher.sheet.start',
|
|
'account_voucher.voucher_sheet_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'print_', 'tryton-ok', default=True),
|
|
])
|
|
print_ = StateReport('account_voucher.sheet_report')
|
|
|
|
def do_print_(self, action):
|
|
payment_mode_id = None
|
|
if self.start.payment_mode:
|
|
payment_mode_id = self.start.payment_mode.id
|
|
data = {
|
|
'start_date': self.start.start_date,
|
|
'end_date': self.start.end_date,
|
|
'voucher_type': self.start.voucher_type,
|
|
'payment_mode': payment_mode_id,
|
|
'include_draft': self.start.include_draft,
|
|
}
|
|
return action, data
|
|
|
|
def transition_print_(self):
|
|
return 'end'
|
|
|
|
|
|
class VoucherSheetReport(Report):
|
|
'Voucher Sheet Report'
|
|
__name__ = 'account_voucher.sheet_report'
|
|
|
|
@classmethod
|
|
def get_context(cls, records, header, data):
|
|
report_context = super().get_context(records, header, data)
|
|
pool = Pool()
|
|
Voucher = pool.get('account.voucher')
|
|
Company = pool.get('company.company')
|
|
dom_search = [
|
|
('date', '>=', data['start_date']),
|
|
('date', '<=', data['end_date']),
|
|
('voucher_type', '=', data['voucher_type']),
|
|
]
|
|
if data['payment_mode']:
|
|
dom_search.append(
|
|
('payment_mode', '=', data['payment_mode']),
|
|
)
|
|
if not data['include_draft']:
|
|
dom_search.append(
|
|
('state', 'in', ['processed', 'posted']),
|
|
)
|
|
else:
|
|
dom_search.append(
|
|
('state', '!=', 'cancel'),
|
|
)
|
|
|
|
records = Voucher.search(dom_search)
|
|
report_context['records'] = records
|
|
report_context['company'] = Company(Transaction().context.get('company'))
|
|
report_context['sum_amount'] = sum([obj.amount_to_pay for obj in records])
|
|
return report_context
|
|
|
|
|
|
class AddZeroAdjustmentStart(ModelView):
|
|
'Add Zero Adjustment Start'
|
|
__name__ = 'account_voucher.add_zero_adjustment.start'
|
|
final_amount = fields.Numeric('Final Amount', required=True)
|
|
|
|
|
|
class AddZeroAdjustment(Wizard):
|
|
'Add Zero Adjustment Start'
|
|
__name__ = 'account_voucher.add_zero_adjustment'
|
|
start = StateView('account_voucher.add_zero_adjustment.start',
|
|
'account_voucher.add_zero_adjustment_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'accept', 'tryton-ok', default=True),
|
|
])
|
|
accept = StateTransition()
|
|
|
|
def transition_accept(self):
|
|
Voucher = Pool().get('account.voucher')
|
|
VoucherLine = Pool().get('account.voucher.line')
|
|
id_ = Transaction().context['active_id']
|
|
voucher = Voucher(id_)
|
|
line_to_create = self.get_line_value()
|
|
VoucherLine.create([line_to_create])
|
|
voucher.on_change_lines()
|
|
voucher.save()
|
|
return 'end'
|
|
|
|
def get_line_value(self):
|
|
Voucher = Pool().get('account.voucher')
|
|
Config = Pool().get('account.voucher_configuration')
|
|
id_ = Transaction().context['active_id']
|
|
voucher = Voucher(id_)
|
|
balance = self.start.final_amount - voucher.amount_to_pay
|
|
|
|
config = Config.get_configuration()
|
|
if not config.account_adjust_expense or \
|
|
not config.account_adjust_income or voucher.state != 'draft':
|
|
return 'end'
|
|
|
|
if voucher.voucher_type in ['payment', 'multipayment']:
|
|
if balance > _ZERO:
|
|
account = config.account_adjust_expense
|
|
else:
|
|
account = config.account_adjust_income
|
|
else:
|
|
if balance > _ZERO:
|
|
account = config.account_adjust_income
|
|
else:
|
|
account = config.account_adjust_expense
|
|
|
|
return {
|
|
'voucher': id_,
|
|
'account': account.id,
|
|
'amount': balance,
|
|
'detail': account.name
|
|
}
|
|
|
|
class TaxesConsolidationStart(ModelView):
|
|
'Taxes Consolidation Start'
|
|
__name__ = 'account_voucher.taxes_consolidation.start'
|
|
company = fields.Many2One('company.company', 'Company',
|
|
required=True)
|
|
journal = fields.Many2One('account.journal', 'Journal',
|
|
required=True)
|
|
fiscalyear = fields.Many2One('account.fiscalyear',
|
|
'Fiscal Year', required=True, domain=[
|
|
('state', '=', 'open'),
|
|
])
|
|
periods = fields.Many2Many('account.period', None, None,
|
|
'Periods', required=True, domain=[
|
|
('type', '=', 'standard'),
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
], depends=['fiscalyear'])
|
|
payoff_account = fields.Many2One('account.account',
|
|
'Payoff Account', required=True, domain=[
|
|
('type', '==', None),
|
|
])
|
|
description = fields.Char('Description', required=True)
|
|
party = fields.Many2One('party.party', 'Party',
|
|
required=True)
|
|
taxes = fields.Many2Many('account.tax', None, None,
|
|
'Taxes', required=True)
|
|
|
|
@staticmethod
|
|
def default_company():
|
|
return Transaction().context.get('company')
|
|
|
|
@staticmethod
|
|
def default_fiscalyear():
|
|
FiscalYear = Pool().get('account.fiscalyear')
|
|
return FiscalYear.find(
|
|
Transaction().context.get('company'), exception=False)
|
|
|
|
@staticmethod
|
|
def default_journal():
|
|
Journal = Pool().get('account.journal')
|
|
journals = Journal.search([
|
|
('type', '=', 'general')
|
|
])
|
|
if journals:
|
|
return journals[0].id
|
|
|
|
|
|
class TaxesConsolidation(Wizard):
|
|
'Taxes Consolidation'
|
|
__name__ = 'account_voucher.taxes_consolidation'
|
|
start = StateView('account_voucher.taxes_consolidation.start',
|
|
'account_voucher.taxes_consolidation_start_view_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Ok', 'create_', 'tryton-print', default=True),
|
|
])
|
|
create_ = StateTransition()
|
|
done = StateView('account_voucher.taxes_consolidation.done',
|
|
'account_voucher.taxes_consolidation_done_view_form', [
|
|
Button('Done', 'end', 'tryton-ok', default=True),
|
|
])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(TaxesConsolidation, cls).__setup__()
|
|
|
|
def default_done(self, fields):
|
|
return {'result': self.result}
|
|
|
|
def transition_create_(self):
|
|
pool = Pool()
|
|
Note = pool.get('account.note')
|
|
Move = pool.get('account.move')
|
|
MoveLine = pool.get('account.move.line')
|
|
NoteLine = pool.get('account.note.line')
|
|
|
|
taxes_accounts = []
|
|
for tax in self.start.taxes:
|
|
if not tax.invoice_account.reconcile:
|
|
raise UserError(gettext(
|
|
'account_voucher.msg_tax_account_no_reconcile',
|
|
invoice=tax.invoice_account.name,
|
|
tax=tax.name))
|
|
if not tax.credit_note_account.reconcile:
|
|
raise UserError(gettext(
|
|
'account_voucher.msg_tax_account_no_reconcile',
|
|
invoice=tax.invoice_account.name,
|
|
tax=tax.name))
|
|
|
|
taxes_accounts.extend([tax.invoice_account.id, tax.credit_note_account.id])
|
|
periods_ids = [p.id for p in self.start.periods]
|
|
|
|
moves_draft = Move.search([
|
|
('period', 'in', periods_ids),
|
|
('state', '=', 'draft'),
|
|
])
|
|
|
|
if moves_draft:
|
|
raise UserError(gettext('account_voucher.msg_moves_in_draft'))
|
|
|
|
move_lines = MoveLine.search([
|
|
('move.period', 'in', periods_ids),
|
|
('account', 'in', taxes_accounts),
|
|
('reconciliation', '=', None),
|
|
('account.reconcile', '=', True),
|
|
('move.state', '=', 'posted'),
|
|
('state', '=', 'valid'),
|
|
])
|
|
max_period = None
|
|
for p in self.start.periods:
|
|
if max_period:
|
|
if p.end_date > max_period.end_date:
|
|
max_period = p
|
|
else:
|
|
max_period = p
|
|
|
|
note, = Note.create([{
|
|
'period': max_period.id,
|
|
'date': max_period.end_date,
|
|
'journal': self.start.journal.id,
|
|
'state': 'draft',
|
|
'description': self.start.description,
|
|
}])
|
|
|
|
balance = []
|
|
lines_to_create = []
|
|
note_id = note.id
|
|
for line in move_lines:
|
|
lines_to_create.append({
|
|
'account': line.account.id,
|
|
'party': line.party.id if line.party else None,
|
|
'debit': line.credit,
|
|
'credit': line.debit,
|
|
'description': line.description,
|
|
'note': note_id,
|
|
'move_line': line.id,
|
|
})
|
|
if line.account.party_required and not line.party:
|
|
raise UserError(gettext(
|
|
'account_voucher.msg_line_party_required',
|
|
s=line.account.code or '[-]'
|
|
))
|
|
balance.append(line.debit - line.credit)
|
|
NoteLine.create(lines_to_create)
|
|
|
|
payable_line = {
|
|
'account': self.start.payoff_account.id,
|
|
'note': note.id,
|
|
'debit': _ZERO,
|
|
'credit': _ZERO,
|
|
'party': self.start.party.id,
|
|
}
|
|
|
|
amount = sum(balance)
|
|
if amount > _ZERO:
|
|
payable_line['debit'] = abs(amount)
|
|
else:
|
|
payable_line['credit'] = abs(amount)
|
|
|
|
NoteLine.create([payable_line])
|
|
note.set_number()
|
|
self.result = UserError(gettext(
|
|
'account_voucher.msg_note_created',
|
|
s=note.number
|
|
))
|
|
return 'done'
|
|
|
|
|
|
class TaxesConsolidationDone(ModelView):
|
|
'Taxes Consolidation Done'
|
|
__name__ = 'account_voucher.taxes_consolidation.done'
|
|
result = fields.Text('Result', readonly=True)
|
|
|
|
|
|
class ReceiptRelation(Workflow, ModelSQL, ModelView):
|
|
'Receipt Relation'
|
|
__name__ = 'account.voucher.receipt_relation'
|
|
_rec_name = 'number'
|
|
number = fields.Char('Number', readonly=True, help="Voucher Number")
|
|
party = fields.Many2One('party.party', 'Party', select=True,
|
|
states={
|
|
'readonly': Eval('state') != 'draft',
|
|
'required': Eval('voucher_type') != 'multipayment',
|
|
}, context={'party': Eval('party')})
|
|
date = fields.Date('Date', required=True, states=_STATES)
|
|
|
|
|
|
class AdvanceVoucherStart(ModelView):
|
|
'Advance Voucher Start'
|
|
__name__ = 'account_voucher.advance_voucher.start'
|
|
payment_mode = fields.Many2One('account.voucher.paymode', 'Payment Mode',
|
|
domain=[], required=True)
|
|
payment_amount = fields.Numeric('Payment amount', digits=(16, 2),
|
|
required=True)
|
|
party = fields.Many2One('party.party', 'Party', required=True)
|
|
pay_date = fields.Date('Pay Date', required=True)
|
|
kind = fields.Selection([
|
|
('advance', 'Advance'),
|
|
('pay', 'Pay'),
|
|
],'Kind', required=True)
|
|
reference = fields.Char('Reference')
|
|
|
|
@classmethod
|
|
def default_pay_date(cls):
|
|
Date = Pool().get('ir.date')
|
|
return Date.today()
|
|
|
|
@classmethod
|
|
def default_kind(cls):
|
|
return 'advance'
|
|
|
|
@classmethod
|
|
def default_party(cls):
|
|
active_model = Transaction().context.get('active_model')
|
|
ActiveModel = Pool().get(active_model)
|
|
active_id = Transaction().context.get('active_id', False)
|
|
record = ActiveModel(active_id)
|
|
if hasattr(record, 'party') and record.party:
|
|
return record.party.id
|
|
|
|
|
|
class AdvanceVoucher(Wizard):
|
|
'Advance Voucher'
|
|
__name__ = 'account_voucher.advance_voucher'
|
|
start = StateView('account_voucher.advance_voucher.start',
|
|
'account_voucher.view_voucher_advance_form', [
|
|
Button('Cancel', 'end', 'tryton-cancel'),
|
|
Button('Pay', 'pay_', 'tryton-ok', default=True),
|
|
])
|
|
pay_ = StateTransition()
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(AdvanceVoucher, cls).__setup__()
|
|
|
|
def transition_pay_(self):
|
|
pool = Pool()
|
|
active_model = Transaction().context.get('active_model')
|
|
Voucher = pool.get('account.voucher')
|
|
Config = pool.get('account.voucher_configuration')
|
|
ModelOrigin = pool.get(active_model)
|
|
config = Config.get_configuration()
|
|
active_id = Transaction().context.get('active_id', False)
|
|
ModelVoucher = pool.get(active_model + '-account.voucher')
|
|
record_origin = ModelOrigin(active_id)
|
|
|
|
description = None
|
|
if hasattr(record_origin, 'number'):
|
|
description = record_origin.number
|
|
elif hasattr(record_origin, 'reference'):
|
|
description = record_origin.reference
|
|
|
|
form = self.start
|
|
|
|
if form.kind == 'advance':
|
|
account_id = config.customer_advance_account.id
|
|
detail = config.prepayment_description
|
|
else:
|
|
account_id = form.party.account_receivable_used
|
|
detail = ''
|
|
|
|
voucher, = Voucher.create([{
|
|
'party': form.party.id,
|
|
'payment_mode': form.payment_mode.id,
|
|
'date': form.pay_date,
|
|
'description': description,
|
|
'reference': form.reference,
|
|
'state': 'draft',
|
|
'voucher_type': 'receipt',
|
|
'account': form.payment_mode.account.id,
|
|
'journal': form.payment_mode.journal.id,
|
|
'method_counterpart': 'one_line',
|
|
'amount_to_pay': form.payment_amount,
|
|
'lines': [
|
|
('create', [{
|
|
'account': account_id,
|
|
'detail': detail,
|
|
'amount': form.payment_amount,
|
|
}])
|
|
]
|
|
}])
|
|
voucher.save()
|
|
Voucher.process([voucher])
|
|
Voucher.post([voucher])
|
|
if hasattr(ModelVoucher, 'set_voucher_origin'):
|
|
ModelVoucher.set_voucher_origin(voucher.id, record_origin.id)
|
|
if hasattr(ModelOrigin, 'reconcile'):
|
|
ModelOrigin.reconcile(record_origin, voucher)
|
|
return 'end'
|
|
|
|
|
|
class ForwardVoucherMail(Wizard):
|
|
'Forward Purchase Mail'
|
|
__name__ = 'account_voucher.forward_mail'
|
|
start_state = 'forward_mail'
|
|
forward_mail = StateTransition()
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(ForwardVoucherMail, cls).__setup__()
|
|
|
|
def transition_forward_mail(self):
|
|
pool = Pool()
|
|
Voucher = pool.get('account.voucher')
|
|
ids = Transaction().context['active_ids']
|
|
if ids:
|
|
voucher = Voucher(ids[0])
|
|
if voucher.state == 'posted' and not voucher.sended_mail:
|
|
voucher.send_payment_receipt_emails()
|
|
return 'end'
|
|
|
|
|
|
class DuplicateLineNote(Wizard):
|
|
'Duplicate Line Note'
|
|
__name__ = 'account.voucher.duplicate_lines'
|
|
start_state = 'duplicate_lines'
|
|
duplicate_lines = StateTransition()
|
|
|
|
def transition_duplicate_lines(self):
|
|
active_id = Transaction().context.get('active_id')
|
|
NoteLine = Pool().get('account.note.line')
|
|
line = NoteLine(active_id)
|
|
default = {'note': line.note}
|
|
NoteLine.copy([line], default=default)
|
|
return 'end'
|