mirror of
https://bitbucket.org/presik/trytonpsk-sale_pos.git
synced 2023-12-14 07:13:02 +01:00
2160 lines
81 KiB
Python
2160 lines
81 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, Or
|
|
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.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')
|
|
# Migrated to sale_shop
|
|
# payments = fields.One2Many('account.statement.line', 'sale', 'Payments')
|
|
# paid_amount = fields.Function(fields.Numeric('Paid Amount', digits=(16, 2)),
|
|
# 'get_paid_amount')
|
|
# residual_amount = fields.Function(fields.Numeric('Residual Amount',
|
|
# digits=(16, 2), readonly=True), 'get_residual_amount')
|
|
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']),
|
|
'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
|
|
|
|
# @staticmethod
|
|
# def default_sale_device():
|
|
# User = Pool().get('res.user')
|
|
# user = User(Transaction().user)
|
|
# return user.sale_device and user.sale_device.id or None
|
|
|
|
@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
|
|
# cls.recheck_taxes(sale)
|
|
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))
|
|
|
|
|
|
# @classmethod
|
|
# def recheck_taxes(cls, sale):
|
|
# for line in sale.lines:
|
|
# vat_required = None
|
|
# if line.product and line.product.account_category:
|
|
# for txr in line.product.account_category.customer_taxes_used:
|
|
# if txr.type == 'percentage' and txr.rate > 0:
|
|
# vat_required = txr.id
|
|
# break
|
|
# if vat_required:
|
|
# tax_obj = [t.id for t in line.taxes if t.rate and t.rate > 0]
|
|
# if vat_required not in tax_obj:
|
|
# raise ProductMissingTaxError(
|
|
# gettext('sale_pos.msg_missing_tax', s=line.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:
|
|
if not l.product or (not hasattr(l.product, 'extra_tax') or not l.product.extra_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)
|
|
|
|
# @classmethod
|
|
# def import_data(cls, fields_names, data):
|
|
# pool = Pool()
|
|
# Product = pool.get('product.product')
|
|
# Party = pool.get('party.party')
|
|
# Invoice = pool.get('account.invoice')
|
|
# SaleLine = pool.get('sale.line')
|
|
# Voucher = pool.get('account.voucher')
|
|
# PaymentMode = pool.get('account.voucher.paymode')
|
|
# Tax = pool.get('account.tax')
|
|
# user_ = pool.get('res.user')(Transaction().user)
|
|
# shop_id = user_.shop.id
|
|
# device_id = user_.sale_device.id
|
|
# count = 0
|
|
#
|
|
# # number_sales = list(set([v[0] for v in data[1:]]))
|
|
# code_products = list(set([v[6] for v in data[1:]]))
|
|
# number_parties = list(set([v[4] for v in data[1:]]))
|
|
#
|
|
# products = Product.search([
|
|
# ('code', 'in', code_products),
|
|
# ('salable', '=', 'true')
|
|
# ])
|
|
# products = [p.code for p in products]
|
|
# not_product = [code for code in code_products if code not in products]
|
|
# if not products or len(not_product) > 0:
|
|
# not_product = code_products if not products else not_product
|
|
# not_product = ', '.join(map(str, not_product))
|
|
# raise ImportSalesError(
|
|
# gettext('sale_pos.msg_import_data_sale', field='producto', s=not_product))
|
|
#
|
|
# parties = Party.search([
|
|
# ('id_number', 'in', number_parties),
|
|
# ])
|
|
# parties = [p.id_number for p in parties]
|
|
# not_party = [party for party in number_parties if party not in parties]
|
|
# if not parties or len(not_party) > 0:
|
|
# not_party = number_parties if not parties else not_party
|
|
# not_party = ', '.join(map(str, not_party))
|
|
# raise ImportSalesError(
|
|
# gettext('sale_pos.msg_import_data_sale', field='tercero', s=not_party))
|
|
#
|
|
# sale_to_create = {}
|
|
# sales_to_pay = []
|
|
# for row in data[1:]:
|
|
# code_ = row[6]
|
|
# products = Product.search([
|
|
# ('code', '=', code_)
|
|
# ])
|
|
# product = products[0]
|
|
# day, month, year = row[3].split('/')
|
|
# sale_date = date(int(year), int(month), int(day))
|
|
# partys = Party.search([
|
|
# ('id_number', '=', row[4])
|
|
# ])
|
|
# party = partys[0]
|
|
# if row[0] not in sale_to_create.keys():
|
|
# # with Transaction().set_context(ctx):
|
|
# sale, = cls.create([{
|
|
# 'sale_date': sale_date,
|
|
# 'party': party.id,
|
|
# 'number': row[0],
|
|
# 'invoice_number': row[0],
|
|
# 'invoice_date': sale_date,
|
|
# 'invoice_type': 'P',
|
|
# 'description': row[1],
|
|
# 'reference': row[2],
|
|
# 'payment_term': 1,
|
|
# 'shop': shop_id,
|
|
# 'sale_device': device_id,
|
|
# 'invoice_address': party.address_get(type='invoice'),
|
|
# 'shipment_address': party.address_get(type='delivery'),
|
|
# }])
|
|
# sale.on_change_party()
|
|
# sale.save()
|
|
# sale_to_create[row[0]] = sale.id
|
|
# payment_modes = PaymentMode.search([
|
|
# ('id', '=', row[10])
|
|
# ])
|
|
# if not payment_modes:
|
|
# not_party
|
|
# raise ImportSalesError(
|
|
# gettext('sale_pos.msg_import_data_sale', field='modo de pago', s=row[10]))
|
|
# sales_to_pay.append({
|
|
# 'sale': sale,
|
|
# 'payment_mode': payment_modes[0]
|
|
# })
|
|
# count += 1
|
|
# sale_id = sale_to_create[row[0]]
|
|
# tax_imp = list(product.customer_taxes_used)
|
|
# if row[9]:
|
|
# tax = Tax.search(['id', '=', row[9]])
|
|
# tax_imp.extend(tax)
|
|
# line = {
|
|
# 'sale': sale_id,
|
|
# 'product': product.id,
|
|
# 'quantity': row[7],
|
|
# 'description': row[5],
|
|
# 'unit_digits': product.sale_uom.digits,
|
|
# 'unit': product.sale_uom,
|
|
# 'unit_price': Decimal(row[8]),
|
|
# 'discount': Decimal('0.00'),
|
|
# 'taxes': [('add', tax_imp)],
|
|
# }
|
|
# SaleLine.create([line])
|
|
#
|
|
# for p in sales_to_pay:
|
|
# sale = p['sale']
|
|
# payment_mode = p['payment_mode']
|
|
# cls.store_cache([sale])
|
|
# sale = cls.process_pos(sale)
|
|
# invoice = sale.invoice
|
|
# invoice.accounting_date = sale.invoice_date
|
|
# cls.post_invoices(sale)
|
|
# sale.state = 'done'
|
|
# sale.invoice_state = 'paid'
|
|
# sale.save()
|
|
# vouchers = Invoice.create_voucher([invoice])
|
|
# for voucher in vouchers:
|
|
# voucher.date = sale.invoice_date
|
|
# voucher.payment_mode = payment_mode
|
|
# voucher.save()
|
|
# Voucher.process([voucher])
|
|
# Voucher.post([voucher])
|
|
#
|
|
# return count
|
|
|
|
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.save()
|
|
Invoice.update_taxes([invoice])
|
|
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()
|
|
sale_date = Date.today()
|
|
cls.write([sale], {
|
|
'invoice_number': number,
|
|
'invoice_date': sale_date,
|
|
})
|
|
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:
|
|
if sale.invoice_date:
|
|
inv_date = sale.invoice_date
|
|
elif sale.shipment_date:
|
|
inv_date = sale.shipment_date
|
|
else:
|
|
inv_date = Date.today()
|
|
|
|
sale.invoice = invoice.id
|
|
to_write = {
|
|
'shop': sale.shop.id,
|
|
'invoice_date': inv_date,
|
|
'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
|
|
@ModelView.button_action('sale_pos.wizard_add_product')
|
|
def wizard_add_product(cls, sales):
|
|
pass
|
|
|
|
@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):
|
|
print('ingresa to reconcile')
|
|
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
|
|
if not sale.payments or not sale.number:
|
|
continue
|
|
try:
|
|
# if 1:
|
|
for invoice in sale.invoices:
|
|
print('ingresa a invoice')
|
|
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 l in invoice.move.lines:
|
|
if l.account.id == account_reconcile_id:
|
|
reconcile_lines.append(l)
|
|
break
|
|
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(l.debit-l.credit for l in reconcile_lines)
|
|
if reconcile_lines and amount_reconcile == 0:
|
|
Reconciliation.create([{
|
|
'lines': [('add', reconcile_lines)],
|
|
'date': TODAY,
|
|
}])
|
|
invoice_.process([invoice_])
|
|
sale.write([sale], {'state': 'done'})
|
|
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']),
|
|
]
|
|
|
|
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
|
|
)
|
|
print(cost_taxed, sale_price_taxed, 'casdf')
|
|
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['start_time'] and data['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['shop']:
|
|
where &= invoice.shop == data['shop']
|
|
|
|
if data['party']:
|
|
where &= invoice.party == data['party']
|
|
if data['payment_terms']:
|
|
where &= invoice.payment_term.in_(data['payment_terms'])
|
|
if data['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['detailed']:
|
|
columns, group_by = cls.set_detailed_query(
|
|
tables, columns, group_by)
|
|
|
|
columns.extend(cls.get_columns_kind(tables, withs, 'count'))
|
|
if data['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['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['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['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 SaleGenerateShipment(Wizard):
|
|
"""
|
|
Sale Generate Shipment
|
|
"""
|
|
__name__ = 'sale.sale.generate_shipment'
|
|
start_state = 'generate_shipment'
|
|
generate_shipment = StateTransition()
|
|
|
|
@classmethod
|
|
def __setup__(cls):
|
|
super(SaleGenerateShipment, cls).__setup__()
|
|
|
|
def transition_generate_shipment(self):
|
|
Sale = Pool().get('sale.sale')
|
|
ids = Transaction().context['active_ids']
|
|
if ids:
|
|
sales = Sale.browse(ids)
|
|
for sale in sales:
|
|
if sale.shipment_state != 'none':
|
|
continue
|
|
if sale.total_amount_cache < 0 if sale.total_amount_cache else sale.total_amount < 0:
|
|
sale.create_shipment('return')
|
|
sale.set_shipment_state()
|
|
else:
|
|
sale.create_shipment('out')
|
|
sale.set_shipment_state()
|
|
return 'end'
|
|
|
|
|
|
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
|