Files
tryton/sale.py
T
2018-12-29 14:20:29 +01:00

191 lines
6.9 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 datetime
from trytond.i18n import gettext
from trytond.pool import PoolMeta, Pool
from trytond.model import ModelView, Workflow
from trytond.transaction import Transaction
from trytond.tools import grouped_slice
from .exceptions import StockQuantityError, StockQuantityWarning
__all__ = ['Sale']
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
@classmethod
@ModelView.button
@Workflow.transition('quotation')
def quote(cls, sales):
super(Sale, cls).quote(sales)
cls._check_stock_quantity(sales)
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, sales):
super(Sale, cls).confirm(sales)
cls._check_stock_quantity(sales)
@classmethod
def _check_stock_quantity(cls, sales):
for sale in sales:
sale.check_stock_quantity()
@classmethod
def _stock_quantity_states(cls):
return ['quotation', 'confirmed']
@classmethod
def _stock_quantity_next_supply_date(cls, product):
pool = Pool()
try:
PurchaseRequest = pool.get('purchase.request')
except KeyError:
PurchaseRequest = None
if (getattr(PurchaseRequest, 'get_supply_dates', None)
and product.purchasable):
return PurchaseRequest.get_supply_dates(product)[0]
else:
# TODO compute supply date for production
return datetime.date.max
def check_stock_quantity(self):
pool = Pool()
Product = pool.get('product.product')
Date = pool.get('ir.date')
Uom = pool.get('product.uom')
Group = pool.get('res.group')
User = pool.get('res.user')
ModelData = pool.get('ir.model.data')
Line = pool.get('sale.line')
Warning = pool.get('res.user.warning')
transaction = Transaction()
def in_group():
group = Group(ModelData.get_id('sale_stock_quantity',
'group_sale_stock_quantity'))
user_id = transaction.user
if user_id == 0:
user_id = transaction.context.get('user', user_id)
if user_id == 0:
return True
user = User(user_id)
return group in user.groups
def filter_line(line):
return (line.product
and line.product.type == 'goods'
and not line.product.consumable
and line.quantity > 0
# Use getattr as supply_on_sale comes from sale_supply module
and not getattr(line, 'supply_on_sale', False))
def get_delta(date):
'Compute quantity delta at the date'
if date in date2delta:
return date2delta[date]
with transaction.set_context(forecast=True,
stock_date_start=date,
stock_date_end=date):
pbl = Product.products_by_location(
[self.warehouse.id],
with_childs=True,
grouping_filter=(product_ids,))
delta = {}
for key, qty in pbl.items():
_, product_id = key
delta[product_id] = qty
date2delta[date] = delta
return delta
date2delta = {}
def raise_(line_id, message_values):
if not in_group():
raise StockQuantityError(
gettext('sale_stock_quantity.msg_sale_stock_quantity',
**message_values))
warning_name = 'stock_quantity_warning_%s' % line_id
if Warning.check(warning_name):
raise StockQuantityWarning(warning_name,
gettext('sale_stock_quantity.msg_sale_stock_quantity',
**message_values))
today = Date.today()
sale_date = self.sale_date or today
product_ids = {l.product.id for l in filter(filter_line, self.lines)}
product_ids = list(product_ids)
# The product must be available at least the day before
# for sale in the future
stock_date = sale_date
if sale_date > today:
stock_date -= datetime.timedelta(1)
with transaction.set_context(
locations=[self.warehouse.id],
stock_date_end=stock_date,
stock_assign=True):
products = Product.browse(product_ids)
quantities = {p: p.forecast_quantity for p in products}
# Remove quantities from other sales
for sub_product_ids in grouped_slice(product_ids):
lines = Line.search([
('sale.company', '=', self.company.id),
('sale.state', 'in', self._stock_quantity_states()),
('sale.id', '!=', self.id),
['OR',
('sale.sale_date', '<=', sale_date),
('sale.sale_date', '=', None),
],
('product', 'in', sub_product_ids),
('quantity', '>', 0),
])
for line in lines:
product = line.product
date = line.sale.sale_date or today
if date > today:
continue
quantity = Uom.compute_qty(line.unit, line.quantity,
product.default_uom, round=False)
quantities[product] -= quantity
for line in filter(filter_line, self.lines):
product = line.product
quantity = Uom.compute_qty(line.unit, line.quantity,
product.default_uom, round=False)
next_supply_date = self._stock_quantity_next_supply_date(product)
message_values = {
'line': line.rec_name,
'forecast_quantity': quantities[product],
'default_uom': product.default_uom.symbol,
'quantity': line.quantity,
'unit': line.unit.symbol,
}
if (quantities[product] < quantity
and sale_date < next_supply_date):
raise_(line.id, message_values)
# Update quantities if the same product is many times in lines
quantities[product] -= quantity
# Check other dates until next supply date
if next_supply_date != datetime.date.max:
forecast_quantity = quantities[product]
date = sale_date + datetime.timedelta(1)
while date < next_supply_date:
delta = get_delta(date)
forecast_quantity += delta.get(product.id, 0)
if forecast_quantity < 0:
message_values['forecast_quantity'] = forecast_quantity
raise_(line.id, message_values)
date += datetime.timedelta(1)