trytonpsk-sale_pos/sale.py

2052 lines
76 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.
import math
import calendar
from decimal import Decimal
from datetime import datetime, date, timedelta
from itertools import chain
from sql import Null, Table, With
from sql.operators import NotIn
from sql.aggregate import Sum, Count
from sql.conditionals import Case, Coalesce
from trytond.model import ModelView, fields
from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction
from trytond.pyson import Bool, Eval, Not
from trytond.wizard import (
Wizard, StateView, StateAction, StateReport, StateTransition, Button
)
from trytond.report import Report
from trytond.i18n import gettext
from .exceptions import (
ProductMissingTaxError, ImportSalesError, SaleDeleteError,
SaleForceDraftError, SaleDeviceError, DraftStatementError,
PartyMissingAccount
)
from trytond.model.exceptions import AccessError
from trytond.modules.sale.exceptions import SaleValidationError
_ZERO = Decimal('0.00')
TODAY = date.today()
STATES = {
'readonly': Eval('state').in_(['processing', 'done', 'cancel']),
}
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
self_pick_up = fields.Boolean('Self Pick Up', states=STATES, depends=['state'],
help='The goods are picked up by the customer before the sale, so no '
'shipment is created.')
pos_create_date = fields.DateTime('Create Date', readonly=True)
invoice_number = fields.Char('Invoice Number', states={'readonly': True})
invoice_date = fields.Date('Invoice Date')
invoice = fields.Many2One('account.invoice', 'Invoice')
sale_device = fields.Many2One('sale.device', 'Sale Device',
domain=[('shop', '=', Eval('shop'))], depends=['shop'], states=STATES)
position = fields.Char('Position', states=STATES)
pre_sale = fields.Boolean('Pre-Sale', help="This option for pre-sale, change method of invoice to shipment")
shipment_date = fields.Date('Shipment Date', states=STATES)
turn = fields.Integer('Turn', states=STATES)
reservation = fields.Boolean('Reservation')
@classmethod
def __setup__(cls):
super(Sale, cls).__setup__()
cls.party.states['readonly'] = Bool(Eval('invoice_number'))
cls.state_string = super(Sale, cls).state.translated('state')
for fname in cls.self_pick_up.on_change:
if fname not in cls.shop.on_change:
cls.shop.on_change.add(fname)
if fname not in cls.party.on_change:
cls.party.on_change.add(fname)
cls._buttons.update({
'wizard_generate_invoice': {
'invisible': Eval('state').in_(['done', 'processing', 'cancel', 'transferred']),
'readonly': Not(Bool(Eval('lines'))),
},
'wizard_generate_shipment': {
'invisible': Eval('shipment_state').in_(['sent', 'exception']),
'readonly': Not(Bool(Eval('lines'))),
},
})
@classmethod
def search_rec_name(cls, name, clause):
_, operator, value = clause
if operator.startswith('!') or operator.startswith('not '):
bool_op = 'AND'
else:
bool_op = 'OR'
domain = [
bool_op,
('number', operator, value),
('reference', operator, value),
('description', operator, value),
('invoice.number', operator, value),
]
return domain
@classmethod
def process(cls, sales):
configuration = Pool().get('sale.configuration')(1)
for sale in sales:
if sale.invoices:
invoice = sale.invoices[0]
if invoice.state in ('posted', 'paid'):
continue
if hasattr(configuration, 'password_force_assign') and configuration.password_force_assign and sale.shipment_state == 'none':
cls.validate_stock(sale)
cls.process_pos(sale)
super(Sale, cls).process(sales)
@classmethod
def validate_stock(cls, sale):
Product = Pool().get('product.product')
product_ids = {l.product.id: l.quantity for l in sale.lines}
today = date.today()
with Transaction().set_context({'stock_date_end': today, 'locations': [sale.warehouse.id]}):
products = Product.search([
('id', 'in', product_ids.keys()),
])
for product in products:
if product.quantity <= product_ids[product.id]:
raise SaleValidationError(gettext('sale_pos.msg_product_quantity', s=product.rec_name))
def get_tax_amount(self):
# Esto es necesario para impuestos de Licores y Cigarrillos
values = self._get_taxes().values()
res = sum((v['amount'] for v in values), Decimal(0))
Tax = Pool().get('account.tax')
taxes_update = Tax.search([
('type', '=', 'fixed'),
('group.kind', '=', 'sale'),
('amount', '=', 0),
])
taxes_update_ids = [t.id for t in taxes_update]
taxes_update_id = None
if taxes_update_ids:
taxes_update_id = taxes_update_ids[0]
amounts = []
if taxes_update_id:
for l in self.lines:
classification_tax = any(t.classification_tax == '02' for t in l.taxes)
if not l.product or (not hasattr(l.product, 'extra_tax') or not l.product.extra_tax or not classification_tax):
continue
raw_val = Decimal(float(l.product.extra_tax) * l.quantity)
val = self.currency.round(raw_val)
amounts.append(val)
return res + sum(amounts)
def create_invoice(self):
'Create and return an invoice'
pool = Pool()
Invoice = pool.get('account.invoice')
if self.invoice_method == 'manual':
return
invoice_lines = []
sales = [self]
for sale in sales:
for line in sale.lines:
if self.total_amount < 0 and line.quantity > 0:
continue
if self.total_amount > 0 and line.quantity < 0:
continue
invoice_lines.append(line.get_invoice_line())
invoice_lines = list(chain(*invoice_lines))
if not invoice_lines:
return
sale = sales[0]
invoice = self._get_invoice_sale()
if self.invoice_type:
invoice.authorization = self._get_authorization(sale)
invoice.invoice_type = self.invoice_type
if getattr(invoice, 'lines', None):
invoice_lines = list(invoice.lines) + invoice_lines
invoice.lines = invoice_lines
invoice.turn = self._get_turn()
invoice.shop = self.shop
if invoice.shop and invoice.shop.tax_exempt:
for line in invoice.lines:
if not line.product.account_category.account_other_income:
raise AccessError(
gettext('sale_shop.msg_dont_account_other_income',
product=line.product.rec_name))
line.account = line.product.account_category.account_other_income
invoice.save()
Invoice.update_taxes([invoice])
self.copy_resources_to(invoice)
return invoice
def _get_invoice_sale(self):
invoice = super(Sale, self)._get_invoice_sale()
config = Pool().get('sale.configuration')(1)
if hasattr(config, 'invoice_date_from_statement') and config.invoice_date_from_statement:
invoice.invoice_date = self.sale_date
invoice.accounting_date = self.sale_date
else:
invoice_date = self.invoice_date
if not invoice_date:
invoice_date = Pool().get('ir.date').today()
self.invoice_date = invoice_date
self.save()
invoice.invoice_date = invoice_date
invoice.accounting_date = invoice_date
return invoice
def _get_authorization(self, sale):
authorization_id = None
if sale.untaxed_amount_cache >= 0:
if sale.invoice_type == 'P' and sale.shop.pos_authorization:
authorization_id = sale.shop.pos_authorization.id
elif sale.invoice_type == 'M' and sale.shop.manual_authorization:
authorization_id = sale.shop.manual_authorization.id
elif sale.invoice_type == 'C' and sale.shop.computer_authorization:
authorization_id = sale.shop.computer_authorization.id
elif sale.invoice_type in ['1', '2', '3'] and sale.shop.electronic_authorization:
authorization_id = sale.shop.electronic_authorization.id
elif sale.shop.debit_note_electronic_authorization and sale.invoice_type == '92':
authorization_id = sale.shop.debit_note_electronic_authorization.id
else:
if sale.shop.credit_note_electronic_authorization and sale.invoice_type in ['91', 'N']:
authorization_id = sale.shop.credit_note_electronic_authorization.id
return authorization_id
@classmethod
def process_pos(cls, sale):
if not sale:
return
if sale.invoices:
invoice = sale.invoices[0]
if invoice.number and invoice.state != 'draft':
return sale
pool = Pool()
Date = pool.get('ir.date')
# We need to keep that sequence for invoices been taked from shop
# configuration instead fiscalyear, because some countries sequences
# are setted by shop
number = None
invoice = sale.create_invoice()
sale.set_invoice_state()
sequence = sale.get_sequence(sale)
if not sale.invoice_number and sale.invoice_method == 'order':
if sequence:
number = sequence.get()
cls.write([sale], {
'invoice_number': number,
})
else:
number = sale.invoice_number
position = sale.position if sale.position else None
sale_kind = None
if hasattr(sale, 'kind') and sale.kind:
sale_kind = sale.kind
if invoice:
sale.invoice = invoice.id
to_write = {
'shop': sale.shop.id,
'number': number,
'reference': sale.reference or sale.number,
'position': position,
'turn': sale.turn,
}
if sale_kind:
to_write['sale_kind'] = sale_kind
if sale.invoice_type:
pass
invoice.write([invoice], to_write)
sale.save()
return sale
@classmethod
def cancel(cls, sales):
pool = Pool()
Invoice = pool.get('account.invoice')
Journal = pool.get('account.journal')
super(Sale, cls).cancel(sales)
journals = Journal.search([
('type', '=', 'revenue'),
])
if journals:
journal = journals[0]
else:
return
for sale in sales:
if not sale.number or not sale.invoice_number:
continue
sale_invoices = list(sale.invoices)
if not sale.invoices:
invoice, = Invoice.create([{
'party': sale.party.id,
'account': sale.party.account_receivable.id,
'invoice_date': sale.invoice_date or sale.sale_date,
'payment_term': sale.payment_term.id,
'salesman': sale.salesman and sale.salesman.id,
'journal': journal.id,
'invoice_address': sale.invoice_address.id,
'number': sale.invoice_number,
'reference': sale.number
}])
sale_invoices.append(invoice)
Invoice.cancel(sale_invoices)
@staticmethod
def default_party():
User = Pool().get('res.user')
user = User(Transaction().user)
return user.shop.party.id if user.shop and user.shop.party else None
@fields.depends('invoice_method', 'pre_sale')
def on_change_pre_sale(self):
if self.pre_sale:
self.invoice_method = 'shipment'
else:
self.invoice_method = 'order'
@classmethod
def create(cls, vlist):
now = datetime.now()
vlist = [x.copy() for x in vlist]
for vals in vlist:
vals['pos_create_date'] = now
return super(Sale, cls).create(vlist)
@classmethod
def delete(cls, sales):
for sale in sales:
if sale.invoice_number:
raise SaleDeleteError(
gettext('sale_pos.msg_delete_invoice_number',
sale=sale.rec_name,
s=sale.invoice_number))
super(Sale, cls).delete(sales)
@classmethod
def process_sale_pos(cls, sale):
# We must set directly state and amounts cache because is faster
# 'state': 'confirmed',
cls.write([sale], {
'state': 'processing',
'untaxed_amount_cache': sale.untaxed_amount,
'tax_amount_cache': sale.tax_amount,
'total_amount_cache': sale.total_amount
})
sale = cls.process_pos(sale)
return sale
@classmethod
def update_state(cls, sales):
for sale in sales:
if not sale.is_done():
continue
@classmethod
def do_stock_moves(cls, sales):
for sale in sales:
if sale.shipment_state == "none":
sale.create_shipment('out')
sale.set_shipment_state()
@classmethod
def post_invoices(cls, sale):
pool = Pool()
Date = pool.get('ir.date')
Invoice = pool.get('account.invoice')
invoice = None
if sale.invoice:
invoice = sale.invoice
else:
if sale.invoices:
invoice = sale.invoices[0]
if invoice:
if invoice.payment_term.id != sale.payment_term.id:
invoice.payment_term = sale.payment_term
# _invoice_date = sale.invoice_date or Date.today()
# if invoice.state == 'draft':
# if not getattr(invoice, 'invoice_date', False):
# invoice.invoice_date = _invoice_date
# if not getattr(invoice, 'accounting_date', False):
# invoice.accounting_date = _invoice_date or date.today()
invoice.save()
if hasattr(sale.shop, 'workflow_invoice') \
and sale.shop.workflow_invoice == 'validated':
Invoice.validate_invoice([invoice])
return
Invoice.process_invoice([invoice])
if invoice.state == 'posted' and sale.residual_amount <= 0:
try:
cls.do_reconcile([sale])
except:
pass
@classmethod
def do_reconcile(cls, sales):
Reconciliation = Pool().get('account.move.reconciliation')
Config = Pool().get('sale.configuration')
account_advance = Config(1).advance_account.id if Config(1).advance_account else None
for sale in sales:
reconcile_lines = []
advance_lines = []
account_reconcile_id = None
invoice_id = None
invoice_ = None
if not sale.payments or not sale.number:
continue
try:
for invoice in sale.invoices:
if invoice.state == 'paid' or not invoice.move:
continue
invoice.process([invoice])
invoice_id = invoice.id
invoice_ = invoice
account_reconcile_id = invoice.account.id
for line in invoice.payment_lines:
if not line.reconciliation and line.account.id == account_reconcile_id:
reconcile_lines.append(line)
for ml in invoice.move.lines:
if ml.account.id == account_reconcile_id:
reconcile_lines.append(ml)
break
if invoice_:
for st_line in sale.payments:
if st_line.account.id == invoice_.account.id:
st_line.invoice = invoice_id
st_line.save()
elif st_line.move and st_line.move.state != 'posted':
st_line.move.post([st_line.move])
if not st_line.move:
st_line.create_move()
for ml in st_line.move.lines:
if not ml.reconciliation and ml.account.id == account_reconcile_id:
reconcile_lines.append(ml)
if account_advance and not ml.reconciliation and ml.account.id == account_advance:
advance_lines.append(ml)
if advance_lines:
invoice_.create_cross_advance(advance_lines)
amount_reconcile = sum(r.debit-r.credit for r in reconcile_lines)
if reconcile_lines and amount_reconcile == 0:
Reconciliation.create([{
'lines': [('add', reconcile_lines)],
'date': TODAY,
}])
invoice_.process([invoice_])
sale_write = {'state': 'done'}
if invoice.state == 'paid':
sale_write['invoice_state'] = 'paid'
sale.write([sale], sale_write)
except Exception as e:
print('error: ', e)
print('Warning: Sale number not processed %s' % sale.number)
@classmethod
def workflow_to_end(cls, sales):
sale = sales[0]
_sale = cls.process_sale_pos(sale)
if _sale:
sale = _sale
cls.post_invoices(sale)
try:
cls.do_stock_moves([sale])
cls.do_reconcile([sale])
cls.update_state([sale])
except Exception as e:
print(e)
@classmethod
@ModelView.button
def wizard_generate_invoice(cls, sales):
cls.quote(sales)
cls.workflow_to_end(sales)
@classmethod
@ModelView.button
def wizard_generate_shipment(cls, sales):
cls.do_stock_moves(sales)
@classmethod
def copy(cls, sales, default=None):
if default is None:
default = {}
default['invoice'] = None
default['invoice_number'] = None
default['invoice_date'] = None
if sales:
sale = sales[0]
if sale.invoice and sale.invoice.number:
default['reference'] = sale.invoice.number
default['invoice_number'] = None
return super(Sale, cls).copy(sales, default)
def get_sequence(self, sale):
sequence = None
if sale.untaxed_amount_cache >= 0:
if sale.invoice_type == 'C' and sale.shop.computer_authorization:
sequence = sale.shop.computer_authorization.sequence
elif sale.invoice_type == 'P' and sale.shop.pos_authorization:
sequence = sale.shop.pos_authorization.sequence
elif sale.invoice_type == 'M' and sale.shop.manual_authorization:
sequence = sale.shop.manual_authorization.sequence
elif sale.invoice_type in ['1','2','3'] and sale.shop.electronic_authorization:
sequence = sale.shop.electronic_authorization.sequence
elif sale.shop.invoice_sequence:
sequence = sale.shop.invoice_sequence
else:
if sale.shop.credit_note_electronic_authorization and sale.invoice_type in ['91', 'N']:
sequence = sale.shop.credit_note_electronic_authorization.sequence
elif sale.shop.debit_note_electronic_authorization and sale.invoice_type == '92':
sequence = sale.shop.debit_note_electronic_authorization.sequence
else:
if sale.shop.credit_note_sequence:
sequence = sale.shop.credit_note_sequence
return sequence
def create_shipment(self, shipment_type):
if self.self_pick_up:
return self.create_moves_without_shipment(shipment_type)
return super(Sale, self).create_shipment(shipment_type)
def create_moves_without_shipment(self, shipment_type):
pool = Pool()
Move = pool.get('stock.move')
if self.shipment_method != 'order':
return
moves = {}
for line in self.lines:
move = line.get_move(shipment_type)
if move:
moves[line.id] = move
to_create = []
for m in moves:
moves[m].state = 'draft'
to_create.append(moves[m]._save_values)
Move.create(to_create)
Move.do(self.moves)
self.set_shipment_state()
# WTF
# @fields.depends('lines', 'currency', 'party')
# def on_change_lines(self):
# '''
# Overrides this method completely if the sale is self pick up to improve
# performance: Computes untaxed, total and tax amounts from the already
# computed values in sale lines.
# '''
#
# self.untaxed_amount = Decimal('0.0')
# self.tax_amount = Decimal('0.0')
# self.total_amount = Decimal('0.0')
#
# if self.lines:
# self.untaxed_amount = reduce(lambda x, y: x + y,
# [(getattr(l, 'amount', None) or Decimal(0))
# for l in self.lines if l.type == 'line'], Decimal(0)
# )
# self.total_amount = reduce(lambda x, y: x + y,
# [(getattr(l, 'amount_w_tax', None) or Decimal(0))
# for l in self.lines if l.type == 'line'], Decimal(0)
# )
# if self.currency:
# self.untaxed_amount = self.currency.round(self.untaxed_amount)
# self.total_amount = self.currency.round(self.total_amount)
# self.tax_amount = self.total_amount - self.untaxed_amount
# if self.currency:
# self.tax_amount = self.currency.round(self.tax_amount)
def get_invoice_number(self, name=None):
if self.invoices:
return self.invoices[0].number
@classmethod
def dash_on_change_party(cls, args, ctx={}):
if args.get('party'):
Party = Pool().get('party.party')
party = Party(args.get('party')['id'])
address = party.address_get(type='invoice')
return {
'invoice_address': {'id': address.id, 'name': address.name},
'shipment_address': None
}
def _get_turn(self):
Statement = Pool().get('account.statement')
if not self.sale_device:
return
statements = Statement.search([
('state', '=', 'draft'),
('sale_device', '=', self.sale_device.id),
('turn', '!=', None),
], order=[('create_date', 'DESC')])
if statements:
return statements[0].turn
class SaleLine(metaclass=PoolMeta):
__name__ = 'sale.line'
# @fields.depends('unit_price', 'unit_price_full', 'product', 'quantity')
# def on_change_unit_price_full(self, name=None):
# pool = Pool()
# Tax = pool.get('account.tax')
# if self.product and self.unit_price_full and self.product.template.customer_taxes_used:
# res = Tax.reverse_compute(self.unit_price_full,
# self.product.template.customer_taxes_used)
# self.unit_price = res
@classmethod
def dash_on_change_line(cls, args, ctx):
if not args.get('quantity') or not args.get('product'):
return {}
Line = Pool().get('sale.line')
Sale = Pool().get('sale.sale')
Product = Pool().get('product.product')
PriceListLine = Pool().get('product.price_list.line')
product_id = args['product']['id']
product = Product(product_id)
company_id = ctx.get('company')
with Transaction().set_context(ctx):
unit_price = product.list_price
if args.get('quantity'):
quantity = int(args.get('quantity'))
else:
quantity = 1
if ctx.get('price_list'):
price_list_id = ctx.get('price_list')['id']
else:
price_list_id = None
percent_commission = 0
if price_list_id:
price_lines = PriceListLine.search([
('price_list', '=', price_list_id),
('product', '=', product_id),
])
if price_lines:
price_line = price_lines[0]
unit_price = float(unit_price)
unit_price = Decimal(eval(price_line.formula))
percent_commission = price_line.price_list.percent_commission
sale = Sale(company=company_id)
taxes_ids = [t.id for t in product.customer_taxes_used]
line = Line(
sale=sale.id,
type='line',
taxes=taxes_ids,
unit_price=unit_price,
quantity=quantity,
product=product.id,
)
res = cls.get_price_with_tax([line], ['amount_w_tax', 'unit_price_w_tax'])
res = {
'unit_price_w_tax': math.ceil(res['unit_price_w_tax'][None]),
'amount_w_tax': math.ceil(res['amount_w_tax'][None]),
'unit_price': math.ceil(unit_price),
'taxes': [[('add'), taxes_ids]],
'unit': product.template.default_uom.id,
'type': 'line',
}
if percent_commission:
res['commission_amount'] = round((((unit_price) * quantity) * percent_commission), 0)
return res
@classmethod
def __setup__(cls):
super(SaleLine, cls).__setup__()
# Allow edit product, quantity and unit in lines without parent sale
# for fname in ('product', 'quantity', 'unit'):
# field = getattr(cls, fname)
# if field.states.get('readonly'):
# del field.states['readonly']
@staticmethod
def default_sale():
if Transaction().context.get('sale'):
return Transaction().context.get('sale')
return None
# @fields.depends('sale')
# def on_change_product(self):
# Sale = Pool().get('sale.sale')
#
# if not self.sale:
# sale_id = Transaction().context.get('sale')
# if sale_id:
# self.sale = Sale(sale_id)
# super(SaleLine, self).on_change_product()
@fields.depends('sale')
def on_change_with_amount(self):
if not self.sale:
self.sale = Transaction().context.get('sale')
return super(SaleLine, self).on_change_with_amount()
def get_from_location(self, name):
res = super(SaleLine, self).get_from_location(name)
if self.sale.self_pick_up:
if self.warehouse and self.quantity >= 0:
return self.warehouse.storage_location.id
return res
def get_to_location(self, name):
res = super(SaleLine, self).get_to_location(name)
if self.sale.self_pick_up:
if self.warehouse and self.quantity < 0:
return self.warehouse.storage_location.id
return res
def compute_price_w_tax(self, line):
Tax = Pool().get('account.tax')
res = line.unit_price * Decimal(line.quantity)
if line.taxes and line.unit_price:
tax_list = Tax.compute(line.taxes,
line.unit_price or Decimal('0.0'), 1)
res = sum([t['amount'] for t in tax_list], Decimal('0.0'))
res = (res + line.unit_price) * Decimal(line.quantity)
if line.product.extra_tax:
res += line.product.extra_tax * Decimal(line.quantity)
res = res.quantize(
Decimal(1) / 10 ** self.__class__.unit_price.digits[1])
return res
class SaleForceDraft(Wizard):
'Sale Force Draft'
__name__ = 'sale_pos.force_draft'
start_state = 'force_draft'
force_draft = StateTransition()
@classmethod
def __setup__(cls):
super(SaleForceDraft, cls).__setup__()
def transition_force_draft(self):
sale_table = Table('sale_sale')
invoice_table = Table('account_invoice')
move_table = Table('account_move')
stock_move_table = Table('stock_move')
Sale = Pool().get('sale.sale')
MoveLine = Pool().get('account.move.line')
Date = Pool().get('ir.date')
cursor = Transaction().connection.cursor()
ids = Transaction().context['active_ids']
if not ids:
return 'end'
sales = Sale.browse(ids)
for sale in sales:
# if sale.sale_date != Date.today():
# return 'end'
# The payments must be delete
for pay in sale.payments:
if pay.statement.state != 'draft':
return 'end'
if pay.move:
self.unreconcile_move(pay.move)
pay.invoice = None
pay.save()
pay.move.delete([pay.move])
pay.delete([pay])
# The invoices must be delete
for invoice in sale.invoices:
if (hasattr(invoice, 'cufe') and invoice.cufe) or \
hasattr(invoice, 'electronic_state') and \
invoice.electronic_state == 'submitted':
raise SaleForceDraftError(
gettext('sale_pos.msg_with_electronic_invoice'))
if invoice.move:
move = invoice.move
MoveLine.check_journal_period_modify(move.period, move.journal)
if invoice.state == 'paid':
self.unreconcile_move(invoice.move)
if invoice.move:
cursor.execute(*move_table.update(
columns=[move_table.state],
values=['draft'],
where=move_table.id == invoice.move.id)
)
cursor.execute(*move_table.delete(
where=move_table.id == invoice.move.id)
)
cursor.execute(*invoice_table.update(
columns=[invoice_table.state, invoice_table.number],
values=['validate', None],
where=invoice_table.id == invoice.id)
)
cursor.execute(*invoice_table.delete(
where=invoice_table.id == invoice.id)
)
if sale.id:
cursor.execute(*sale_table.update(
columns=[sale_table.state, sale_table.shipment_state, sale_table.invoice_state],
values=['draft', 'none', 'none'],
where=sale_table.id == sale.id)
)
# The stock moves must be delete
stock_moves = [m.id for line in sale.lines for m in line.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))
)
return 'end'
def unreconcile_move(self, move):
Reconciliation = Pool().get('account.move.reconciliation')
reconciliations = [l.reconciliation for l in move.lines if l.reconciliation]
if reconciliations:
Reconciliation.delete(reconciliations)
class SaleUpdateDateStart(ModelView):
'Sale Update Date Start'
__name__ = 'sale_pos.sale_update_date.start'
new_date = fields.Date('New Date', required=True)
class SaleUpdateDate(Wizard):
'Sale Update Date'
__name__ = 'sale_pos.sale_update_date'
start = StateView('sale_pos.sale_update_date.start',
'sale_pos.sale_update_date_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'accept', 'tryton-ok', default=True),
])
accept = StateTransition()
def transition_accept(self):
sale = Table('sale_sale')
id_ = Transaction().context['active_id']
cursor = Transaction().connection.cursor()
if id_:
cursor.execute(*sale.update(
columns=[sale.sale_date],
values=[self.start.new_date],
where=sale.id == id_)
)
return 'end'
class SaleDetailedStart(ModelView):
'Sale Detailed Start'
__name__ = 'sale_pos.sale_detailed.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
company = fields.Many2One('company.company',
'Company', required=True)
salesman = fields.Many2One('company.employee', 'Salesman')
party = fields.Many2One('party.party', 'Party')
product = fields.Many2One('product.product', 'Product')
shop = fields.Many2One('sale.shop', 'Shop')
@staticmethod
def default_company():
return Transaction().context.get('company')
class SaleDetailed(Wizard):
'Sale Detailed'
__name__ = 'sale_pos.sale_detailed'
start = StateView('sale_pos.sale_detailed.start',
'sale_pos.sale_detailed_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateAction('sale_pos.report_sale_detailed')
@classmethod
def __setup__(cls):
super(SaleDetailed, cls).__setup__()
def do_print_(self, action):
salesman_id = None
party_id = None
product_id = None
shop_id = None
if self.start.salesman:
salesman_id = self.start.salesman.id
if self.start.shop:
shop_id = self.start.shop.id
if self.start.party:
party_id = self.start.party.id
if self.start.product:
product_id = self.start.product.id
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'salesman': salesman_id,
'party': party_id,
'product': product_id,
'shop': shop_id,
}
return action, data
def transition_print_(self):
return 'end'
class SaleDetailedReport(Report):
__name__ = 'sale_pos.report_sale_detailed'
@classmethod
def get_context(cls, records, header, data):
from operator import itemgetter
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
InvoiceLine = pool.get('account.invoice.line')
Company = pool.get('company.company')
Product = pool.get('product.product')
invoice_dom = [
('invoice_date', '>=', data['start_date']),
('invoice_date', '<=', data['end_date']),
('company', '=', data['company']),
('type', '=', 'out'),
('state', 'in', ['posted', 'paid', 'validated', 'transferred']),
]
if data['salesman']:
invoice_dom.append(
('salesman', '=', data['salesman'])
)
if data['shop']:
invoice_dom.append(
('shop', '=', data['shop'])
)
if data['party']:
invoice_dom.append(
('party', '=', data['party'])
)
fields_inv = [
'id', 'number', 'invoice_date', 'state', 'payment_term.name',
'shop.name', 'party.name', 'invoice_type'
]
invoices = Invoice.search_read(invoice_dom, fields_names=fields_inv,
order=[('invoice_date', 'ASC')]
)
line_dom = [
('product', '!=', None),
('type', '=', 'line'),
]
if data['product']:
line_dom.append(('product', '=', data['product']))
invoices_ids = {inv['id']: inv for inv in invoices}
line_dom.append(('invoice', 'in', invoices_ids.keys()))
fields_lines = [
'id', 'product.name', 'product.code', 'unit.symbol', 'quantity', 'invoice',
'product.last_cost', 'product.cost_price', 'unit_price',
'product.account_category.name', 'taxes'
]
config = pool.get('sale.configuration')(1)
products_exception = []
if hasattr(config, 'tip_product') and config.tip_product and config.exclude_tip_and_delivery:
products_exception.append(config.tip_product.id)
if hasattr(config, 'delivery_product') and config.delivery_product and config.exclude_tip_and_delivery:
products_exception.append(config.delivery_product.id)
if len(products_exception) > 0:
line_dom.append(('product', 'not in', products_exception))
lines = InvoiceLine.search_read(line_dom, fields_names=fields_lines)
untaxed_amount = []
total_amount = []
untaxed_amount_append = untaxed_amount.append
total_amount_append = total_amount.append
get_prices_taxed = Product.get_prices_taxed
dgetter = itemgetter(
'invoice', 'unit_price', 'quantity', 'product.', 'taxes'
)
pt_getter = itemgetter(
'id', 'name', 'cost_price', 'last_cost', 'account_category.'
)
for line in lines:
invoice, unit_price, qty, product, taxes = dgetter(line)
product_id, name, cost_price, last_cost, category = pt_getter(product)
line.update(invoices_ids[invoice])
cost_price = cost_price or last_cost or 0
amount = round(qty * float(unit_price), 2)
utility = unit_price - cost_price
cost_taxed, sale_price_taxed = get_prices_taxed(
product_id, cost_price, unit_price, taxes
)
amount_taxed = float(sale_price_taxed) * qty
line.update({
'amount': amount,
'cost_price': cost_price,
'product': name,
'code':product['code'],
'amount_taxed': amount_taxed,
'total_cost': float(cost_taxed) * qty,
'utility': float(utility) * qty,
'utility_rate': (utility / cost_price) if cost_price else 0,
'sale_price_taxed': sale_price_taxed,
'cost_price_taxed': cost_taxed,
'category': category['name'],
'shop': line['shop.']['name'] if line.get('shop.') else '',
})
untaxed_amount_append(amount)
total_amount_append(amount_taxed)
report_context['records'] = lines
report_context['untaxed_amount'] = sum(untaxed_amount)
report_context['total_amount'] = sum(total_amount)
report_context['company'] = Company(data['company'])
return report_context
class SaleIncomeDailyStart(ModelView):
'Sale Income Daily Start'
__name__ = 'sale_pos.sale_income_daily.start'
company = fields.Many2One('company.company', 'Company', required=True)
date = fields.Date('Date', required=True)
shop = fields.Many2One('sale.shop', 'Shop')
user = fields.Many2One('res.user', 'User')
# journal = fields.Many2One('account.statement.journal', 'Journal')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_shop():
return Transaction().context.get('shop')
@staticmethod
def default_user():
return Transaction().user
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
class SaleIncomeDaily(Wizard):
'Sale Income Daily'
__name__ = 'sale_pos.sale_income_daily'
start = StateView('sale_pos.sale_income_daily.start',
'sale_pos.sale_income_daily_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('sale_pos.sale_income_daily_report')
def do_print_(self, action):
shop_id, user_id = None, None
if self.start.user:
user_id = self.start.user.id
if self.start.shop:
shop_id = self.start.shop.id
report_context = {
'company': self.start.company.id,
'date': self.start.date,
'user': user_id,
'shop': shop_id,
}
return action, report_context
def transition_print_(self):
return 'end'
class SaleIncomeDailyReport(Report):
'Income Daily Report'
__name__ = 'sale_pos.sale_income_daily_report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
Voucher = pool.get('account.voucher')
Company = pool.get('company.company')
company = Company(data['company'])
Statement = pool.get('account.statement')
Shop = pool.get('sale.shop')
User = pool.get('res.user')
records = []
devices = []
statements_ = {}
advances = []
total_advances = []
advances_cash = []
advances_electronic = []
total_statements = []
statement_cash = []
statement_electronic = []
advances_voucher = []
total_payments = []
payments = []
payments_voucher = []
payments_cash = []
payments_electronic = []
dom_statement = [
('date', '=', data['date']),
]
if data['shop']:
dom_statement.append(('sale_device.shop.id', '=', data['shop']))
statements = Statement.search(dom_statement)
user_id = Transaction().user
user = User(user_id)
for statement in statements:
st_amount = sum(l.amount for l in statement.lines)
to_add = {
'journal': statement.journal.name,
'turn': statement.turn,
'total_amount': st_amount,
}
if statement.sale_device.id not in statements_.keys():
statements_[statement.sale_device.id] = {
'name': statement.sale_device.name,
'total': [st_amount],
'records': [to_add]
}
else:
statements_[statement.sale_device.id]['total'].append(st_amount)
statements_[statement.sale_device.id]['records'].append(to_add)
total_statements.append(st_amount)
for l in statement.lines:
if l.statement.journal.kind == 'cash':
statement_cash.append(l.amount)
else:
statement_electronic.append(l.amount)
dom_vouchers = [
('move', '!=', None),
('move.post_date', '=', data['date']),
]
if data['user']:
dom_vouchers.append(
('create_uid', '=', data['user']),
)
vouchers = Voucher.search(dom_vouchers)
for v in vouchers:
cash = 0
electronic = 0
for l in v.lines:
if v.voucher_type == 'receipt':
if v.payment_mode.payment_type == 'cash':
advances_cash.append(l.amount)
cash = l.amount
else:
advances_electronic.append(l.amount)
electronic = l.amount
advances_voucher.append(l.amount)
advances.append({
'number': l.voucher.number,
'reference': l.detail,
'party': l.party.name if l.party else l.voucher.party.name,
'total_amount': l.amount,
'payment_mode': l.voucher.payment_mode.name,
'cash': cash,
'electronic': electronic,
})
total_advances.append(l.amount)
if v.voucher_type == 'payment':
amount_ = l.amount * (-1)
if v.payment_mode.payment_type == 'cash':
payments_cash.append(amount_)
cash = amount_
else:
payments_electronic.append(amount_)
electronic = amount_
payments_voucher.append(amount_)
payments.append({
'number': l.voucher.number,
'reference': l.detail,
'party': l.party.name if l.party else l.voucher.party.name,
'total_amount': amount_,
'payment_mode': l.voucher.payment_mode.name,
'cash': cash,
'electronic': electronic,
})
total_payments.append(amount_)
dom_invoices = [
('company', '=', data['company']),
('invoice_date', '=', data['date']),
('number', '!=', None),
# ('state', 'in', ['posted', 'paid', 'canceled', 'validated']),
('type', '=', 'out'),
]
shop_names = ''
if data['shop']:
shop_names = Shop(data['shop']).name
dom_invoices.append(
('shop', '=', data['shop'])
)
else:
shops = Shop.search([])
for s in shops:
shop_names += s.name + ', '
invoices = Invoice.search(dom_invoices, order=[('number', 'ASC')])
invoices_number = []
total_invoices_cash = []
total_invoices_electronic = []
total_invoices_credit = []
total_invoices_paid = []
devices = {}
total_invoices_amount = []
for invoice in invoices:
invoices_number.append(invoice.number)
cash = 0
electronic = 0
credit = 0
total_invoices_amount.append(invoice.total_amount)
if not invoice.sales:
continue
sale = invoice.sales[0]
device_id = sale.sale_device.id
if not sale.payments:
total_invoices_credit.append(invoice.amount_to_pay)
credit = invoice.amount_to_pay
else:
for p in sale.payments:
if p.statement.date == data['date']:
if p.statement.journal.kind == 'cash':
cash += p.amount
total_invoices_cash.append(p.amount)
else:
electronic += p.amount
total_invoices_electronic.append(p.amount)
total_invoices_paid.append(p.amount)
paid = cash + electronic
inv = {
'number': invoice.number,
'reference': invoice.reference,
'party': invoice.party.name,
'total_amount': invoice.total_amount,
'credit': credit,
'cash': cash,
'electronic': electronic,
'paid': paid,
'state': invoice.state_string,
'sale_device': sale.sale_device.name,
}
records.append(inv)
try:
devices[device_id]['total'].append(invoice.total_amount)
devices[device_id]['cash'].append(cash)
devices[device_id]['electronic'].append(electronic)
devices[device_id]['credit'].append(credit)
devices[device_id]['paid'].append(paid)
except:
devices[device_id] = {
'name': sale.sale_device.name,
'total': [invoice.total_amount],
'credit': [credit],
'cash': [cash],
'electronic': [electronic],
'paid': [paid],
}
advances_cash_ = sum(advances_cash)
advances_electronic_ = sum(advances_electronic)
statement_cash_ = sum(statement_cash)
statement_electronic_ = sum(statement_electronic)
payments_cash_ = sum(payments_cash)
payments_electronic_ = sum(payments_electronic)
report_context['records'] = records
report_context['devices'] = devices.values()
report_context['advances'] = advances
report_context['statements'] = statements_.values()
report_context['date'] = data['date']
report_context['company'] = company.party.name
report_context['shop'] = shop_names
report_context['user'] = user.name
report_context['print_date'] = datetime.now()
report_context['statement_cash'] = statement_cash_
report_context['statement_electronic'] = statement_electronic_
report_context['total_invoices_amount'] = sum(total_invoices_amount)
report_context['total_invoices_cash'] = sum(total_invoices_cash)
report_context['total_invoices_electronic'] = sum(total_invoices_electronic)
report_context['total_invoices_credit'] = sum(total_invoices_credit)
report_context['total_invoices_paid'] = sum(total_invoices_paid)
report_context['total_advances'] = sum(total_advances)
report_context['advances_cash'] = advances_cash_
report_context['advances_electronic'] = advances_electronic_
report_context['advances_voucher'] = sum(advances_voucher)
report_context['total_statements'] = sum(total_statements)
report_context['start_invoice'] = min(invoices_number) if invoices_number else ''
report_context['end_invoice'] = max(invoices_number) if invoices_number else ''
report_context['total_cash'] = advances_cash_ + statement_cash_
report_context['total_electronic'] = advances_electronic_ + statement_electronic_
report_context['total_payments'] = sum(total_payments)
report_context['payments_voucher'] = sum(payments_voucher)
report_context['payments_cash'] = payments_cash_
report_context['payments_electronic'] = payments_electronic_
report_context['payments'] = payments
return report_context
class SaleByKindStart(ModelView):
'Sale by Kind Start'
__name__ = 'sale_pos.sale_by_kind.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
start_time = fields.Time('Start Time')
end_time = fields.Time('End Time')
shop = fields.Many2One('sale.shop', 'Shop')
payment_terms = fields.Many2Many('account.invoice.payment_term', None,
None, 'Payment Term')
kind = fields.Selection([
('', ''),
('delivery', 'Delivery'),
('take_away', 'Take Away'),
('to_table', 'To Table'),
('catering', 'Catering'),
], 'Kind Order',)
party = fields.Many2One('party.party', 'Party')
detailed_by_party = fields.Boolean('Detailed By Party')
including_tax = fields.Boolean('Including Tax')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_shop():
return Transaction().context.get('shop')
@staticmethod
def default_date():
Date = Pool().get('ir.date')
return Date.today()
class SaleByKind(Wizard):
'Sale By Kind'
__name__ = 'sale_pos.sale_by_kind'
start = StateView('sale_pos.sale_by_kind.start',
'sale_pos.sale_by_kind_start_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('sale_pos.sale_by_kind_report')
def do_print_(self, action):
shop_id = None
if self.start.shop:
shop_id = self.start.shop.id
payment_terms = None
if self.start.payment_terms:
payment_terms = [p.id for p in self.start.payment_terms]
report_context = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'start_time': self.start.start_time,
'end_time': self.start.end_time,
'shop': shop_id,
'payment_terms': payment_terms,
'party': self.start.party,
'kind': self.start.kind,
'detailed': self.start.detailed_by_party,
'including_tax': self.start.including_tax,
}
return action, report_context
def transition_print_(self):
return 'end'
class SaleByKindReport(Report):
'Sale by Kind Report'
__name__ = 'sale_pos.sale_by_kind_report'
@classmethod
def get_query(cls, data):
pool = Pool()
Shop = pool.get('sale.shop')
Invoice = pool.get('account.invoice')
Line = pool.get('account.invoice.line')
Party = pool.get('party.party')
InvoiceTax = pool.get('account.invoice.tax')
tables = {}
tables['invoice'] = invoice = Invoice.__table__()
tables['shop'] = shop = Shop.__table__()
tables['party'] = party = Party.__table__()
tables['invoicetax'] = invoicetax = InvoiceTax.__table__()
tables['invoice.line'] = line = Line.__table__()
columns_invoicetax = [
invoice.id.as_('invoice'),
Sum(Coalesce(invoicetax.amount, 0)).as_('amount'),
]
query = invoice.join(
invoicetax, 'LEFT',
condition=invoice.id == invoicetax.invoice).select(
*columns_invoicetax,
group_by=(invoice.id)
)
withs = {}
withs['with_invoicetax'] = with_invoicetax = With(query=query)
columns = [
invoice.shop.as_('shop_id'),
shop.name.as_('shop'),
]
group_by = (invoice.shop, shop.name)
from_item = invoice.join(
shop, condition=shop.id == invoice.shop
).join(party, condition=party.id == invoice.party
).join(
with_invoicetax, 'LEFT',
condition=invoice.id == with_invoicetax.invoice
)
return from_item, tables, columns, group_by, withs
@classmethod
def _where(cls, tables, data):
invoice = tables['invoice']
where = invoice.state.in_(['posted', 'paid', 'validated'])
where &= invoice.invoice_date >= data['start_date']
where &= invoice.invoice_date <= data['end_date']
where &= invoice.company == data['company']
where &= invoice.type == 'out'
if data.get('start_time') and data.get('end_time'):
start_date = datetime.combine(
data['start_date'], data['start_time']) + timedelta(hours=5)
end_date = datetime.combine(
data['end_date'], data['end_time']) + timedelta(hours=5)
where &= invoice.create_date <= end_date
where &= invoice.create_date >= start_date
if data.get('shop'):
where &= invoice.shop == data['shop']
if data.get('party'):
where &= invoice.party == data['party']
if data.get('payment_terms'):
where &= invoice.payment_term.in_(data['payment_terms'])
if data.get('kind'):
where &= invoice.sale_kind == data['kind']
return where
@classmethod
def set_detailed_query(cls, tables, columns, group_by):
party = tables['party']
columns.extend([
party.id.as_('party_id'),
party.name.as_('party'),
])
group_by += (party.id, party.name)
return columns, group_by
@classmethod
def get_columns_kind(cls, tables, withs, type):
invoice = tables['invoice']
line = tables['invoice.line']
invoicetax = withs['with_invoicetax']
condition_case_other = ((invoice.sale_kind == '') | (invoice.sale_kind == Null))
kinds = ['to_table', 'take_away', 'delivery', 'catering', 'others']
columns_count = []
columns_line_amount = []
columns_invoice_amount = []
for k in kinds:
if k != 'others':
columns_count.extend([Sum(Case((invoice.sale_kind == k, 1), else_=0)).as_('sales_'+k)])
columns_line_amount.extend([Sum(Case((invoice.sale_kind == k, (line.quantity*line.unit_price)), else_=0)).as_('amount_'+k)])
columns_invoice_amount.extend([Sum(Case((invoice.sale_kind == k, (invoice.untaxed_amount_cache + invoicetax.amount)), else_=0)).as_('amount_'+k)])
else:
columns_count.extend([Sum(Case((condition_case_other, 1), else_=0)).as_('sales_'+k)])
columns_line_amount.extend([Sum(Case((condition_case_other, (line.quantity*line.unit_price)), else_=0)).as_('amount_'+k)])
columns_invoice_amount.extend([Sum(Case((condition_case_other, (invoice.untaxed_amount_cache + invoicetax.amount)), else_=0)).as_('amount_'+k)])
if type == 'count':
return columns_count
elif type == 'line_amount':
return columns_line_amount
else:
return columns_invoice_amount
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
company = Company(data['company'])
config = pool.get('sale.configuration')(1)
products_exception = []
if hasattr(config, 'tip_product') and config.tip_product and config.exclude_tip_and_delivery:
products_exception.append(config.tip_product.id)
if hasattr(config, 'delivery_product') and config.delivery_product and config.exclude_tip_and_delivery:
products_exception.append(config.delivery_product.id)
from_item, tables, columns, group_by, withs = cls.get_query(data)
invoice = tables['invoice']
columns.extend([Count(invoice.number).as_('sales_total')])
if data.get('detailed'):
columns, group_by = cls.set_detailed_query(
tables, columns, group_by)
columns.extend(cls.get_columns_kind(tables, withs, 'count'))
if data.get('including_tax'):
invoicetax = withs['with_invoicetax']
columns.extend(cls.get_columns_kind(tables, withs, 'invoice_amount'))
columns.extend([
Sum(invoice.untaxed_amount_cache + invoicetax.amount).as_('amount_total')
])
query = from_item.select(
*columns,
where=cls._where(tables, data),
group_by=group_by, with_=withs.values()
)
records, totals = cls.get_values(query, {}, {}, data['detailed'])
if not data.get('including_tax'):
from_item, tables, columns, group_by, withs = cls.get_query(data)
where = cls._where(tables, data)
line = tables['invoice.line']
invoice = tables['invoice']
from_item = from_item.join(line, condition=line.invoice == invoice.id)
if len(products_exception) > 0:
where &= NotIn(line.product, products_exception)
columns.extend([Sum(line.quantity*line.unit_price).as_('amount_total')])
if data.get('detailed'):
columns, group_by = cls.set_detailed_query(
tables, columns, group_by)
columns.extend(cls.get_columns_kind(tables, withs, 'line_amount'))
query = from_item.select(
*columns,
where=where,
group_by=group_by,
with_=withs.values())
records, totals = cls.get_values(
query, records,
totals, data.get('detailed'))
report_context['records'] = records.values()
report_context['company'] = company.rec_name
report_context['total'] = totals if len(totals) > 1 else None
report_context['data'] = data
return report_context
@classmethod
def get_values(cls, query, records, totals, detailed):
cursor = Transaction().connection.cursor()
cursor.execute(*query)
columns = list(cursor.description)
result = cursor.fetchall()
for row in result:
key = row[0]
# key = str(row[0]) + '_' + str(row[2])
if detailed:
key = str(row[0]) + '_' + str(row[3])
row_dict = {}
for i, col in enumerate(columns):
if col.name.startswith('sales') or col.name.startswith('amount'):
try:
totals[col.name] += row[i]
except:
totals[col.name] = row[i]
row_dict[col.name] = row[i]
try:
records[key].update(row_dict)
except:
records[key] = row_dict
return records, totals
class PortfolioPaymentsStart(ModelView):
'Portfolio Payments Start'
__name__ = 'sale_pos.portfolio_payments.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
user = fields.Many2One('res.user', 'User')
salesman = fields.Many2One('company.employee', 'Salesman')
is_post_date = fields.Boolean('Is Post Date')
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_start_date():
today_ = date.today()
# before_month = today_ - timedelta(month=1)
month = today_.month
day = today_.day
year = today_.year
if month == 1:
old_month = 12
else:
old_month = month - 1
_, end_day = calendar.monthrange(year, old_month)
return date(year, old_month, end_day)
@staticmethod
def default_end_date():
return date.today()
class PortfolioPayments(Wizard):
'Portfolio Payments'
__name__ = 'sale_pos.portfolio_payments'
start = StateView('sale_pos.portfolio_payments.start',
'sale_pos.portfolio_payments_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Ok', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('sale_pos.portfolio_payments_report')
def do_print_(self, action):
data = {
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'company': self.start.company.id,
'company_name': self.start.company.rec_name,
'is_post_date': self.start.is_post_date,
}
if self.start.user:
data['user'] = self.start.user.id
if self.start.salesman:
data['salesman'] = self.start.salesman.id
return action, data
def transition_print_(self):
return 'end'
class PortfolioPaymentsReport(Report):
'Portfolio Payments Report'
__name__ = 'sale_pos.portfolio_payments_report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
VoucherLine = pool.get('account.voucher.line')
today = date.today()
dom_search = [
('voucher.company', '=', data['company']),
('voucher.state', '=', 'posted'),
('voucher.voucher_type', '=', 'receipt'),
]
if not data['is_post_date']:
dom_search.append([
('voucher.date', '>=', data['start_date']),
('voucher.date', '<=', data['end_date']), ]
)
else:
dom_search.append([
('voucher.move.post_date', '>=', data['start_date']),
('voucher.move.post_date', '<=', data['end_date']), ]
)
if data.get('user'):
dom_search.append(
('create_uid', '=', data['user']),
)
voucher_lines = []
for line in VoucherLine.search(dom_search):
voucher = line.voucher
move_line = line.move_line
if not move_line:
continue
origin = move_line.move_origin
expiration_date = move_line.maturity_date
expiration_days = (today - expiration_date).days \
if expiration_date else None
if origin and origin.__name__ == 'account.invoice':
shop = origin.shop.rec_name if origin.shop else ''
salesman = origin.salesman.rec_name if origin.salesman else ''
salesman_id = origin.salesman.id if origin.salesman else None
number = origin.number
invoice_date = origin.invoice_date
invoice_amount = origin.total_amount
invoice_balance = origin.amount_to_pay
else:
shop = ''
salesman = ''
salesman_id = ''
number = move_line.reference
invoice_date = ''
invoice_amount = move_line.amount
invoice_balance = None
values = {
'number': voucher.number,
'shop': shop,
'salesman': salesman,
'salesman_id': salesman_id,
'party_id': voucher.party.id_number_full,
'party_name': voucher.party.name,
'date': voucher.date,
'payment_mode': voucher.payment_mode.rec_name,
'check_number': voucher.check_number,
'payment_amount': line.amount,
'invoice_number': number,
'invoice_date': invoice_date,
'expiration_date': expiration_date,
'invoice_amount': invoice_amount,
'invoice_balance': invoice_balance,
'expiration_days': expiration_days,
'state': voucher.state_string,
}
if voucher.state == 'posted':
values['post_date'] = voucher.move.post_date
if data.get('salesman'):
if data['salesman'] == values['salesman_id']:
voucher_lines.append(values)
else:
voucher_lines.append(values)
report_context['records'] = voucher_lines
report_context['company'] = data['company_name']
report_context['sum_amount'] = sum([obj['payment_amount'] for obj in voucher_lines])
return report_context
class SalesCostsStart(ModelView):
'Sales Costs Start'
__name__ = 'sale_pos.sales_costs.start'
company = fields.Many2One('company.company', 'Company', required=True)
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
shop = fields.Many2One('sale.shop', 'Shop')
@staticmethod
def default_company():
return Transaction().context.get('company')
class SalesCosts(Wizard):
'Sales Costs'
__name__ = 'sale_pos.sales_costs'
start = StateView('sale_pos.sales_costs.start',
'sale_pos.sales_costs_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('sale_pos.sales_costs_report')
def do_print_(self, action):
shop_id = None
if self.start.shop:
shop_id = self.start.shop.id
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'shop': shop_id,
}
return action, data
def transition_print_(self):
return 'end'
class SalesCostsReport(Report):
__name__ = 'sale_pos.sales_costs_report'
@classmethod
def get_line(cls, line, date_, number, origin):
return {
'date': date_,
'move': number,
'party': line.party.name if line.party else '',
'reference': line.reference,
'description': line.description,
'origin': origin,
'debit': line.debit,
'credit': line.credit,
}
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
# Invoice = pool.get('account.invoice')
InvoiceLine = pool.get('account.invoice.line')
Company = pool.get('company.company')
Shop = pool.get('sale.shop')
company = Company(data['company'])
shop_name = ''
if data['shop']:
shop = Shop(data['shop'])
shop_name = shop.name
dom = [
('invoice.invoice_date', '>=', data['start_date']),
('invoice.invoice_date', '<=', data['end_date']),
('invoice.move', '!=', None),
('invoice_type', '=', 'out'),
('type', '=', 'line'),
]
if data['shop']:
dom.append(('invoice.shop', '=', data['shop']))
lines = InvoiceLine.search_read(dom, fields_names=[
'product', 'product.rec_name', 'product.code', 'quantity',
'unit_price', 'unit.symbol', 'product.cost_price'
])
records = {}
total_cost = []
total_income = []
append_cost = total_cost.append
append_income = total_income.append
for line in lines:
qty, unit_price = line['quantity'], line['unit_price']
cost_price = line['product.']['cost_price']
income_amount = qty * float(unit_price)
cost_amount = qty * float(cost_price)
append_cost(cost_amount)
append_income(income_amount)
try:
rec_product = records[line['product']]
rec_product['qty'].append(qty)
rec_product['income_amount'].append(income_amount)
rec_product['cost_amount'].append(cost_amount)
except:
code, name = line['product.']['code'], line['product.']['rec_name']
records[line['product']] = {
'code': code,
'name': name,
'category': '',
'uom': line['unit.']['symbol'],
'qty': [qty],
'cost_price': cost_price,
'income_amount': [income_amount],
'cost_amount': [cost_amount],
}
report_context['shop'] = shop_name
report_context['company'] = company
report_context['records'] = records.values()
report_context['total_cost'] = total_cost
report_context['total_income'] = total_income
return report_context
class WizardSalePayment(metaclass=PoolMeta):
__name__ = 'sale.payment'
def transition_pay_(self):
res = super(WizardSalePayment, self).transition_pay_()
Sale = Pool().get('sale.sale')
active_id = Transaction().context.get('active_id', False)
sale = Sale(active_id)
if self.start.do_invoice:
Sale.workflow_to_end([sale])
return res
class SalesAuditStart(ModelView):
'Sales Audit Start'
__name__ = 'sale_pos.sales_audit.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
shop = fields.Many2One('sale.shop', 'Shop', required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
class SalesAudit(Wizard):
'Sale Detailed'
__name__ = 'sale_pos.sales_audit'
start = StateView('sale_pos.sales_audit.start',
'sale_pos.sales_audit_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateAction('sale_pos.sales_audit_report')
def do_print_(self, action):
data = {
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'shop': self.start.shop.id,
}
return action, data
def transition_print_(self):
return 'end'
class SalesAuditReport(Report):
__name__ = 'sale_pos.sales_audit_report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
Sale = pool.get('sale.sale')
Company = pool.get('company.company')
Product = pool.get('product.product')
Shop = pool.get('sale.shop')
shop = Shop(data['shop'])
sales_dom = [
('sale_date', '>=', data['start_date']),
('sale_date', '<=', data['end_date']),
('company', '=', data['company']),
('shop', '=', data['shop'])
# ('state', 'in', ['posted', 'paid', 'validated']),
]
sales = Sale.search(sales_dom, order=[('sale_date', 'ASC')])
records = []
append_rec = records.append
for rec in sales:
ship_number = ""
ship_date = ""
ship_state = ""
inv_number = ""
inv_date = ""
inv_state = ""
move = ""
move_date = ""
sale_cost = []
if rec.shipments:
ship = rec.shipments[0]
ship_number = ship.number
ship_date = ship.effective_date
ship_state = ship.state
elif rec.shipment_returns:
ship = rec.shipment_returns[0]
ship_number = ship.number
ship_date = ship.effective_date
ship_state = ship.state
if rec.invoices:
inv = rec.invoices[0]
inv_number = inv.number
inv_date = inv.invoice_date
inv_state = inv.state_string
if inv.move:
move = inv.move.number
move_date = inv.move.date
for ln in inv.move.lines:
if ln.account.code[0] == '6':
sale_cost.append(ln.debit - ln.credit)
res = {
'sale': rec.number,
'party': rec.party.name,
'reference': rec.reference,
'untaxed_amount': rec.untaxed_amount_cache,
'paid': rec.paid_amount,
'sale_date': rec.sale_date,
'state': rec.state_string,
'ship_number': ship_number,
'ship_date': ship_date,
'ship_state': ship_state,
'inv_number': inv_number,
'inv_date': inv_date,
'inv_state': inv_state,
'account_move': move,
'move_date': move_date,
'sale_cost': sale_cost,
}
print(' sale cost ...', sale_cost)
append_rec(res)
report_context['records'] = records
report_context['shop'] = shop.name
report_context['company'] = Company(data['company'])
return report_context
class DeleteSalesDraft(Wizard):
'Delete Sales Draft'
__name__ = 'sale_pos.delete_sales_draft'
start_state = 'delete_sales_draft'
delete_sales_draft = StateTransition()
@classmethod
def __setup__(cls):
super(DeleteSalesDraft, cls).__setup__()
def transition_delete_sales_draft(self):
cursor = Transaction().connection.cursor()
Sale = Pool().get('sale.sale')
sales = Sale.search(['AND',
['OR', [
('state', '=', 'draft'),
('invoice_number', '=', ''),
('lines', '=', None),
('payments', '=', None)
], [
('state', '=', 'draft'),
('invoice_number', '=', None),
('lines', '=', None),
('payments', '=', None)
],
]])
sales_delete = []
sales_cancel = []
for sale in sales:
if sale.lines:
continue
if sale.payments:
continue
if sale.number:
sales_cancel.append(sale)
else:
sales_delete.append(str(sale.id))
Sale.cancel(sales_cancel)
if len(sales_delete) > 1:
cursor.execute("DELETE from sale_sale WHERE \
id in (%s)" % (', '.join(sales_delete)))
return 'end'