trytonpsk-account_voucher/voucher.py

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'