# This file is part of the sale_pos module for Tryton. # The COPYRIGHT file at the top level of this repository contains the full # copyright notices and license terms. from datetime import date from decimal import Decimal from trytond.model import fields, ModelView, ModelSQL, Workflow from trytond.pool import Pool, PoolMeta from trytond.transaction import Transaction from trytond.wizard import Button, StateTransition, StateView, Wizard __all__ = [ 'Journal', 'Statement', 'StatementLine', 'OpenStatementStart', 'OpenStatementDone', 'OpenStatement', 'CloseStatementStart', 'BillMoney', 'CloseStatementDone', 'CloseStatement', 'MoneyCount', 'ExpensesDaily', ] _ZERO = Decimal("0.0") MONEY = [50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50] PAYMENT_CODES = [ ('', ''), ('1', 'Instrumento no definido'), ('10', 'Efectivo'), ('44', 'Nota Cambiaria'), ('20', 'Cheque'), ('48', 'Tarjeta Crédito'), ('49', 'Tarjeta Débito'), ('42', 'Consignación bancaria'), ('47', 'Transferencia Débito Bancaria'), ('45', 'Transferencia Crédito Bancaria'), ] class Journal(metaclass=PoolMeta): __name__ = 'account.statement.journal' devices = fields.One2Many('sale.device', 'journal', 'Devices') payment_means_code = fields.Selection(PAYMENT_CODES, 'Payment Means Code', required=True) kind = fields.Selection([ ('cash', 'Cash'), ('electronic', 'Electronic'), ('transfer', 'Transfer'), ('payment', 'Payment'), ('other', 'Other'), ], 'Kind', select=True) default_start_balance = fields.Numeric('Default Start Balance by Device') require_voucher = fields.Boolean('Require Voucher') @staticmethod def default_kind(): return 'cash' class Statement(metaclass=PoolMeta): __name__ = 'account.statement' users = fields.Function(fields.One2Many('res.user', None, 'Users'), 'get_users', setter='set_users', searcher='search_users') turn = fields.Integer('Turn') count_money = fields.One2Many('sale_pos.money_count', 'statement', 'Count Money') sale_device = fields.Many2One('sale.device', 'Device', required=False) total_money = fields.Function(fields.Numeric("Total Money"), 'get_total_money') amount_difference = fields.Function(fields.Numeric("Amount Difference"), 'get_amount_difference') expenses_daily = fields.One2Many('sale_pos.expenses_daily', 'statement', 'Expenses Daily') balance_next_shift = fields.Numeric('balance next shift', digits=(10, 2)) @classmethod def __setup__(cls): super(Statement, cls).__setup__() cls._error_messages.update({ 'not_count_money': 'There is not count money!', 'cant_delete_statement': 'This statement %s cannot be deleted because it has payments' }) @classmethod def cron_autopost(cls): # Add automatic post for validate statement statements = cls.search([ ('state', '=', 'validated'), ], order=[('date', 'ASC')]) if statements: cls.post([statements[0]]) def get_total_money(self, name): res = 0 for line in self.count_money: res = res + line.amount if res: return res def get_amount_difference(self, name): res = 0 amount = sum(l.amount for l in self.lines) if self.total_money is not None: res = self.total_money - amount return res @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)] # @classmethod # def trigger_create(cls, records): # super(Statement, cls).trigger_create(records) # pool = Pool() # MoneyCount = pool.get('sale_pos.money_count') # BillMoney = pool.get('sale_pos.bill_money') # bills = BillMoney.search([]) # money_to_create = [] # for statement in records: # for bill in bills: # money_to_create.append({ # 'statement': statement.id, # 'bill': bill.value, # 'quantity': 0, # }) # MoneyCount.create(money_to_create) @classmethod def create_move(cls, statements): '''Create move for the statements and try to reconcile the lines. Returns the list of move, statement and lines ''' pool = Pool() Line = pool.get('account.statement.line') Move = pool.get('account.move') Invoice = pool.get('account.invoice') to_write = [] moves = [] for statement in statements: for line in statement.lines: if line.move: continue if line.invoice and line.invoice.state in ( 'draft', 'validated'): if not line.invoice.invoice_date: for sale in line.invoice.sales: line.invoice.write( [line.invoice], {'invoice_date': sale.sale_date} ) Invoice.post([line.invoice]) move = line._get_move2() Move.post([move]) to_write.append([line]) to_write.append({'move': move.id}) moves.append(move) for mline in move.lines: if mline.account == line.account: line.reconcile([(mline, line)]) if to_write: Line.write(*to_write) return moves @classmethod @ModelView.button @Workflow.transition('validated') def validate_statement(cls, statements): for statement in statements: getattr(statement, 'validate_%s' % statement.validation)() cls.create_move(statements) cls.write(statements, { 'state': 'validated', }) @classmethod def delete(cls, statements): for statement in statements: if statement.lines: cls.raise_user_error('cant_delete_statement', (statement.rec_name,)) else: super(Statement, cls).delete(statements) class StatementLine(metaclass=PoolMeta): __name__ = 'account.statement.line' sale = fields.Many2One('sale.sale', 'Sale', ondelete='RESTRICT') voucher = fields.Char('Voucher Number') extra_amount = fields.Numeric('Extra', digits=(16, 2)) net_amount = fields.Function(fields.Numeric('Net Amount', digits=(16, 2)), 'get_net_amount') def get_net_amount(self, name=None): res = 0 if self.extra_amount and self.amount: res = self.extra_amount + self.amount return res def get_move_line2(self, values): 'Return counterpart Move Line for the amount' move_id = values['move_id'] journal_account = values.get('journal_account', False) account = values.get('account') amount = values.get('amount') credit = _ZERO debit = _ZERO if journal_account: if amount >= 0: debit = amount else: credit = abs(amount) else: if amount >= 0: credit = amount else: debit = abs(amount) if not account: self.raise_user_error('debit_credit_account_statement_journal', (self.journal.rec_name,)) res = { 'description': self.description, 'debit': debit, 'credit': credit, 'account': account.id, 'party': self.party.id if account.party_required else None, 'move': move_id, } return res def _get_move2(self): # Return Move for the grouping key pool = Pool() Move = pool.get('account.move') MoveLine = pool.get('account.move.line') Period = pool.get('account.period') Journal = pool.get('account.statement.journal') company_id = self.statement.company.id period_id = Period.find(company_id, date=self.date) _move = { 'period': period_id, 'journal': self.statement.journal.journal.id, 'date': self.date, 'origin': str(self.statement), 'company': company_id, 'state': 'draft', } move, = Move.create([_move]) move_line1 = self.get_move_line2({ 'move_id': move.id, 'account': self.account, 'amount': self.amount }) extra_amount = 0 if self.extra_amount: extra_amount = self.extra_amount move_line2 = self.get_move_line2({ 'account': self.statement.journal.account, 'move_id': move.id, 'amount': self.amount + extra_amount, 'journal_account': True, }) to_create = [move_line1, move_line2] journals = Journal.search([ ('kind', '=', 'cash') ]) journal_ids = [j.id for j in journals] # Just add extra amount to move line when payment is different to cash if self.extra_amount and self.extra_amount > 0 and \ self.statement.journal.id not in journal_ids: if journals: journal = journals[0] move_line3 = self.get_move_line2({ 'account': journal.account, 'move_id': move.id, 'amount': self.extra_amount }) to_create.append(move_line3) MoveLine.create(to_create) return move def create_move(self): # Overwrite sale_pos > create_move move = self._get_move2() self.write([self], {'move': move.id}) 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_pos.open_statement_start', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'create_', 'tryton-ok', default=True), ]) create_ = StateTransition() done = StateView('open.statement.done', 'sale_pos.open_statement_done', [ Button('Done', 'end', 'tryton-ok', default=True), ]) @classmethod def __setup__(cls): super(OpenStatement, cls).__setup__() cls._error_messages.update({ 'open_statement': 'Statement %s opened. \n', 'statement_already_opened': 'Statement %s already opened. \n', 'user_without_device': 'User %s has not any device assigned yet.' '\n', }) 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') CountMoney = pool.get('sale_pos.money_count') 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), ('sale_device', '=', device.id), ], order=[ ('date', 'ASC'), ]) journals_of_draft_statements = [s.journal for s in statements if s.state == 'draft'] vlist = [] result = '' for journal in device.journals: statements_today = Statement.search([ ('journal', '=', journal.id), ('date', '=', date.today()), ('sale_device', '=', device.id), ]) turn = len(statements_today) + 1 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': journal.default_start_balance or Decimal('0.0'), 'end_balance': Decimal('0.0'), 'turn': turn, 'sale_device': device.id, } vlist.append(values) result += self.raise_user_error('open_statement', error_args=(journal.rec_name,), raise_exception=False) else: result += self.raise_user_error('statement_already_opened', error_args=(journal.rec_name,), raise_exception=False) created_sts = Statement.create(vlist) for st in created_sts: if st.journal.kind == 'cash': to_create = [] for m in MONEY: to_create.append({ 'bill': m, 'quantity': 0, 'statement': st.id, }) CountMoney.create(to_create) self.result = result else: self.result = self.raise_user_error('user_without_device', error_args=(user.rec_name,), raise_exception=False) return 'done' class CloseStatementStart(ModelView): 'Close Statement' __name__ = 'close.statement.start' 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_pos.close_statement_start', [ Button('Cancel', 'end', 'tryton-cancel'), Button('Ok', 'validate', 'tryton-ok', default=True), ]) validate = StateTransition() done = StateView('close.statement.done', 'sale_pos.close_statement_done', [ Button('Done', 'end', 'tryton-ok', default=True), ]) @classmethod def __setup__(cls): super(CloseStatement, cls).__setup__() cls._error_messages.update({ 'close_statement': 'Statement %s closed. \n', 'statement_already_closed': 'Statement %s already closed. \n', 'not_statement_found': 'Statement %s not found. \n', 'user_without_device': 'User %s has not any device assigned yet.' '\n', }) def default_done(self, fields): return { 'result': self.result, } def transition_validate(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] draft_statements = { s.journal: s for s in Statement.search([ ('journal', 'in', journals), ('sale_device', '=', device.id), ], order=[ ('create_date', 'ASC'), ])} result = '' statements = [] for journal in device.journals: statement = draft_statements.get(journal) if statement and statement.state == 'draft': end_balance = statement.start_balance for line in statement.lines: end_balance += line.amount statement.end_balance = end_balance statement.save() statements.append(statement) result += self.raise_user_error('close_statement', error_args=(statement.rec_name,), raise_exception=False) elif statement: result += self.raise_user_error('statement_already_closed', error_args=(statement.rec_name,), raise_exception=False) else: result += self.raise_user_error('not_statement_found', error_args=(journal.rec_name,), raise_exception=False) if statements: Statement.validate_statement(statements) self.result = result else: self.result = self.raise_user_error('user_without_device', error_args=(user.rec_name,), raise_exception=False) return 'done' class MoneyCount(ModelSQL, ModelView): 'Money Count' __name__ = 'sale_pos.money_count' statement = fields.Many2One('account.statement', 'Statement', required=True) bill = fields.Integer('Bill', required=True) quantity = fields.Integer('Quantity') amount = fields.Function(fields.Numeric('Amount', digits=(16,2)), 'get_amount') @staticmethod def default_quantity(): return 0 def get_amount(self, name=None): res = Decimal(0) if self.quantity and self.bill: res = Decimal(self.quantity * self.bill) return res @classmethod def __setup__(cls): super(MoneyCount, cls).__setup__() cls._order.insert(0, ('bill', 'DESC')) class ExpensesDaily(ModelSQL, ModelView): 'Expenses Daily' __name__ = 'sale_pos.expenses_daily' _rec_name = 'invoice_number' statement = fields.Many2One('account.statement', 'Statement', required=True) invoice_number = fields.Char('Invoice Number', required=True) description = fields.Char('Description', required=True) amount = fields.Numeric('Amount', digits=(16, 2), required=True) party = fields.Many2One('party.party', 'Party') reference = fields.Char('Reference') class BillMoney(ModelSQL, ModelView): 'Bill Money' __name__ = 'sale_pos.bill_money' value = fields.Integer('Value', required=True) @classmethod def __setup__(cls): super(BillMoney, cls).__setup__()