trytonpsk-sale_pos_frontend.../sale.py

429 lines
16 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 __future__ import unicode_literals
from datetime import date, datetime
from sql import Table
from trytond.pool import PoolMeta, Pool
from trytond.model import fields, ModelSQL
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.wizard import Wizard
from decimal import Decimal
from trytond.wizard import StateReport
from trytond.report import Report
from trytond.i18n import gettext
from .exceptions import PayValidationError, ProductionValidationWarning
from trytond.modules.sale_pos_frontend.sale import SaleSquareBox
from operator import itemgetter
KIND = [
('', ''),
('take_away', 'Take Away'),
('delivery', 'Delivery'),
('to_table', 'To Table')
]
class SaleMove(ModelSQL):
"Sale - Stock Move"
__name__ = "sale.sale-stock.move"
_table = 'sale_sale-stock_move_rel'
sale = fields.Many2One('sale.sale', 'Sale',
ondelete='CASCADE', select=True, required=True)
move = fields.Many2One('stock.move', 'Stock Move',
ondelete='CASCADE', select=True, required=True)
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
consumer = fields.Many2One('party.consumer', 'Consumer')
table_assigned = fields.Many2One('sale.shop.table', 'Table Assigned',
domain=[
('shop', '=', Eval('shop')),
])
productions = fields.Function(fields.One2Many('production', None, 'Productions'), 'get_productions')
production_moves = fields.Many2Many('sale.sale-stock.move', 'sale', 'move', 'Production Moves')
delivery_time = fields.Numeric('Delivery Time (Min)', help="In Minutes")
order_status = fields.Selection([
('', ''),
('draft', 'Draft'),
('commanded', 'Commanded'),
('in_preparation', 'In Preparation'),
('dispatched', 'Dispatched'),
('delivered', 'Delivered'),
('rejected', 'Rejected'),
('cancelled', 'Cancelled'),
], 'Order Status')
order_status_string = order_status.translated('order_status')
shipping_time = fields.DateTime('Shipping Time')
waiting_time = fields.Function(fields.Integer('Shipping Time',
help="In Minutes"), 'get_waiting_time')
@classmethod
def __setup__(cls):
super(Sale, cls).__setup__()
@staticmethod
def default_order_status():
return 'draft'
def get_waiting_time(self, name=None):
now = datetime.now()
if self.state in ('draft', 'quotation', 'confirmed', 'processing'):
delta = int((now - self.create_date).seconds / 60)
return delta
@classmethod
def do(cls, sales):
if sales:
ShopTable = Pool().get('sale.shop.table')
tables = ShopTable.search([('sale', 'in', [s.id for s in sales])])
if tables:
ShopTable.write(tables, {'sale': None, 'state':'available'})
@classmethod
def transition_pay_(cls, sales_to_pay):
pool = Pool()
Statement = pool.get('account.statement')
StatementLine = pool.get('account.statement.line')
for sale_pay in sales_to_pay:
sale = sale_pay['sale']
journal_id = sale_pay['journal_id']
statements = Statement.search([
('journal', '=', journal_id),
('state', '=', 'draft'),
('sale_device', '=', sale.sale_device),
], order=[('date', 'DESC')])
if not statements:
raise PayValidationError(
gettext('sale_pos_frontend_rest.msg_message_error', s='A draft statement payments has not been created.'))
if not sale.number:
cls.set_number([sale])
if not sale.party.account_receivable:
raise PayValidationError(
gettext('sale_pos_frontend_rest.msg_party_not_account_receivable', s=sale.party.name))
account = sale.party.account_receivable.id
payment = StatementLine(
statement=statements[0].id,
date=date.today(),
amount=sale.total_amount,
party=sale.party.id,
account=account,
description=sale.number,
sale=sale.id,
)
payment.save()
sale.save()
cls.workflow_to_end([sale])
@classmethod
def update_consumer(cls, args, context):
Consumer = Pool().get('party.consumer')
fields = args['fields']
consumer = Consumer(args['id'])
for key, value in fields.items():
if hasattr(consumer, key):
Consumer.write([consumer], {key: value})
return cls._get_object(consumer)
@classmethod
def create_consumer(cls, args, context):
Consumer = Pool().get('party.consumer')
consumer = None
notes = ''
if args:
# fields = args['fields']
print('args....', args)
consumer, = Consumer.create([args])
# if consumer and consumer.party:
# party = consumer.party
# party.write([party], {'notes': notes})
# return cls._get_object(consumer)
return {
'id': consumer.id,
'name': consumer.name,
'phone': consumer.phone,
'address': consumer.address or '',
'notes': consumer.notes,
'delivery': consumer.delivery
}
@classmethod
def _get_object(cls, consumer):
obj_ = {
'msg': 'ok',
# 'party': None,
'consumer': {
'id': consumer.id,
'name': consumer.name,
'phone': consumer.phone,
'address': consumer.address or '',
'notes': consumer.notes or '',
},
}
if consumer.party:
obj_['party'] = consumer.party.id
return obj_
@classmethod
def _move(cls, from_location, to_location, company, product, uom,
quantity):
Move = Pool().get('stock.move')
move = Move(
product=product,
uom=uom,
quantity=quantity,
from_location=from_location,
to_location=to_location,
company=company,
currency=company.currency if company else None,
state='draft',
)
return move
@classmethod
def _explode_move_values(cls, from_location, to_location, company,
bom_io, quantity):
move = cls._move(from_location, to_location, company,
bom_io.product, bom_io.uom, quantity)
move.from_location = from_location.id if from_location else None
move.to_location = to_location.id if to_location else None
move.unit_price_required = move.on_change_with_unit_price_required()
return move
@classmethod
def _create_productions(cls, lines):
pool = Pool()
Move = Pool().get('stock.move')
Location = pool.get('stock.location')
Bom = pool.get('production.bom')
Uom = pool.get('product.uom')
for line in lines:
if hasattr(line, 'production') and line.production:
return
if line.product.producible:
boms = Bom.search([
('output_products', '=', line.product.id)
])
if not boms:
continue
raise ProductionValidationWarning(
gettext('sale_pos_frontend_rest.msg_msg_product_without_bom', product=line.product.rec_name))
else:
bom_ = boms[0]
locations = Location.search([
('type', '=', 'production'),
])
location_ = locations[0] if locations else None
if not hasattr(line, 'sale'):
return
sale = line.sale
date_ = sale.sale_date
product_ = line.product
if not (bom_ and product_ and product_.default_uom):
return
if sale.warehouse:
storage_location = sale.warehouse.storage_location
else:
storage_location = None
factor = bom_.compute_factor(product_, line.quantity or 0,
product_.default_uom)
inputs = []
for input_ in bom_.inputs:
quantity = input_.compute_quantity(factor)
move = cls._explode_move_values(storage_location,
location_, sale.company, input_, quantity)
if move:
move.planned_date = date_
move.effective_date = date_
move.save()
inputs.append(move)
quantity = Uom.compute_qty(input_.uom, line.quantity,
input_.product.default_uom, round=False)
outputs = []
for output in bom_.outputs:
quantity = output.compute_quantity(factor)
move = cls._explode_move_values(location_, storage_location,
sale.company, output, quantity)
if move:
move.planned_date = date_
move.effective_date = date_
move.unit_price = Decimal(0)
move.save()
outputs.append(move)
moves = inputs + outputs
Move.do(inputs+outputs)
sale.production_moves = moves
sale.save()
def get_productions(self, name):
productions = []
for line in self.lines:
if hasattr(line, 'production') and line.production:
productions.append(line.production)
return productions
class SaleForceDraft(Wizard):
__name__ = 'sale_pos.force_draft'
def transition_force_draft(self):
pool = Pool()
Sale = pool.get('sale.sale')
Production = pool.get('production')
ids = Transaction().context['active_ids']
stock_move_table = Table('stock_move')
cursor = Transaction().connection.cursor()
if not ids:
return 'end'
for sale in Sale.browse(ids):
stock_moves = [line.id for line in sale.production_moves]
if stock_moves:
cursor.execute(*stock_move_table.update(
columns=[stock_move_table.state],
values=['draft'],
where=stock_move_table.id.in_(stock_moves)
))
cursor.execute(*stock_move_table.delete(
where=stock_move_table.id.in_(stock_moves))
)
for p in sale.productions:
cursor = Transaction().connection.cursor()
cursor.execute("UPDATE production SET state='waiting' WHERE id in (%s)" % (p.id))
Production.draft([p])
Production.cancel([p])
Production.delete([p])
return super(SaleForceDraft, self).transition_force_draft()
class SaleLine(metaclass=PoolMeta):
__name__ = 'sale.line'
production = fields.Many2One('production', 'Production')
class SaleSquareBoxGlobal(SaleSquareBox):
'Sale Square Box Global'
__name__ = 'sale_pos_frontend_rest.sale_square_box_global'
@classmethod
def __setup__(cls):
super(SaleSquareBoxGlobal, cls).__setup__()
cls.print_ = StateReport('sale_pos_frontend_rest.sale_square_box_global_report')
class SaleSquareBoxGlobalReport(Report):
'Square Box Global Report'
__name__ = 'sale_pos_frontend_rest.sale_square_box_global_report'
@classmethod
def __setup__(cls):
super(SaleSquareBoxGlobalReport, cls).__setup__()
@classmethod
def get_stament_cash(cls, statement):
Statement = Pool().get('account.statement')
if statement:
value = {
'statement': Statement(statement['id']),
'base': statement['total_money'] or 0,
'bills': sum(k['amount'] for k in statement['count_money.'] if float(k['bill']) >= 1000),
'coins': sum(k['amount'] for k in statement['count_money.'] if float(k['bill']) < 1000)
}
return value
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
Company = pool.get('company.company')
Device = pool.get('sale.device')
company = Company(data['company'])
Statement = pool.get('account.statement')
Shop = pool.get('sale.shop')
# User = pool.get('res.user')
dom_statement = [
('date', '=', data['date']),
]
devices = Device.search([('shop', '=', data['shop'])])
device_ids = [d.id for d in devices]
dom_statement.append(('sale_device', 'in', device_ids))
if data['turn']:
dom_statement.append(('turn', '=', int(data['turn'])))
fields_statement = ['name', 'create_date', 'journal.kind', 'balance', 'count_money.bill', 'count_money.quantity', 'count_money.amount', 'total_money']
cash = None
statements = Statement.search_read(dom_statement, fields_names=fields_statement)
lst_statements = []
balance_statements = []
for st in statements:
kind = st['journal.']['kind']
if kind == 'cash':
cash = st
else:
balance_statements.append(st['balance'])
lst_statements.append(st)
dom_invoices = [
('company', '=', data['company']),
('invoice_date', '=', data['date']),
('type', '=', 'out'),
('shop', '=', data['shop']),
('turn', '=', int(data['turn']))
]
fields_inv = ['number', 'invoice_type', 'total_amount', 'state',
'payment_term.payment_type', 'sales.residual_amount',
'payment_term.rec_name', 'party.name', 'reference']
invoices = Invoice.search_read(dom_invoices, fields_names=fields_inv)
pos = []
electronic = []
cancelled = []
credits = []
invs = []
pos_append = pos.append
electronic_append = electronic.append
cancelled_append = cancelled.append
credits_append = credits.append
invs_append = invs.append
for invoice in invoices:
residual_amount = invoice['sales.'][0]['residual_amount']
if residual_amount > 0:
invoice['residual_amount'] = residual_amount
credits_append(residual_amount)
invs_append(invoice)
if invoice['state'] == 'cancelled':
cancelled_append((invoice['total_amount'], invoice))
elif invoice['invoice_type'] == '1':
electronic_append(invoice['total_amount'])
else:
pos_append(invoice['total_amount'])
report_context['total_sale'] = sum(pos) + sum(electronic)
report_context['pos'] = sum(pos)
report_context['electronic'] = sum(electronic)
report_context['cash'] = cls.get_stament_cash(cash)
report_context['credits'] = sum(credits)
report_context['invoice_credits'] = invs
report_context['statements'] = lst_statements
report_context['balance_statements'] = balance_statements
report_context['data'] = data
report_context['company'] = company
report_context['shop'] = Shop(data['shop'])
return report_context