We add a process button for account admin to allow the user to force the process in case the queued task has failed. issue10215 review361451002
407 lines
15 KiB
Python
407 lines
15 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 collections import OrderedDict
|
|
|
|
from sql import Literal
|
|
from sql.conditionals import Coalesce
|
|
|
|
from trytond.i18n import gettext
|
|
from trytond.model import (fields, ModelView, ModelSQL, Workflow, MatchMixin,
|
|
sequence_ordered)
|
|
from trytond.pyson import Eval, If, Bool, Id
|
|
from trytond.pool import Pool, PoolMeta
|
|
from trytond.tools import grouped_slice
|
|
from trytond.transaction import Transaction
|
|
|
|
from trytond.modules.account.exceptions import ClosePeriodError
|
|
from trytond.modules.company.model import CompanyValueMixin
|
|
|
|
|
|
class Configuration(metaclass=PoolMeta):
|
|
__name__ = 'account.configuration'
|
|
|
|
default_customer_payment_term = fields.MultiValue(
|
|
fields.Many2One(
|
|
'account.invoice.payment_term', "Default Customer Payment Term"))
|
|
|
|
@classmethod
|
|
def multivalue_model(cls, field):
|
|
pool = Pool()
|
|
if field in 'default_customer_payment_term':
|
|
return pool.get('account.configuration.default_payment_term')
|
|
return super().multivalue_model(field)
|
|
|
|
|
|
class ConfigurationDefaultPaymentTerm(ModelSQL, CompanyValueMixin):
|
|
"Account Configuration Default Payment Term"
|
|
__name__ = 'account.configuration.default_payment_term'
|
|
|
|
default_customer_payment_term = fields.Many2One(
|
|
'account.invoice.payment_term', "Default Customer Payment Term")
|
|
|
|
|
|
class FiscalYear(metaclass=PoolMeta):
|
|
__name__ = 'account.fiscalyear'
|
|
invoice_sequences = fields.One2Many(
|
|
'account.fiscalyear.invoice_sequence', 'fiscalyear',
|
|
"Invoice Sequences",
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
depends=['company'])
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
pool = Pool()
|
|
Sequence = pool.get('account.fiscalyear.invoice_sequence')
|
|
sequence = Sequence.__table__()
|
|
sql_table = cls.__table__()
|
|
|
|
super(FiscalYear, cls).__register__(module_name)
|
|
cursor = Transaction().connection.cursor()
|
|
table = cls.__table_handler__(module_name)
|
|
|
|
# Migration from 4.2: Use Match pattern for sequences
|
|
if (table.column_exist('in_invoice_sequence')
|
|
and table.column_exist('in_credit_note_sequence')
|
|
and table.column_exist('out_invoice_sequence')
|
|
and table.column_exist('out_credit_note_sequence')):
|
|
cursor.execute(*sequence.insert(columns=[
|
|
sequence.sequence, sequence.fiscalyear,
|
|
sequence.company,
|
|
sequence.out_invoice_sequence,
|
|
sequence.out_credit_note_sequence,
|
|
sequence.in_invoice_sequence,
|
|
sequence.in_credit_note_sequence],
|
|
values=sql_table.select(
|
|
Literal(20), sql_table.id,
|
|
sql_table.company,
|
|
sql_table.out_invoice_sequence,
|
|
sql_table.out_credit_note_sequence,
|
|
sql_table.in_invoice_sequence,
|
|
sql_table.in_credit_note_sequence)))
|
|
table.drop_column('out_invoice_sequence')
|
|
table.drop_column('out_credit_note_sequence')
|
|
table.drop_column('in_invoice_sequence')
|
|
table.drop_column('in_credit_note_sequence')
|
|
|
|
@staticmethod
|
|
def default_invoice_sequences():
|
|
if Transaction().user == 0:
|
|
return []
|
|
return [{}]
|
|
|
|
|
|
class Period(metaclass=PoolMeta):
|
|
__name__ = 'account.period'
|
|
|
|
@classmethod
|
|
def __register__(cls, module_name):
|
|
pool = Pool()
|
|
Sequence = pool.get('account.fiscalyear.invoice_sequence')
|
|
FiscalYear = pool.get('account.fiscalyear')
|
|
sequence = Sequence.__table__()
|
|
fiscalyear = FiscalYear.__table__()
|
|
sql_table = cls.__table__()
|
|
|
|
super(Period, cls).__register__(module_name)
|
|
cursor = Transaction().connection.cursor()
|
|
table = cls.__table_handler__(module_name)
|
|
|
|
# Migration from 4.2: Use Match pattern for sequences
|
|
if (table.column_exist('in_invoice_sequence')
|
|
and table.column_exist('in_credit_note_sequence')
|
|
and table.column_exist('out_invoice_sequence')
|
|
and table.column_exist('out_credit_note_sequence')):
|
|
cursor.execute(*sequence.insert(columns=[
|
|
sequence.sequence, sequence.fiscalyear,
|
|
sequence.company, sequence.period,
|
|
sequence.out_invoice_sequence,
|
|
sequence.out_credit_note_sequence,
|
|
sequence.in_invoice_sequence,
|
|
sequence.in_credit_note_sequence],
|
|
values=sql_table.join(fiscalyear,
|
|
condition=(fiscalyear.id == sql_table.fiscalyear)
|
|
).select(
|
|
Literal(10), sql_table.fiscalyear,
|
|
fiscalyear.company, sql_table.id,
|
|
Coalesce(sql_table.out_invoice_sequence,
|
|
fiscalyear.out_invoice_sequence),
|
|
Coalesce(sql_table.out_credit_note_sequence,
|
|
fiscalyear.out_credit_note_sequence),
|
|
Coalesce(sql_table.in_invoice_sequence,
|
|
fiscalyear.in_invoice_sequence),
|
|
Coalesce(sql_table.in_credit_note_sequence,
|
|
fiscalyear.in_credit_note_sequence))))
|
|
table.drop_column('out_invoice_sequence')
|
|
table.drop_column('out_credit_note_sequence')
|
|
table.drop_column('in_invoice_sequence')
|
|
table.drop_column('in_credit_note_sequence')
|
|
|
|
@classmethod
|
|
@ModelView.button
|
|
@Workflow.transition('close')
|
|
def close(cls, periods):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
company_ids = list({p.company.id for p in periods})
|
|
invoices = Invoice.search([
|
|
('company', 'in', company_ids),
|
|
('state', '=', 'posted'),
|
|
('move', '=', None),
|
|
])
|
|
if invoices:
|
|
names = ', '.join(i.rec_name for i in invoices[:5])
|
|
if len(invoices) > 5:
|
|
names += '...'
|
|
raise ClosePeriodError(
|
|
gettext('account_invoice.msg_close_period_non_posted_invoices',
|
|
invoices=names))
|
|
super().close(periods)
|
|
|
|
|
|
class InvoiceSequence(sequence_ordered(), ModelSQL, ModelView, MatchMixin):
|
|
'Invoice Sequence'
|
|
__name__ = 'account.fiscalyear.invoice_sequence'
|
|
company = fields.Many2One('company.company', "Company", required=True)
|
|
fiscalyear = fields.Many2One(
|
|
'account.fiscalyear', "Fiscal Year", required=True, ondelete='CASCADE',
|
|
domain=[
|
|
('company', '=', Eval('company', -1)),
|
|
],
|
|
depends=['company'])
|
|
period = fields.Many2One('account.period', 'Period',
|
|
domain=[
|
|
('fiscalyear', '=', Eval('fiscalyear')),
|
|
('type', '=', 'standard'),
|
|
],
|
|
depends=['fiscalyear'])
|
|
in_invoice_sequence = fields.Many2One('ir.sequence.strict',
|
|
'Supplier Invoice Sequence', required=True,
|
|
domain=[
|
|
('sequence_type', '=',
|
|
Id('account_invoice', 'sequence_type_account_invoice')),
|
|
('company', '=', Eval('company')),
|
|
],
|
|
depends=['company'])
|
|
in_credit_note_sequence = fields.Many2One('ir.sequence.strict',
|
|
'Supplier Credit Note Sequence', required=True,
|
|
domain=[
|
|
('sequence_type', '=',
|
|
Id('account_invoice', 'sequence_type_account_invoice')),
|
|
('company', '=', Eval('company')),
|
|
],
|
|
depends=['company'])
|
|
out_invoice_sequence = fields.Many2One('ir.sequence.strict',
|
|
'Customer Invoice Sequence', required=True,
|
|
domain=[
|
|
('sequence_type', '=',
|
|
Id('account_invoice', 'sequence_type_account_invoice')),
|
|
('company', '=', Eval('company')),
|
|
],
|
|
depends=['company'])
|
|
out_credit_note_sequence = fields.Many2One('ir.sequence.strict',
|
|
'Customer Credit Note Sequence', required=True,
|
|
domain=[
|
|
('sequence_type', '=',
|
|
Id('account_invoice', 'sequence_type_account_invoice')),
|
|
('company', '=', Eval('company')),
|
|
],
|
|
depends=['company'])
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(InvoiceSequence, cls).__setup__()
|
|
cls._order.insert(0, ('fiscalyear', 'ASC'))
|
|
|
|
@classmethod
|
|
def default_company(cls):
|
|
return Transaction().context.get('company')
|
|
|
|
|
|
class Move(metaclass=PoolMeta):
|
|
__name__ = 'account.move'
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
return super(Move, cls)._get_origin() + ['account.invoice']
|
|
|
|
|
|
class MoveLine(metaclass=PoolMeta):
|
|
__name__ = 'account.move.line'
|
|
|
|
invoice_payment = fields.Function(fields.Many2One(
|
|
'account.invoice', "Invoice Payment",
|
|
domain=[
|
|
('account', '=', Eval('account', -1)),
|
|
If(Bool(Eval('party')),
|
|
('party', '=', Eval('party')),
|
|
(),
|
|
),
|
|
],
|
|
states={
|
|
'invisible': Bool(Eval('reconciliation')),
|
|
},
|
|
depends=['account', 'party', 'reconciliation']),
|
|
'get_invoice_payment',
|
|
setter='set_invoice_payment',
|
|
searcher='search_invoice_payment')
|
|
invoice_payments = fields.Many2Many(
|
|
'account.invoice-account.move.line', 'line', 'invoice',
|
|
"Invoice Payments", readonly=True)
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(MoveLine, cls).__setup__()
|
|
cls._check_modify_exclude.add('invoice_payment')
|
|
|
|
@classmethod
|
|
def _get_origin(cls):
|
|
return super()._get_origin() + [
|
|
'account.invoice.line', 'account.invoice.tax']
|
|
|
|
@classmethod
|
|
def get_invoice_payment(cls, lines, name):
|
|
pool = Pool()
|
|
InvoicePaymentLine = pool.get('account.invoice-account.move.line')
|
|
|
|
ids = list(map(int, lines))
|
|
result = dict.fromkeys(ids, None)
|
|
for sub_ids in grouped_slice(ids):
|
|
payment_lines = InvoicePaymentLine.search([
|
|
('line', 'in', list(sub_ids)),
|
|
])
|
|
result.update({p.line.id: p.invoice.id for p in payment_lines})
|
|
return result
|
|
|
|
@classmethod
|
|
def set_invoice_payment(cls, lines, name, value):
|
|
pool = Pool()
|
|
Invoice = pool.get('account.invoice')
|
|
Invoice.remove_payment_lines(lines)
|
|
if value:
|
|
Invoice.add_payment_lines({Invoice(value): lines})
|
|
|
|
@classmethod
|
|
def search_invoice_payment(cls, name, domain):
|
|
return [('invoice_payments',) + tuple(domain[1:])]
|
|
|
|
@property
|
|
def product(self):
|
|
pool = Pool()
|
|
InvoiceLine = pool.get('account.invoice.line')
|
|
product = super().product
|
|
if (isinstance(self.origin, InvoiceLine)
|
|
and self.origin.product):
|
|
product = self.origin.product
|
|
return product
|
|
|
|
|
|
def _invoices_to_process(reconciliations):
|
|
pool = Pool()
|
|
Reconciliation = pool.get('account.move.reconciliation')
|
|
Invoice = pool.get('account.invoice')
|
|
|
|
move_ids = set()
|
|
others = set()
|
|
for reconciliation in reconciliations:
|
|
for line in reconciliation.lines:
|
|
move_ids.add(line.move.id)
|
|
others.update(line.reconciliations_delegated)
|
|
|
|
invoices = set()
|
|
for sub_ids in grouped_slice(move_ids):
|
|
invoices.update(Invoice.search([
|
|
('move', 'in', list(sub_ids)),
|
|
]))
|
|
if others:
|
|
invoices.update(_invoices_to_process(Reconciliation.browse(others)))
|
|
|
|
return invoices
|
|
|
|
|
|
class Reconciliation(metaclass=PoolMeta):
|
|
__name__ = 'account.move.reconciliation'
|
|
|
|
@classmethod
|
|
def create(cls, vlist):
|
|
Invoice = Pool().get('account.invoice')
|
|
reconciliations = super(Reconciliation, cls).create(vlist)
|
|
Invoice.__queue__.process(list(_invoices_to_process(reconciliations)))
|
|
return reconciliations
|
|
|
|
@classmethod
|
|
def delete(cls, reconciliations):
|
|
Invoice = Pool().get('account.invoice')
|
|
|
|
invoices_to_process = _invoices_to_process(reconciliations)
|
|
super(Reconciliation, cls).delete(reconciliations)
|
|
Invoice.__queue__.process(list(invoices_to_process))
|
|
|
|
|
|
class RenewFiscalYear(metaclass=PoolMeta):
|
|
__name__ = 'account.fiscalyear.renew'
|
|
|
|
def fiscalyear_defaults(self):
|
|
defaults = super(RenewFiscalYear, self).fiscalyear_defaults()
|
|
defaults['invoice_sequences'] = None
|
|
return defaults
|
|
|
|
@property
|
|
def invoice_sequence_fields(self):
|
|
return ['out_invoice_sequence', 'out_credit_note_sequence',
|
|
'in_invoice_sequence', 'in_credit_note_sequence']
|
|
|
|
def create_fiscalyear(self):
|
|
pool = Pool()
|
|
Sequence = pool.get('ir.sequence.strict')
|
|
InvoiceSequence = pool.get('account.fiscalyear.invoice_sequence')
|
|
fiscalyear = super(RenewFiscalYear, self).create_fiscalyear()
|
|
|
|
def standard_period(period):
|
|
return period.type == 'standard'
|
|
|
|
period_mapping = {}
|
|
for previous, new in zip(
|
|
filter(
|
|
standard_period, self.start.previous_fiscalyear.periods),
|
|
filter(standard_period, fiscalyear.periods)):
|
|
period_mapping[previous] = new.id
|
|
|
|
InvoiceSequence.copy(
|
|
self.start.previous_fiscalyear.invoice_sequences,
|
|
default={
|
|
'fiscalyear': fiscalyear.id,
|
|
'period': lambda data: period_mapping.get(data['period']),
|
|
})
|
|
|
|
if not self.start.reset_sequences:
|
|
return fiscalyear
|
|
sequences = OrderedDict()
|
|
for invoice_sequence in fiscalyear.invoice_sequences:
|
|
for field in self.invoice_sequence_fields:
|
|
sequence = getattr(invoice_sequence, field, None)
|
|
sequences[sequence.id] = sequence
|
|
copies = Sequence.copy(list(sequences.values()), default={
|
|
'next_number': 1,
|
|
'name': lambda data: data['name'].replace(
|
|
self.start.previous_fiscalyear.name,
|
|
self.start.name)
|
|
})
|
|
|
|
mapping = {}
|
|
for previous_id, new_sequence in zip(sequences.keys(), copies):
|
|
mapping[previous_id] = new_sequence.id
|
|
to_write = []
|
|
for new_sequence, old_sequence in zip(
|
|
fiscalyear.invoice_sequences,
|
|
self.start.previous_fiscalyear.invoice_sequences):
|
|
values = {}
|
|
for field in self.invoice_sequence_fields:
|
|
sequence = getattr(old_sequence, field, None)
|
|
values[field] = mapping[sequence.id]
|
|
to_write.extend(([new_sequence], values))
|
|
if to_write:
|
|
InvoiceSequence.write(*to_write)
|
|
return fiscalyear
|