trytonpsk-sale_pos/statement.py

582 lines
20 KiB
Python
Raw Permalink 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.
2023-03-27 22:50:21 +02:00
from datetime import date, datetime
2020-04-15 21:47:31 +02:00
from decimal import Decimal
2023-03-27 22:50:21 +02:00
from dateutil import tz
2023-10-24 21:42:33 +02:00
2020-04-15 21:47:31 +02:00
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
2023-10-24 21:42:33 +02:00
from trytond.exceptions import UserError
2020-04-15 21:47:31 +02:00
_ZERO = Decimal("0.0")
2023-11-27 04:04:15 +01:00
MONEY = [100000, 50000, 20000, 10000, 5000, 2000, 1000, 500, 200, 100, 50]
2021-01-10 17:17:43 +01:00
2020-04-15 21:47:31 +02:00
class Journal(metaclass=PoolMeta):
__name__ = 'account.statement.journal'
2022-02-11 18:45:11 +01:00
# IS THIS NECCESARY?
2020-04-15 21:47:31 +02:00
devices = fields.One2Many('sale.device', 'journal', 'Devices')
2020-04-15 21:47:31 +02:00
class Statement(metaclass=PoolMeta):
__name__ = 'account.statement'
2023-10-21 01:54:42 +02:00
users = fields.Function(fields.One2Many(
'res.user', None, 'Users'),
'get_users', setter='set_users', searcher='search_users')
2022-10-14 00:32:56 +02:00
turn = fields.Integer('Turn', states={'readonly': True})
2021-11-09 15:23:23 +01:00
count_money = fields.One2Many(
'sale_pos.money_count', 'statement', 'Count Money')
2020-04-15 21:47:31 +02:00
sale_device = fields.Many2One('sale.device', 'Device', required=False)
2021-11-09 15:23:23 +01:00
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"),
2023-04-28 19:40:09 +02:00
'get_amount_difference')
2020-12-26 16:29:18 +01:00
expenses_daily = fields.One2Many('sale_pos.expenses_daily', 'statement',
2023-04-28 19:40:09 +02:00
'Expenses Daily')
2023-03-03 21:41:13 +01:00
additional_income = fields.One2Many('sale_pos.additional_income', 'statement',
2023-04-28 19:40:09 +02:00
'Additional Income')
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__()
2023-04-28 19:40:09 +02:00
cls._transitions |= set((
('posted', 'draft'),
))
cls._buttons.update({
'force_draft': {
'invisible': Eval('state') != 'posted',
'depends': ['state'],
},
})
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
2023-10-21 01:54:42 +02:00
amount = sum(li.amount for li 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):
2023-04-28 19:40:09 +02:00
return {'users':
2023-01-06 21:29:54 +01:00
{s.id: [
u.id
for j in s.journal
for d in j.devices
for u in d.users
2023-04-28 19:40:09 +02:00
]} 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
2023-04-28 19:40:09 +02:00
@classmethod
@ModelView.button
@Workflow.transition('draft')
def force_draft(cls, statements):
pass
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(
2021-11-09 15:23:23 +01:00
[line.invoice], {
'invoice_date': sale.sale_date}
2021-01-04 13:50:22 +01:00
)
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'
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)),
2022-02-11 18:45:11 +01:00
'get_net_amount')
2021-03-02 02:33:04 +01:00
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
2023-08-24 22:27:26 +02:00
@staticmethod
def default_extra_amount():
return 0
2022-02-11 18:45:11 +01:00
# All this was moved to Sale Shop
# 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:
# raise StatementValidationError(
# gettext('sale_pos.msg_account_statement_journal', s=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,
# })
# if self.statement.journal.require_party and self.statement.journal.party:
# party = self.statement.journal.party.id
# move_line2['party'] = party
# 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
2020-04-15 21:47:31 +02:00
2023-03-03 21:41:13 +01:00
class AdditionalIncome(ModelSQL, ModelView):
" Additional Incomes "
__name__ = 'sale_pos.additional_income'
statement = fields.Many2One('account.statement', 'Statement')
description = fields.Char('Description')
party = fields.Many2One('party.party', 'Party')
2023-06-02 16:15:28 +02:00
amount = fields.Numeric('Amount', digits=(16, 2), states={
'required': True})
2023-03-03 21:41:13 +01:00
2020-04-15 21:47:31 +02:00
class OpenStatementStart(ModelView):
'Open Statement'
__name__ = 'open.statement.start'
class OpenStatementDone(ModelView):
'Open Statement'
__name__ = 'open.statement.done'
2022-07-23 19:46:44 +02:00
result = fields.Text('Result', readonly=True, translate=True)
2020-04-15 21:47:31 +02:00
class OpenStatement(Wizard):
'Open Statement'
__name__ = 'open.statement'
2023-10-21 01:54:42 +02:00
start = StateView(
'open.statement.start',
2022-07-23 19:46:44 +02:00
'sale_pos.open_statement_start', [
2023-10-24 21:42:33 +02:00
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'create_', 'tryton-ok', default=True),
])
2020-04-15 21:47:31 +02:00
create_ = StateTransition()
2023-10-21 01:54:42 +02:00
done = StateView(
'open.statement.done',
2022-07-23 19:46:44 +02:00
'sale_pos.open_statement_done', [
2023-10-21 01:54:42 +02:00
Button('Done', 'end', 'tryton-ok', default=True),
2022-07-23 19:46:44 +02: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,
2023-10-24 21:42:33 +02:00
}
def _check_previous(self, sale_device, today):
pool = Pool()
Statement = pool.get('account.statement')
res = Statement.search_read([
('sale_device', '=', sale_device.id),
('state', '=', 'draft'),
], fields_names=['id'])
return res
2020-04-15 21:47:31 +02:00
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')
2023-01-06 21:29:54 +01:00
Sale = pool.get('sale.sale')
Invoice = pool.get('account.invoice')
Date = pool.get('ir.date')
2023-03-27 22:50:21 +02:00
Company = pool.get('company.company')
company = Company(Transaction().context.get('company'))
2023-01-06 21:29:54 +01:00
today = Date.today()
2020-04-15 21:47:31 +02:00
user = Transaction().user
user = User(user)
device = user.sale_device
2023-10-24 21:42:33 +02:00
time_zone = company.timezone
now_ = datetime.now(tz.gettz(time_zone))
today = date(now_.year, now_.month, now_.day)
previous = self._check_previous(device, today)
print("res............", previous)
if previous:
raise UserError(
gettext('sale_pos.msg_previous_statement_open'))
return 'end'
2020-04-15 21:47:31 +02:00
if device:
journals = [j.id for j in device.journals]
statements = Statement.search([
('journal', 'in', journals),
('sale_device', '=', device.id),
], order=[
('date', 'ASC'),
])
2023-10-24 21:42:33 +02:00
journals_of_draft_statements = [
s.journal for s in statements if s.state == 'draft'
]
2020-04-15 21:47:31 +02:00
vlist = []
result = ''
2023-10-24 21:42:33 +02:00
2020-04-15 21:47:31 +02:00
for journal in device.journals:
statements_today = Statement.search([
('journal', '=', journal.id),
2023-03-27 22:11:51 +02:00
('date', '=', today),
2020-04-15 21:47:31 +02:00
('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)
2022-07-23 19:46:44 +02:00
# result += 'sale_shop.msg_open_statement %(s)' % journal.rec_name
result += f'Estado de Cuenta {journal.rec_name} abierto.\n \n'
2020-04-15 21:47:31 +02:00
else:
2022-07-23 19:46:44 +02:00
result += f'El Estado de Cuenta {journal.rec_name} ya estaba abierto.\n \n'
2021-01-10 17:17:43 +01:00
created_sts = Statement.create(vlist)
for st in created_sts:
if st.journal.kind == 'cash':
2023-01-06 21:29:54 +01:00
turn = st.turn
sales = Sale.search([
('sale_device', '=', device.id),
('sale_date', '=', today),
('payment_term.payment_type', '=', '1'),
('payments', '=', None)
])
invoices = [s.invoices[0] for s in sales if s.invoices]
write_ = {'turn': turn}
Sale.write(sales, write_)
Invoice.write(invoices, write_)
2021-01-10 17:17:43 +01:00
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:
2022-07-23 19:49:48 +02:00
self.result = f'El usuario {user.rec_name} no tiene asginado un terminal.\n \n'
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'
2023-10-21 01:54:42 +02:00
start = StateView(
'close.statement.start',
'sale_pos.close_statement_start', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'validate', 'tryton-ok', default=True),
])
2020-04-15 21:47:31 +02:00
validate = StateTransition()
2023-10-21 01:54:42 +02:00
done = StateView(
'close.statement.done',
'sale_pos.close_statement_done', [
Button('Done', 'end', 'tryton-ok', default=True),
])
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)
2022-07-23 19:46:44 +02:00
result += f'Estado de Cuenta {journal.rec_name} cerrado.\n \n'
2020-04-15 21:47:31 +02:00
elif statement:
2022-07-23 19:46:44 +02:00
result += f'El Estado de Cuenta {journal.rec_name} ya estaba cerrado.\n \n'
2020-04-15 21:47:31 +02:00
else:
2022-07-23 19:46:44 +02:00
result += f'El Estado de Cuenta {journal.rec_name} no encontrado.\n \n'
2020-04-15 21:47:31 +02:00
if statements:
Statement.validate_statement(statements)
self.result = result
else:
2022-07-23 19:46:44 +02:00
self.result = f'El usuario {user.rec_name} no tiene asginado un terminal.\n \n'
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',
2021-11-09 15:23:23 +01:00
required=True)
2020-04-15 21:47:31 +02:00
bill = fields.Integer('Bill', required=True)
quantity = fields.Integer('Quantity')
2021-11-09 15:23:23 +01:00
amount = fields.Function(fields.Numeric('Amount', digits=(16, 2)),
'get_amount')
2020-04-15 21:47:31 +02:00
@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'
2021-11-09 15:23:23 +01:00
statement = fields.Many2One(
'account.statement', 'Statement', required=True)
2020-12-26 16:29:18 +01:00
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__()