trytonpsk-sale_shop/sale.py

1208 lines
44 KiB
Python

# This file is part sale_shop module for Tryton.
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
import logging
from decimal import Decimal
from datetime import date, timedelta
import calendar
from multiprocessing import context
from unittest import result
from sql.aggregate import Sum
from sql.functions import Extract
from sql.conditionals import Case
from sql.operators import In
from trytond.model import fields, Unique, ModelView
from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, Or, Not,If
from trytond.report import Report
from trytond.wizard import (
Wizard, StateView, StateAction, StateReport, Button, StateTransition
)
from trytond.i18n import gettext
from .exceptions import (
SaleMissingSequenceError, ShopUserError, SaleWriteError, PartyMissingAccount
)
from trytond.modules.sale.exceptions import SaleValidationError
logger = logging.getLogger(__name__)
_ZERO = Decimal('0.00')
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
shop = fields.Many2One('sale.shop', 'Shop', required=True, domain=[
('id', 'in', Eval('context', {}).get('shops', [])),
], states={
'readonly': Or(Bool(Eval('number')), Bool(Eval('lines'))),
}, depends=['number', 'lines'])
shop_address = fields.Function(fields.Many2One('party.address',
'Shop Address'),
'on_change_with_shop_address')
source = fields.Many2One('sale.source', 'Source')
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')
@classmethod
def __setup__(cls):
super(Sale, cls).__setup__()
cls._buttons.update({
'wizard_sale_payment': {
'invisible': Eval('state') == 'done',
'readonly': Not(Bool(Eval('lines'))),
},
})
cls.state_string = super(Sale, cls).state.translated('state')
t = cls.__table__()
cls._sql_constraints.extend([
('number_uniq', Unique(t, t.shop, t.number),
'There is another sale with the same number.\n'
'The number of the sale must be unique!')
])
shipment_addr_domain = cls.shipment_address.domain[:]
if shipment_addr_domain:
cls.shipment_address.domain = [
'OR',
shipment_addr_domain,
[('id', '=', Eval('shop_address', 0))],
]
else:
cls.shipment_address.domain = [('id', '=', Eval('shop_address'))]
cls.shipment_address.depends.append('shop_address')
cls.currency.states['readonly'] = Eval('state') != 'draft'
cls.currency.depends.append('shop')
@staticmethod
def default_company():
User = Pool().get('res.user')
user = User(Transaction().user)
return user.shop.company.id if user.shop else \
Transaction().context.get('company')
@fields.depends('company')
def on_change_company(self):
User = Pool().get('res.user')
user = User(Transaction().user)
if not user.shop:
super(Sale, self).on_change_company()
else:
self.invoice_method = user.shop.sale_invoice_method
self.shipment_method = user.shop.sale_shipment_method
self.payment_term = user.shop.payment_term.id
@staticmethod
def default_shop():
User = Pool().get('res.user')
user = User(Transaction().user)
return user.shop.id if user.shop else None
@staticmethod
def default_description():
config = Pool().get('sale.configuration')(1)
if config.default_description:
return config.default_description
@staticmethod
def default_warehouse():
User = Pool().get('res.user')
user = User(Transaction().user)
if user.shop:
return user.shop.warehouse.id
else:
Location = Pool().get('stock.location')
return Location.search([('type', '=', 'warehouse')], limit=1)[0].id
@staticmethod
def default_price_list():
User = Pool().get('res.user')
user = User(Transaction().user)
return user.shop.price_list.id if user.shop else None
@staticmethod
def default_shop_address():
User = Pool().get('res.user')
user = User(Transaction().user)
return (user.shop and user.shop.address and
user.shop.address.id or None)
@classmethod
def copy(cls, sales, default=None):
if default is None:
default = {}
default['payments'] = None
default['source'] = None
return super(Sale, cls).copy(sales, default)
@fields.depends('shop', 'party')
def on_change_shop(self):
if not self.shop:
return
for fname in ('company', 'warehouse', 'currency', 'payment_term'):
fvalue = getattr(self.shop, fname)
if fvalue:
setattr(self, fname, fvalue.id)
if ((not self.party or not self.party.sale_price_list)
and self.shop.price_list):
self.price_list = self.shop.price_list.id
if self.shop.sale_invoice_method:
self.invoice_method = self.shop.sale_invoice_method
if self.shop.sale_shipment_method:
self.shipment_method = self.shop.sale_shipment_method
@fields.depends('shop')
def on_change_with_shop_address(self, name=None):
return (self.shop and self.shop.address and
self.shop.address.id or None)
@fields.depends('shop', 'party')
def on_change_party(self):
super(Sale, self).on_change_party()
if self.shop:
if not self.price_list and self.invoice_address:
self.price_list = self.shop.price_list.id
self.price_list.rec_name = self.shop.price_list.rec_name
if not self.payment_term and self.invoice_address:
self.payment_term = self.shop.payment_term.id
self.payment_term.rec_name = self.shop.payment_term.rec_name
@classmethod
def get_paid_amount(cls, sales, names):
result = {n: {s.id: Decimal(0) for s in sales} for n in names}
for name in names:
for sale in sales:
for payment in sale.payments:
result[name][sale.id] += payment.amount
result[name][sale.id] += sale.get_total_vouchers_amount()
return result
def get_residual_amount(self, name=None):
total = self.total_amount_cache
if not total:
total = self.total_amount
return (total - self.paid_amount)
@classmethod
def set_number(cls, sales):
'''
Fill the number field with the sale shop or sale config sequence
'''
pool = Pool()
Config = pool.get('sale.configuration')
User = Pool().get('res.user')
config = Config(1)
user = User(Transaction().user)
for sale in sales:
if sale.number:
continue
elif sale.shop:
if sale.total_amount >= Decimal('0'):
number = sale.shop.sale_sequence.get()
else:
if not sale.shop.sale_return_sequence:
raise SaleMissingSequenceError(
gettext('sale_shop.msg_no_return_sequence'))
number = sale.shop.sale_return_sequence.get()
elif user.shop:
number = user.shop.sale_sequence.get()
else:
number = config.sale_sequence.get()
cls.write([sale], {
'number': number,
})
@classmethod
def create(cls, vlist):
vlist2 = []
for vals in vlist:
User = Pool().get('res.user')
user = User(Transaction().user)
vals = vals.copy()
if not 'shop' in vals:
if not user.shop:
raise ShopUserError(
gettext('sale_shop.not_sale_shop', s=user.rec_name))
vals['shop'] = user.shop.id
vlist2.append(vals)
return super(Sale, cls).create(vlist2)
@classmethod
@ModelView.button_action('sale_shop.wizard_sale_payment')
def wizard_sale_payment(cls, sales):
pass
@classmethod
def write(cls, *args):
'''
Only edit Sale users available edit in this shop
'''
User = Pool().get('res.user')
user = User(Transaction().user)
if user.id != 0:
actions = iter(args)
for sales, _ in zip(actions, actions):
for sale in sales:
if not sale.shop:
raise SaleWriteError(
gettext('sale_shop.msg_sale_not_shop'))
if not sale.shop in user.shops:
raise SaleWriteError(gettext('sale_shop.msg_edit_sale_by_shop'))
super(Sale, cls).write(*args)
def _get_invoice_sale(self):
invoice = super(Sale, self)._get_invoice_sale()
invoice.reference = self.reference
invoice.description = self.description
invoice.comment = self.comment
invoice.shop = self.shop.id
invoice.invoice_type = self.invoice_type
return invoice
class SaleLine(metaclass=PoolMeta):
__name__ = 'sale.line'
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price with Tax',
digits=(16, Eval('_parent_sale', {}).get('currency_digits',
Eval('currency_digits', 2))),
states={
'invisible': Eval('type') != 'line',
},
depends=['type']), 'get_price_with_tax')
amount_w_tax = fields.Function(fields.Numeric('Amount with Tax',
digits=(16, Eval('_parent_sale', {}).get('currency_digits',
Eval('currency_digits', 2))),
states={
'invisible': ~Eval('type').in_(['line', 'subtotal']),
},
depends=['type']), 'get_price_with_tax')
unit_price_full = fields.Numeric('Unit Price Full',
digits=(16, Eval('_parent_sale', {}).get('currency_digits',
Eval('currency_digits', 2))),
states={
'invisible': Eval('type') != 'line',
'readonly': True,
},
depends=['type', 'currency_digits'])
@staticmethod
def default_currency_digits():
Company = Pool().get('company.company')
if Transaction().context.get('company'):
company = Company(Transaction().context['company'])
return company.currency.digits
return 2
@staticmethod
def default_currency():
Company = Pool().get('company.company')
if Transaction().context.get('company'):
company = Company(Transaction().context['company'])
return company.currency.id
@classmethod
def get_price_with_tax(cls, lines, names):
pool = Pool()
Tax = pool.get('account.tax')
Company = pool.get('company.company')
amount_w_tax = {}
unit_price_w_tax = {}
def compute_amount_with_tax(line):
tax_amount = _ZERO
amount = _ZERO
if line.taxes:
tax_list = Tax.compute(line.taxes, line.unit_price or _ZERO,
line.quantity or 0.0)
tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
if line.unit_price:
amount = line.unit_price * Decimal(line.quantity)
if hasattr(line.product, 'extra_tax') and line.product.extra_tax:
amount += line.product.extra_tax * Decimal(line.quantity)
return amount + tax_amount
for line in lines:
amount = _ZERO
unit_price = _ZERO
if line.sale:
currency = line.sale.currency
else:
# company = Company(Transaction().context.get('company'))
company = Company(1)
currency = company.currency
if line.type == 'line' and line.quantity:
amount = compute_amount_with_tax(line)
unit_price = amount / Decimal(str(line.quantity))
# Only compute subtotals if the two fields are provided to speed up
elif line.type == 'subtotal' and len(names) == 2:
for line2 in line.sale.lines:
if line2.type == 'line':
amount2 = compute_amount_with_tax(line2)
if currency:
amount2 = currency.round(amount2)
amount += amount2
elif line2.type == 'subtotal':
if line == line2:
break
amount = _ZERO
if currency:
amount = currency.round(amount)
amount_w_tax[line.id] = amount
unit_price_w_tax[line.id] = unit_price
result = {
'amount_w_tax': amount_w_tax,
'unit_price_w_tax': unit_price_w_tax,
}
to_remove = []
for key in list(result.keys()):
if key not in names:
to_remove.append(key)
for rm in to_remove:
del result[key]
return result
@fields.depends('product', 'unit', 'quantity', 'description',
'_parent_sale.party', '_parent_sale.currency', '_parent_sale.sale_date')
def on_change_product(self):
super(SaleLine, self).on_change_product()
if self.product:
desc = self.product.template.name
if self.product.description:
desc += ' | ' + self.product.description
self.description = desc
@fields.depends('product', 'quantity', 'unit', 'taxes',
'_parent_sale.currency', '_parent_sale.party',
'_parent_sale.sale_date', 'unit_price_full')
def on_change_quantity(self):
super(SaleLine, self).on_change_quantity()
if self.unit_price_full:
self.on_change_unit_price_full()
class SaleBySupplierStart(ModelView):
'Sale By Supplier Start'
__name__ = 'sale_shop.sale_by_supplier.start'
start_date = fields.Date('Start Date', required=True)
end_date = fields.Date('End Date', required=True)
suppliers = fields.Many2Many('party.party', None, None, 'Suppliers')
shop = fields.Many2One('sale.shop', 'Shop')
company = fields.Many2One('company.company', 'Company', required=True)
detailed = fields.Boolean('Detailed', select=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_end_date():
Date_ = Pool().get('ir.date')
return Date_.today()
class PrintSaleBySupplier(Wizard):
'Sale By Supplier'
__name__ = 'sale_shop.print_sale_by_supplier'
start = StateView('sale_shop.sale_by_supplier.start',
'sale_shop.print_sale_by_supplier_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-print', default=True),
])
print_ = StateReport('sale_shop.sale_by_supplier_report')
def do_print_(self, action):
suppliers = [s.id for s in self.start.suppliers]
shop_id = None
shop_name = ''
if self.start.shop:
shop_id = self.start.shop.id
shop_name = self.start.shop.name
data = {
'ids': [],
'company': self.start.company.id,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'suppliers': suppliers,
'detailed': self.start.detailed,
'shop': shop_id,
'shop_name': shop_name,
}
return action, data
def transition_print_(self):
return 'end'
class SaleBySupplier(Report):
'Sale By Supplier Report'
__name__ = 'sale_shop.sale_by_supplier_report'
@classmethod
def get_last_purchase(cls, product_id):
PurchaseLine = Pool().get('purchase.line')
lines = PurchaseLine.search([
('product', '=', product_id),
('purchase.state', 'in', ('processing', 'done')),
], order=[('create_date', 'DESC')], limit=1)
if lines:
return (lines[0].purchase.purchase_date,
lines[0].quantity)
else:
return None, None
@classmethod
def get_margin(cls, pline):
res = 0
total_cost = pline['cost_price'] * pline['quantity']
if pline['amount'] != 0:
res = 1 - (total_cost / pline['amount'])
return res
@classmethod
def get_last_sales(cls, product_id, end_date, days):
InvoiceLine = Pool().get('account.invoice.line')
start_date = end_date - timedelta(days=days)
lines = InvoiceLine.search([
('product', '=', product_id),
('invoice.invoice_date', '>=', start_date),
('invoice.invoice_date', '<=', end_date),
('invoice.state', 'in', ('posted', 'paid')),
('invoice.type', '=', 'out'),
('type', '=', 'line'),
])
return sum([l.quantity for l in lines]), start_date
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Company = pool.get('company.company')
Party = pool.get('party.party')
ProductSupplier = pool.get('purchase.product_supplier')
InvoiceLine = pool.get('account.invoice.line')
company = Company(data['company'])
cursor = Transaction().connection.cursor()
dom_suppliers = [
('template', '!=', None)
]
total_sales = []
total_sales_append = total_sales.append
total_cost = []
parties_query = ''
start_date = data['start_date']
end_date = data['end_date']
delta = (data['end_date'] - data['start_date']).days + 1
if data['suppliers']:
dom_suppliers.append([
('party', 'in', data['suppliers']),
])
if len(data['suppliers']) == 1:
supplier = data['suppliers'][0]
parties_query = f' AND pu.party={supplier}'
else:
parties = str(tuple(data['suppliers']))
parties_query = f' AND pu.party IN {parties}'
products_suppliers = ProductSupplier.search(dom_suppliers)
products_dict = {}
for ps in products_suppliers:
if ps.template.products:
product_id = ps.template.products[0].id
products_dict[product_id] = ps.party.id
products_query = ''
if data['suppliers']:
keys = products_dict.keys()
if len(keys) == 1:
product_id = keys[0]
products_query = f' AND al.product={product_id} '
elif keys:
products_ids = str(tuple(keys))
products_query = f' AND al.product IN {products_ids} '
shops_query = ''
start_date = data['start_date']
if data['shop']:
shop = data['shop']
shops_query = f' AND ai.shop={shop} '
query = f"""
SELECT
al.product,
pp.code,
pt.name,
SUM(al.unit_price * al.quantity) AS amount,
SUM(pc.cost_price * al.quantity) AS cost,
SUM(al.quantity) AS quantity
FROM account_invoice_line AS al
JOIN account_invoice AS ai ON al.invoice=ai.id
JOIN product_product AS pp ON al.product=pp.id
JOIN product_template AS pt ON pp.template=pt.id
JOIN product_cost_price AS pc ON pc.product=pp.id
WHERE ai.invoice_date>='{start_date}' AND ai.invoice_date<='{end_date}'
AND ai.state IN ('posted', 'paid')
AND ai.type='out' AND al.type='line'
{products_query}
{shops_query}
GROUP BY al.product, pp.code, pt.name
"""
cursor.execute(query)
_records = cursor.fetchall()
def compute_margin(sales, costs):
margin = 0
if sales and sales != 0:
margin = 1 - costs / sales
return margin
# suppliers = {0: {
# 'name': 'NA',
# 'products': [],
# 'sales': [],
# 'costs': [],
# }}
suppliers = {}
def _rec2dict(rec):
return {
'code': rec[1],
'name': rec[2],
'amount': rec[3],
'quantity': rec[5],
'margin': compute_margin(rec[3], rec[4]),
'daily_sale': rec[5] / delta,
}
for rec in _records:
total_sales_append(rec[3])
pd_id = rec[0]
party_id = products_dict.get(pd_id, 0)
try:
if data['detailed']:
suppliers[party_id]['products'].append(_rec2dict(rec))
suppliers[party_id]['sales'].append(rec[3])
suppliers[party_id]['costs'].append(rec[4])
except Exception as e:
parties = Party.search_read([
('id', '=', int(party_id))
], fields_names=['name'])
if not parties:
continue
party = parties[0]
party_id = party['id']
suppliers[party_id] = {
'name': party['name'],
'sales': [rec[3]],
'costs': [rec[4]]
}
if data['detailed']:
suppliers[party_id]['products'] = [_rec2dict(rec)]
for key, value in suppliers.items():
suppliers[key]['sales'] = sum(value['sales'])
suppliers[key]['costs'] = sum(value['costs'])
# Maybe we need this in the future
# sales_30, date_ = cls.get_last_sales(pline['product_id'],
# data['start_date'], 30)
# sales_60, _ = cls.get_last_sales(pline['product_id'],
# date_, 60)
records = suppliers.values()
report_context['records'] = records
report_context['start_date'] = data['start_date']
report_context['end_date'] = data['end_date']
report_context['today'] = date.today()
report_context['company'] = company.party.name
report_context['shop'] = data['shop_name']
report_context['total_sales'] = sum(total_sales)
report_context['detailed'] = data['detailed']
report_context['compute_margin'] = compute_margin
return report_context
class SaleShopDetailedStart(ModelView):
'Sale Shop Detailed Start'
__name__ = 'sale_shop.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 SaleShopDetailed(Wizard):
'Sale Shop Detailed'
__name__ = 'sale_shop.sale_detailed'
start = StateView('sale_shop.sale_detailed.start',
'sale_shop.sale_shop_detailed_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateAction('sale_shop.report_sale_detailed')
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 SaleShopDetailedReport(Report):
'Sale Shop Detailed Report'
__name__ = 'sale_shop.report_sale_detailed'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
InvoiceLine = pool.get('account.invoice.line')
Company = pool.get('company.company')
invoice_line_dom = [
('invoice.invoice_date', '>=', data['start_date']),
('invoice.invoice_date', '<=', data['end_date']),
('invoice.company', '=', data['company']),
('invoice.type', '=', 'out'),
('invoice.state', 'in', ['posted', 'paid', 'validated']),
]
if data['salesman']:
invoice_line_dom.append(
('invoice.salesman', '=', data['salesman'])
)
if data['shop']:
invoice_line_dom.append(
('invoice.shop', '=', data['shop'])
)
if data['party']:
invoice_line_dom.append(
('invoice.party', '=', data['party'])
)
if data['product']:
invoice_line_dom.append(
('product', '=', data['product'])
)
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:
invoice_line_dom.append(('product', 'not in', products_exception))
start_lines = InvoiceLine.search(
invoice_line_dom, order=[('invoice.invoice_date', 'ASC')]
)
lines = []
amount = []
# amount_w_tax = []
for line in start_lines:
if line.type == 'line' and not line.product:
raise SaleValidationError(
gettext('sale_shop.msg_sale_not_product', s=line.sale.number
))
amount.append(line.amount)
lines.append(line)
# is_invoiced = False
# if line.invoice:
# for inv in line.sale.invoices:
# if line.invoice.state not in ['draft', 'cancel']:
# is_invoiced = True
# if is_invoiced:
# lines.append(line)
# # amount_w_tax.append(line.amount_w_tax)
# else:
# amount.append(line.amount)
# # amount_w_tax.append(line.amount_w_tax)
# report_context['amount_w_tax'] = sum(amount_w_tax)
report_context['amount'] = sum(amount)
report_context['records'] = lines
report_context['company'] = Company(data['company'])
return report_context
class SaleBySellerStart(ModelView):
'Sale By Seller Start'
__name__ = 'sale_shop.sale_by_seller.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',
depends=['company'], domain=[
('company', '=', Eval('company'))
])
payment_method = fields.Many2One('account.invoice.payment_term', 'Payment Method', required=True)
@staticmethod
def default_company():
return Transaction().context.get('company')
class SaleBySeller(Wizard):
'Sale By Seller'
__name__ = 'sale_shop.sale_by_seller.wizard'
start = StateView('sale_shop.sale_by_seller.start',
'sale_shop.sale_by_seller_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateAction('sale_shop.sale_by_seller_report_')
def do_print_(self, action):
data = {
'ids': [],
'company': self.start.company.id,
'company_name': self.start.company.rec_name,
'start_date': self.start.start_date,
'end_date': self.start.end_date,
'shop': None,
'shop_name': None,
'payment_method': self.start.payment_method.id,
}
shop = self.start.shop
if shop:
data['shop'] = self.start.shop.id
data['shop_name'] = self.start.shop.rec_name
return action, data
def transition_print_(self):
return 'end'
class SaleBySellerReport(Report):
__name__ = 'sale_shop.sale_by_seller_report'
@classmethod
def get_context(cls, records, header, data):
report_context = super().get_context(records, header, data)
pool = Pool()
Invoice = pool.get('account.invoice')
Company = pool.get('company.company')
shop = data['shop']
invoice_dom = [
('invoice_date', '>=', data['start_date']),
('invoice_date', '<=', data['end_date']),
('company', '=', data['company']),
('state', 'in', ['posted', 'paid']),
('type', '=', 'out'),
]
if shop:
invoice_dom.append(
('shop', '=', shop),
)
invoices = Invoice.search(invoice_dom)
lines = {}
sum_total_amount = 0
sum_total_cash = 0
sum_total_credit = 0
for line in invoices:
seller = line.salesman
if seller:
cash = 0
credit = 0
seller_id = line.salesman.id
payment_term = line.payment_term.id
amount = line.total_amount
if payment_term == data['payment_method']:
cash = amount
else:
credit = amount
if seller_id not in lines.keys():
lines[seller_id] = {
'shop': line.shop.rec_name,
'seller': line.salesman.rec_name,
'cash': cash,
'credit': credit,
'amount': amount,
}
sum_total_amount += amount
sum_total_cash += cash
sum_total_credit += credit
else:
lines[seller_id]['cash'] += cash
lines[seller_id]['credit'] += credit
lines[seller_id]['amount'] += amount
sum_total_amount += amount
sum_total_cash += cash
sum_total_credit += credit
report_context['records'] = lines.values()
report_context['sum_total_amount'] = sum_total_amount
report_context['sum_total_cash'] = sum_total_cash
report_context['sum_total_credit'] = sum_total_credit
report_context['company'] = Company(data['company'])
report_context['company_name'] = data['company_name']
return report_context
class SaleMonthByShopStart(ModelView):
'Sale Month By Shop Start'
__name__ = 'sale_shop.sale_month_shop.start'
company = fields.Many2One('company.company', 'Company', required=True)
shop = fields.Many2One('sale.shop', 'Shop', required=True,
depends=['company'], domain=[
('company', '=', Eval('company'))
])
fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscal Year',
required=True)
period = fields.Many2One('account.period', 'Period',
depends=['fiscalyear'], required=True, domain=[
('fiscalyear', '=', Eval('fiscalyear')),
])
@staticmethod
def default_company():
return Transaction().context.get('company')
@staticmethod
def default_fiscalyear():
FiscalYear = Pool().get('account.fiscalyear')
return FiscalYear.find(
Transaction().context.get('company'), exception=False)
@fields.depends('fiscalyear')
def on_change_fiscalyear(self):
self.period = None
class SaleMonthByShop(Wizard):
'Sale MonthByShop'
__name__ = 'sale_shop.sale_month_shop'
start = StateView('sale_shop.sale_month_shop.start',
'sale_shop.sale_month_shop_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Print', 'print_', 'tryton-ok', default=True),
])
print_ = StateReport('sale_shop.sale_month_shop_report')
def do_print_(self, action):
data = {
'company': self.start.company.id,
'shop': self.start.shop.id,
'period': self.start.period.id,
'fiscalyear': self.start.fiscalyear.id,
}
return action, data
def transition_print_(self):
return 'end'
class SaleMonthByShopReport(Report):
'Sale Month By Shop'
__name__ = 'sale_shop.sale_month_shop_report'
@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'])
Invoice = pool.get('account.invoice')
Line = pool.get('account.invoice.line')
Payment_term = pool.get('account.invoice.payment_term')
Period = pool.get('account.period')
Shop = pool.get('sale.shop')
period = Period(data['period'])
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)
totals = {}
if len(products_exception) > 0:
invoice = Invoice.__table__()
line = Line.__table__()
payment_term = Payment_term.__table__()
where = invoice.state.in_(['posted', 'paid', 'validated'])
where &= invoice.invoice_date >= period.start_date
where &= In(line.product, products_exception)
where &= invoice.invoice_date <= period.end_date
where &= invoice.shop == data['shop']
where &= invoice.company == data['company']
where &= invoice.type == 'out'
cursor = Transaction().connection.cursor()
cursor.execute(*line.join(
invoice, condition=line.invoice == invoice.id).join(
payment_term, condition=invoice.payment_term == payment_term.id).select(
Extract('DAY', invoice.invoice_date).as_('Day'),
Sum(Case((payment_term.payment_type == '1', line.quantity*line.unit_price), else_=0)).as_('amount_cash'),
Sum(Case((payment_term.payment_type != '1', line.quantity*line.unit_price), else_=0)).as_('amount_credit'),
Sum(line.quantity*line.unit_price).as_('amount'),
where=where,
group_by=(Extract('DAY', invoice.invoice_date))
))
result = cursor.fetchall()
columns = list(cursor.description)
for row in result:
row_dict = {}
for i, col in enumerate(columns):
row_dict[col.name] = row[i]
totals[int(row[0])] = row_dict
invoices = Invoice.search([
('company', '=', data['company']),
('shop', '=', data['shop']),
('invoice_date', '>=', period.start_date),
('invoice_date', '<=', period.end_date),
('type', '=', 'out'),
('state', 'in', ['posted', 'paid', 'validated']),
], order=[('invoice_date', 'ASC')])
year = period.start_date.year
month = period.start_date.month
_, last_day = calendar.monthrange(year, period.start_date.month)
mdays = {(nday + 1): {
'date': date(year, month, nday + 1),
'num_invoices': [],
'untaxed_amount': [],
'tax_amount': [],
'total_amount': [],
'cash': [],
'credit': []
} for nday in range(last_day)}
sum_untaxed_amount = []
sum_total_cash = []
sum_total_credit = []
sum_tax_amount = []
sum_total_amount = []
def _is_credit(payment_term):
res = []
for line in payment_term.lines:
res.append(sum([d.months + d.weeks + d.days
for d in line.relativedeltas]))
if sum(res) > 0:
return True
return False
for invoice in invoices:
if _is_credit(invoice.payment_term):
mdays[invoice.invoice_date.day]['credit'].append(invoice.total_amount)
else:
mdays[invoice.invoice_date.day]['cash'].append(invoice.total_amount)
mdays[invoice.invoice_date.day]['num_invoices'].append(1)
mdays[invoice.invoice_date.day]['untaxed_amount'].append(invoice.untaxed_amount)
mdays[invoice.invoice_date.day]['tax_amount'].append(invoice.tax_amount)
mdays[invoice.invoice_date.day]['total_amount'].append(invoice.total_amount)
for k, v in mdays.items():
if k in totals.keys():
mdays.update({k: {
'date': mdays[k]['date'],
'num_invoices': sum(mdays[k]['num_invoices']),
'untaxed_amount': sum(mdays[k]['untaxed_amount']) - Decimal(totals[k]['amount']),
'tax_amount': sum(mdays[k]['tax_amount']),
'total_amount': sum(mdays[k]['total_amount']) - Decimal(totals[k]['amount']),
'cash': sum(mdays[k]['cash']) - Decimal(totals[k]['amount_cash']),
'credit': sum(mdays[k]['credit']) - Decimal(totals[k]['amount_credit'])
}
})
else:
mdays.update({k: {
'date': mdays[k]['date'],
'num_invoices': sum(mdays[k]['num_invoices']),
'untaxed_amount': sum(mdays[k]['untaxed_amount']),
'tax_amount': sum(mdays[k]['tax_amount']),
'total_amount': sum(mdays[k]['total_amount']),
'cash': sum(mdays[k]['cash']),
'credit': sum(mdays[k]['credit'])
}
})
sum_total_cash.append(mdays[k]['cash'])
sum_total_credit.append(mdays[k]['credit'])
sum_untaxed_amount.append(mdays[k]['untaxed_amount'])
sum_tax_amount.append(mdays[k]['tax_amount'])
sum_total_amount.append(mdays[k]['total_amount'])
report_context['records'] = mdays.values()
report_context['total_credit'] = sum(sum_total_credit)
report_context['total_cash'] = sum(sum_total_cash)
report_context['untaxed_amount'] = sum(sum_untaxed_amount)
report_context['tax_amount'] = sum(sum_tax_amount)
report_context['total_amount'] = sum(sum_total_amount)
report_context['shop'] = Shop(data['shop']).name
report_context['period'] = period.name
report_context['company'] = company.party.name
return report_context
class SalePaymentForm(ModelView):
'Sale Payment Form'
__name__ = 'sale.payment.form'
statement = fields.Many2One('account.statement', 'Statement',
required=True)
amount_payable = fields.Numeric('Amount Payable', required=True,
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits'])
currency_digits = fields.Integer('Currency Digits')
party = fields.Many2One('party.party', 'Party', readonly=True)
require_voucher = fields.Boolean('Require Voucher',
depends=['statement'])
voucher = fields.Char('Voucher Number', states={
'required': Eval('require_voucher', False),
'invisible': Not(Eval('require_voucher', False)),
}, depends=['require_voucher'])
self_pick_up = fields.Boolean('Self Pick Up', readonly=True)
do_invoice = fields.Boolean('Do Invoice')
@classmethod
def __setup__(cls):
super(SalePaymentForm, cls).__setup__()
@fields.depends('statement', 'voucher')
def on_change_with_require_voucher(self):
if self.statement:
return self.statement.journal.require_voucher
else:
return False
@classmethod
def default_require_voucher(cls):
return False
@classmethod
def default_do_invoice(cls):
return True
class WizardSalePayment(Wizard):
'Wizard Sale Payment'
__name__ = 'sale.payment'
start = StateView('sale.payment.form',
'sale_shop.sale_payment_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Pay', 'pay_', 'tryton-ok', default=True),
])
pay_ = StateTransition()
@classmethod
def __setup__(cls):
super(WizardSalePayment, cls).__setup__()
@classmethod
def execute(cls, session_id, data, state_name):
pool = Pool()
Sale = pool.get('sale.sale')
sale = Sale(Transaction().context['active_id'])
result = super().execute(session_id, data, state_name)
dom_statement = [
('state', '=', 'draft')
]
context = Transaction().context
if context.get('sale_device', None) and sale.sale_device:
dom_statement.append(('sale_device', '=', sale.sale_device.id))
if result.get('view'):
result['view']['fields_view']['fields']['statement']['domain']= dom_statement
return result
def default_start(self, fields):
pool = Pool()
Sale = pool.get('sale.sale')
sale = Sale(Transaction().context['active_id'])
return {
'amount_payable': abs(sale.total_amount - sale.paid_amount
if sale.paid_amount else sale.total_amount),
'currency_digits': sale.currency_digits,
'party': sale.party.id,
}
def transition_pay_(self):
pool = Pool()
Sale = pool.get('sale.sale')
StatementLine = pool.get('account.statement.line')
active_id = Transaction().context.get('active_id', False)
sale = Sale(active_id)
form = self.start
if form.amount_payable > 0:
if not sale.number:
Sale.set_number([sale])
if not sale.party.account_receivable:
raise PartyMissingAccount(
gettext('sale_shop.msg_party_without_account_receivable', s=sale.party.name))
account = sale.party.account_receivable.id
if form.amount_payable:
amount = form.amount_payable
if sale.total_amount < 0:
amount = amount * -1
st_line = StatementLine(
statement=form.statement.id,
date=date.today(),
amount=amount,
party=sale.party.id,
account=account,
description=self.start.voucher,
sale=active_id,
number=sale.number,
)
st_line.save()
st_line.create_move()
if sale.total_amount != sale.paid_amount:
return 'start'
sale.save()
# Overload this method in sale pos
# if self.start.do_invoice:
# # for inv in sale.invoices:
# # if inv.state == 'posted':
# # inv.write([inv], {'state': 'draft'})
# Sale.workflow_to_end([sale])
return 'end'