trytonpsk-sale_pos/statement.py

560 lines
19 KiB
Python
Raw Normal View History

2020-04-15 21:47:31 +02:00
# 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
from trytond.pyson import Eval
2021-06-22 20:38:42 +02:00
from trytond.i18n import gettext
from .exceptions import StatementValidationError
2020-04-15 21:47:31 +02:00
_ZERO = Decimal("0.0")
2021-01-10 17:17:43 +01:00
MONEY = [50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50]
2020-04-15 21:47:31 +02:00
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'),
2020-12-28 20:23:34 +01:00
('transfer', 'Transfer'),
2020-04-15 21:47:31 +02:00
('payment', 'Payment'),
('other', 'Other'),
], 'Kind', select=True)
default_start_balance = fields.Numeric('Default Start Balance by Device')
require_voucher = fields.Boolean('Require Voucher')
require_party = fields.Boolean('Require Party')
party = fields.Many2One('party.party', 'Party',
states={
'invisible': ~Eval('require_party')
})
2020-04-15 21:47:31 +02:00
@staticmethod
def default_kind():
return 'cash'
@staticmethod
def default_require_party():
return False
2020-04-15 21:47:31 +02:00
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')
2020-12-26 15:12:46 +01:00
amount_difference = fields.Function(fields.Numeric("Amount Difference"),
'get_amount_difference')
2020-12-26 16:29:18 +01:00
expenses_daily = fields.One2Many('sale_pos.expenses_daily', 'statement',
'Expenses Daily')
2020-12-29 01:31:50 +01:00
balance_next_shift = fields.Numeric('balance next shift', digits=(10, 2))
2021-05-12 21:55:38 +02:00
salesman = fields.Many2One('company.employee', 'Salesman', states={
'readonly': Eval('state').in_(['validated', 'cancel', 'posted']),
})
2021-06-22 20:38:42 +02:00
2020-12-26 15:12:46 +01:00
@classmethod
def __setup__(cls):
super(Statement, cls).__setup__()
2020-04-15 21:47:31 +02:00
@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)
2021-01-04 13:50:22 +01:00
if self.total_money is not None:
2020-04-15 21:47:31 +02:00
res = self.total_money - amount
return res
@classmethod
def get_users(cls, statements, names):
return {'users': {s.id: [u.id
2021-01-04 13:50:22 +01:00
for j in s.journal
for d in j.devices
for u in d.users
]
} for s in statements}
2020-04-15 21:47:31 +02:00
@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)]
2021-01-10 17:17:43 +01:00
# @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)
2020-04-15 21:47:31 +02:00
@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:
2021-01-04 13:50:22 +01:00
line.invoice.write(
[line.invoice], {'invoice_date': sale.sale_date}
)
2020-04-15 21:47:31 +02:00
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:
2021-06-22 20:38:42 +02:00
raise StatementValidationError(
gettext('sale_pos.msg_cant_delete_statement', s=statement.rec_name))
else:
super(Statement, cls).delete(statements)
2020-04-15 21:47:31 +02:00
class StatementLine(metaclass=PoolMeta):
__name__ = 'account.statement.line'
sale = fields.Many2One('sale.sale', 'Sale', ondelete='RESTRICT')
voucher = fields.Char('Voucher Number')
2021-03-02 02:33:04 +01:00
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
2020-04-15 21:47:31 +02:00
def get_move_line2(self, values):
'Return counterpart Move Line for the amount'
move_id = values['move_id']
2021-03-02 02:33:04 +01:00
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
2020-08-31 23:19:41 +02:00
else:
2021-03-02 02:33:04 +01:00
credit = abs(amount)
2020-04-15 21:47:31 +02:00
else:
2021-03-02 02:33:04 +01:00
if amount >= 0:
credit = amount
2020-08-31 23:19:41 +02:00
else:
2021-03-02 02:33:04 +01:00
debit = abs(amount)
2020-04-15 21:47:31 +02:00
if not account:
2021-06-22 20:38:42 +02:00
raise StatementValidationError(
gettext('sale_pos.msg_account_statement_journal', s=self.journal.rec_name))
2020-04-15 21:47:31 +02:00
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')
2021-03-02 02:33:04 +01:00
Journal = pool.get('account.statement.journal')
2020-04-15 21:47:31 +02:00
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])
2021-03-02 02:33:04 +01:00
2020-04-15 21:47:31 +02:00
move_line1 = self.get_move_line2({
'move_id': move.id,
2021-03-02 02:33:04 +01:00
'account': self.account,
'amount': self.amount
2020-04-15 21:47:31 +02:00
})
2021-03-02 05:39:14 +01:00
extra_amount = 0
if self.extra_amount:
extra_amount = self.extra_amount
2020-04-15 21:47:31 +02:00
move_line2 = self.get_move_line2({
'account': self.statement.journal.account,
'move_id': move.id,
2021-03-02 05:39:14 +01:00
'amount': self.amount + extra_amount,
2021-03-02 02:33:04 +01:00
'journal_account': True,
2020-04-15 21:47:31 +02:00
})
if self.statement.journal.require_party and self.statement.journal.party:
party = self.statement.journal.party.id
move_line2['party'] = party
2021-03-02 02:33:04 +01:00
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)
2020-04-15 21:47:31 +02:00
return move
2021-10-20 16:19:57 +02:00
@classmethod
def post_move(cls, lines):
super(StatementLine, cls).post_move(lines)
for s in lines:
if s.invoice and s.move:
invoice = s.invoice
move = s.move
payment_lines = [l for l in move.lines if l.account == invoice.account and l.party == invoice.party]
invoice.add_payment_lines({invoice: payment_lines})
2020-04-15 21:47:31 +02:00
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),
2021-01-04 13:50:22 +01:00
])
2020-04-15 21:47:31 +02:00
create_ = StateTransition()
done = StateView('open.statement.done',
'sale_pos.open_statement_done', [
Button('Done', 'end', 'tryton-ok', default=True),
2021-01-04 13:50:22 +01:00
])
2020-04-15 21:47:31 +02:00
@classmethod
def __setup__(cls):
super(OpenStatement, cls).__setup__()
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')
2021-01-10 17:17:43 +01:00
CountMoney = pool.get('sale_pos.money_count')
2020-04-15 21:47:31 +02:00
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)
2021-06-22 20:38:42 +02:00
result += gettext('sale_shop.msg_open_statement',
s=(journal.rec_name,))
2020-04-15 21:47:31 +02:00
else:
2021-06-22 20:38:42 +02:00
result += gettext('sale_shop.msg_statement_already_opened',
s=(journal.rec_name,))
2021-01-10 17:17:43 +01:00
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)
2020-04-15 21:47:31 +02:00
self.result = result
else:
2021-06-22 20:38:42 +02:00
self.result = gettext('sale_shop.msg_user_without_device',
s=(user.rec_name,))
2020-04-15 21:47:31 +02:00
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),
2021-01-04 13:50:22 +01:00
])
2020-04-15 21:47:31 +02:00
validate = StateTransition()
done = StateView('close.statement.done',
'sale_pos.close_statement_done', [
Button('Done', 'end', 'tryton-ok', default=True),
2021-01-04 13:50:22 +01:00
])
2020-04-15 21:47:31 +02:00
@classmethod
def __setup__(cls):
super(CloseStatement, cls).__setup__()
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)
2021-06-22 20:38:42 +02:00
result += gettext('sale_pos.msg_close_statement',
s=(statement.rec_name,))
2020-04-15 21:47:31 +02:00
elif statement:
2021-06-22 20:38:42 +02:00
result += gettext('sale_pos.msg_statement_already_closed',
s=(statement.rec_name,))
2020-04-15 21:47:31 +02:00
else:
2021-06-22 20:38:42 +02:00
result += gettext('sale_pos.msg_not_statement_found',
s=(journal.rec_name,))
2020-04-15 21:47:31 +02:00
if statements:
Statement.validate_statement(statements)
self.result = result
else:
2021-06-22 20:38:42 +02:00
self.result = gettext('sale_pos.msg_user_without_device',
s=(user.rec_name,))
2020-04-15 21:47:31 +02:00
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
2021-01-10 17:17:43 +01:00
@classmethod
def __setup__(cls):
super(MoneyCount, cls).__setup__()
cls._order.insert(0, ('bill', 'DESC'))
2020-04-15 21:47:31 +02:00
2020-12-26 16:29:18 +01:00
class ExpensesDaily(ModelSQL, ModelView):
'Expenses Daily'
__name__ = 'sale_pos.expenses_daily'
2021-04-06 14:46:38 +02:00
_rec_name = 'invoice_number'
2020-12-26 16:29:18 +01:00
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')
2020-04-15 21:47:31 +02:00
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__()