# This file is part of the sale_payment module for Tryton. # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. from trytond.model import fields, ModelView from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction from trytond.wizard import Button, StateTransition, StateAction, StateView, Wizard from decimal import Decimal from trytond.i18n import gettext from trytond.modules.currency.fields import Monetary from trytond.pyson import Bool, Eval, If from trytond.exceptions import UserError from datetime import datetime __all__ = ['Journal', 'Statement', 'Line', 'OpenStatementStart', 'OpenStatementDone', 'OpenStatement', 'CloseStatementStart', 'CloseStatementDone', 'CloseStatement'] class Journal(metaclass=PoolMeta): __name__ = 'account.statement.journal' devices = fields.One2Many('sale.device', 'journal', 'Devices') class Statement(metaclass=PoolMeta): __name__ = 'account.statement' users = fields.Function(fields.One2Many('res.user', None, 'Users'), 'get_users', searcher='search_users') @classmethod def get_users(cls, statements, names): return {'users': {s.id: [u.id for j in s.journal for d in j.devices for u in d.users ] } for s in statements} @classmethod def search_users(cls, name, clause): pool = Pool() Journal = pool.get('account.statement.journal') Device = pool.get('sale.device') DeviceJournal = pool.get('sale.device.account.statement.journal') User = pool.get('res.user') statement = cls.__table__() journal = Journal.__table__() device = Device.__table__() device_journal = DeviceJournal.__table__() user = User.__table__() query = statement.join( journal, condition=statement.journal == journal.id).join( device_journal, condition=journal.id == device_journal.journal).join( device, condition=device_journal.device == device.id).join( user, condition=device.id == user.sale_device).select( statement.id, where=user.id == clause[2]) return [('id', 'in', query)] class Line(metaclass=PoolMeta): __name__ = 'account.statement.line' sale = fields.Many2One('sale.sale', 'Sale', ondelete='RESTRICT') def create_move(self): ''' Create move for the statement line and return move if created. Redefined method to allow amounts in statement lines greater than the invoice amount. ''' pool = Pool() Move = pool.get('account.move') Period = pool.get('account.period') Invoice = pool.get('account.invoice') Currency = pool.get('currency.currency') MoveLine = pool.get('account.move.line') if self.move: return period_id = Period.find(self.statement.company.id, date=self.date) move_lines = self._get_move_lines() move = Move( period=period_id, journal=self.statement.journal.journal, date=self.date, origin=self, lines=move_lines, ) move.save() self.write([self], { 'move': move.id, }) if self.invoice: with Transaction().set_context(date=self.invoice.currency_date): amount = Currency.compute(self.statement.journal.currency, self.amount, self.statement.company.currency) reconcile_lines = self.invoice.get_reconcile_lines_for_amount( abs(amount)) for move_line in move.lines: if move_line.account == self.invoice.account: Invoice.write([self.invoice], { 'payment_lines': [('add', [move_line.id])], }) break if reconcile_lines[1] == Decimal('0.0'): lines = reconcile_lines[0] + [move_line] MoveLine.reconcile(lines) return move class OpenStatementStart(ModelView): 'Open Statement' __name__ = 'open.statement.start' class OpenStatementDone(ModelView): 'Open Statement' __name__ = 'open.statement.done' result = fields.Text('Result', readonly=True) class OpenStatement(Wizard): 'Open Statement' __name__ = 'open.statement' start = StateView('open.statement.start', 'sale_payment.open_statement_start', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'create_', 'tryton-ok', default=True), ]) create_ = StateTransition() done = StateView('open.statement.done', 'sale_payment.open_statement_done', [ Button('Done', 'end', 'tryton-ok', default=True), ]) def default_done(self, fields): return { 'result': self.result, } def transition_create_(self): pool = Pool() User = pool.get('res.user') Statement = pool.get('account.statement') user = Transaction().user user = User(user) device = user.sale_device if device: journals = [j.id for j in device.journals] statements = Statement.search([ ('journal', 'in', journals), ], order=[ ('date', 'ASC'), ]) journals_of_draft_statements = [s.journal for s in statements if s.state == 'draft'] start_balances = { s.journal.id: s.end_balance or Decimal('0.0') for s in statements } vlist = [] results = [] for journal in device.journals: if journal not in journals_of_draft_statements: values = { 'name': '%s - %s' % (device.rec_name, journal.rec_name), 'journal': journal.id, 'company': user.company.id, 'start_balance': start_balances.get(journal.id, Decimal('0.0')), 'end_balance': Decimal('0.0'), 'total_amount': Decimal('0.0'), 'number_of_lines': 0, } vlist.append(values) results.append(gettext('sale_payment.open_statement', statement=journal.rec_name)) else: results.append(gettext('sale_payment.statement_already_opened', statement=journal.rec_name)) statements.extend(Statement.create(vlist)) self.result = '\n'.join(results) else: self.result = gettext('sale_payment.user_without_device', user=user.rec_name) return 'done' class CloseStatementStart(ModelView): 'Close Statement' __name__ = 'close.statement.start' statementLines = fields.One2Many('statement.line', None, 'Lines Statement', states={'readonly': True}) class CloseStatementDone(ModelView): 'Close Statement' __name__ = 'close.statement.done' result = fields.Text('Result', readonly=True) class CloseStatement(Wizard): 'Close Statement' __name__ = 'close.statement' start = StateView('close.statement.start', 'sale_payment.close_statement_start', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'validate', 'tryton-ok', default=True), ]) validate = StateTransition() done = StateView('close.statement.done', 'sale_payment.close_statement_done', [ Button('Done', 'end', 'tryton-ok', default=True), ]) def default_done(self, fields): return { 'result': self.result, } def default_start(self, fields): pool = Pool() User = pool.get('res.user') StatementLine = pool.get('statement.line') Statement = pool.get('account.statement') statementLines = [] user = Transaction().user user = User(user) device = user.sale_device if device: journals = [j.id for j in device.journals] draft_statements = { s.journal: s for s in Statement.search([ ('journal', 'in', journals), ('state', '=', 'draft'), ], order=[ ('create_date', 'ASC'), ])} for s in draft_statements.values(): end_balance = Decimal('0.0') for line in s.lines: end_balance += line.amount line = { 'journal': s.journal.id, 'start_balance': s.start_balance, 'balance': end_balance, 'end_balance':end_balance+s.start_balance } statementLines.append(line) default = {'statementLines': statementLines} return default def transition_validate(self): pool = Pool() User = pool.get('res.user') Statement = pool.get('account.statement') StatementLine = pool.get('account.statement.line') ConfigurationClosure = pool.get('sale.cash_closures') config = ConfigurationClosure(8) user = Transaction().user user = User(user) employee_party = user.employee.party.id if user.employee else None device = user.sale_device if device: journals = [j.id for j in device.journals] draft_statements = { s.journal: s for s in Statement.search([ ('journal', 'in', journals), ('state', '=', 'draft') ], order=[ ('create_date', 'ASC'), ])} results = [] statements = [] for journal in self.start.statementLines: account = journal.account transfer = journal.transfer mismatch = journal.mismatch end_balance = journal.end_balance journal = journal.journal statement = draft_statements.get(journal) if statement and statement.state == 'draft': if not statement.start_balance: statement.start_balance = Decimal(0) if account and transfer: end_balance = abs(end_balance) - abs(transfer) statement.end_balance = end_balance lines = statement.lines conciliation = tuple([StatementLine( date=datetime.today().date(), amount= abs(transfer)*-1, account=account.id)] ) statement.lines = statement.lines + conciliation if mismatch.real > 0: pass if (config.mismatch_limit and config.account_mismatch_charge): if mismatch and (abs(mismatch) >= abs(config.mismatch_limit.real)): lines = statement.lines if employee_party == None: raise UserError(str("Debe definir un Empleado para el Usuario.")) conciliation_mismatch = tuple([StatementLine( date=datetime.today().date(), amount=mismatch, account=config.account_mismatch_charge.id)] ) statement.lines = statement.lines + conciliation_mismatch end_balance = abs(end_balance) - abs(mismatch) statement.end_balance = end_balance statement.save() statements.append(statement) results.append(gettext('sale_payment.close_statement', statement=statement.rec_name)) elif statement: results.append(gettext('sale_payment.statement_already_closed', statement=statement.rec_name)) else: results.append(gettext('sale_payment.not_statement_found', journal=journal.rec_name)) if statements: Statement.validate_statement(statements) self.result = '\n'.join(results) else: self.result = gettext('sale_payment.user_without_device', user=user.rec_name) return 'done' class StatementLine(ModelView): "statement Line" __name__ = 'statement.line' _states = {'readonly': True} company = fields.Many2One( 'company.company', "Company", required=True, select=True) journal = fields.Many2One('account.statement.journal', 'Journal', required=True, select=True, states=_states) currency = fields.Many2One( 'currency.currency', "Currency") start_balance = Monetary( "Start Balance", currency='currency', digits='currency', states=_states) balance = Monetary( "Balance", currency='currency', digits='currency', states=_states) end_balance = Monetary( "End Balance", currency='currency', digits='currency', readonly=True) transfer = Monetary( "Transfer", currency='currency', digits='currency') real_cash = Monetary( "Real Cash", currency='currency', digits='currency') mismatch = Monetary( "Mismatch", currency='currency', digits='currency', readonly=True) account = fields.Many2One('account.account', "Account", domain=[ ('company', '=', Eval('company', 0)), ('type', '!=', None), ('closed', '!=', True), ], states={'required': If(Eval('transfer', True), True)}) @staticmethod def default_currency(): Company = Pool().get('company.company') company = Transaction().context.get('company') if company: return Company(company).currency.id @staticmethod def default_company(): return Transaction().context.get('company') @fields.depends('end_balance', 'real_cash', 'mismatch') def on_change_real_cash(self): self.mismatch = self.real_cash - self.end_balance class PayInvoiceSupplierStart(ModelView): 'Payment Invoice To Supplier' __name__ = 'pay_invoice.statement.start' invoice_to_pay = fields.One2Many('line_invoice.pay', None, "Invoice To Pay") class PayInvoiceSupplier(Wizard): 'Payment Invoice To Supplier' __name__ = 'pay_invoice.statement' start = StateView('pay_invoice.statement.start', 'sale_payment.pay_invoice_statement_start', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Pay', 'pay', 'tryton-ok', default=True), ]) pay = StateAction('account_statement.act_statement_form') def do_pay(self, action): pool = Pool() User = pool.get('res.user') Statement = pool.get('account.statement') StatementLine = pool.get('account.statement.line') user = Transaction().user user = User(user) device = user.sale_device if device: journals = [j.id for j in device.journals] draft_statements = { s.journal: s for s in Statement.search([ ('journal', 'in', journals), ], order=[ ('create_date', 'ASC'), ])} for pay in self.start.invoice_to_pay: journal = pay.journal account = pay.account invoice = pay.invoice party = pay.party amount = pay.amount statement = draft_statements.get(journal) lines = statement.lines pay_to_add = tuple([StatementLine( date=datetime.today().date(), party=party, related_to=invoice, amount= abs(amount)*-1, account=account.id)]) statement.lines = statement.lines + pay_to_add statement.save() class LinesInvoiceToPay(ModelView): "Lines Invoice To Pay" __name__ = 'line_invoice.pay' company = fields.Many2One( 'company.company', "Company", required=True, select=True) journal = fields.Many2One('account.statement.journal', 'Journal', required=True, select=True) amount = Monetary("Amount", currency='currency', digits='currency', required=True) party = fields.Many2One('party.party', "Party", required=True, context={'company': Eval('company', -1)},) invoice = fields.Reference( "Related To", 'get_relations', required=True, domain={ 'account.invoice': [ ('company', '=', Eval('company', -1)), ('type', '=', 'in'), If(Bool(Eval('party')), ('party', '=', Eval('party')), ()), If(Bool(Eval('account')), ('account', '=', Eval('account')), ()), If(Eval('statement_state') == 'draft', ('state', '=', 'posted'), ('state', '!=', '')), ],}, context={'with_payment': False}) account = fields.Many2One('account.account', "Account", domain=[ ('company', '=', Eval('company', 0)), ('type', '!=', None), ('closed', '!=', True), ],) description = fields.Char("Description") @staticmethod def default_company(): return Transaction().context.get('company') @classmethod def _get_relations(cls): "Return a list of Model names for related_to Reference" return ['account.invoice'] @classmethod def get_relations(cls): Model = Pool().get('ir.model') get_name = Model.get_name models = cls._get_relations() return [(m, get_name(m)) for m in models]