Active Record
This commit is contained in:
+18
@@ -1,6 +1,24 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
|
||||
from trytond.pool import Pool
|
||||
from .order_point import *
|
||||
from .product import *
|
||||
from .purchase_request import *
|
||||
from .shipment import *
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
OrderPoint,
|
||||
Product,
|
||||
PurchaseRequest,
|
||||
CreatePurchaseRequestStart,
|
||||
CreatePurchaseAskTerm,
|
||||
CreatePurchaseAskParty,
|
||||
ShipmentInternal,
|
||||
module='stock_supply', type_='model')
|
||||
Pool.register(
|
||||
CreatePurchaseRequest,
|
||||
CreatePurchase,
|
||||
module='stock_supply', type_='wizard')
|
||||
|
||||
+59
-74
@@ -3,19 +3,19 @@
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.pyson import If, Equal, Eval, Not, In, Get
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond.backend import TableHandler
|
||||
|
||||
__all__ = ['OrderPoint']
|
||||
|
||||
|
||||
class OrderPoint(ModelSQL, ModelView):
|
||||
"""
|
||||
Order Point: Provide a way to define a supply policy for each
|
||||
Order Point
|
||||
Provide a way to define a supply policy for each
|
||||
product on each locations. Order points on warehouse are
|
||||
considered by the supply scheduler to generate purchase requests.
|
||||
"""
|
||||
_name = 'stock.order_point'
|
||||
_description = "Order Point"
|
||||
|
||||
__name__ = 'stock.order_point'
|
||||
product = fields.Many2One('product.product', 'Product', required=True,
|
||||
select=True,
|
||||
domain=[
|
||||
@@ -69,18 +69,19 @@ class OrderPoint(ModelSQL, ModelView):
|
||||
unit_digits = fields.Function(fields.Integer('Unit Digits'),
|
||||
'get_unit_digits')
|
||||
|
||||
def __init__(self):
|
||||
super(OrderPoint, self).__init__()
|
||||
self._constraints += [
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(OrderPoint, cls).__setup__()
|
||||
cls._constraints += [
|
||||
('check_concurrent_internal', 'concurrent_internal_op'),
|
||||
('check_uniqueness', 'unique_op'),
|
||||
]
|
||||
self._sql_constraints += [
|
||||
cls._sql_constraints += [
|
||||
('check_max_qty_greater_min_qty',
|
||||
'CHECK(max_quantity >= min_quantity)',
|
||||
'Maximal quantity must be bigger than Minimal quantity'),
|
||||
]
|
||||
self._error_messages.update({
|
||||
cls._error_messages.update({
|
||||
'unique_op': 'Only one order point is allowed '\
|
||||
'for each product-location pair.',
|
||||
'concurrent_internal_op': 'You can not define ' \
|
||||
@@ -88,68 +89,63 @@ class OrderPoint(ModelSQL, ModelView):
|
||||
'with opposite locations.',
|
||||
})
|
||||
|
||||
def init(self, module_name):
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().cursor
|
||||
# Migration from 2.2
|
||||
table = TableHandler(cursor, self, module_name)
|
||||
table = TableHandler(cursor, cls, module_name)
|
||||
table.drop_constraint('check_min_max_quantity')
|
||||
|
||||
super(OrderPoint, self).init(module_name)
|
||||
super(OrderPoint, cls).__register__(module_name)
|
||||
|
||||
def default_type(self):
|
||||
@staticmethod
|
||||
def default_type():
|
||||
return "purchase"
|
||||
|
||||
def on_change_product(self, vals):
|
||||
product_obj = Pool().get('product.product')
|
||||
def on_change_product(self):
|
||||
res = {
|
||||
'unit': None,
|
||||
'unit.rec_name': '',
|
||||
'unit_digits': 2,
|
||||
}
|
||||
if vals.get('product'):
|
||||
product = product_obj.browse(vals['product'])
|
||||
res['unit'] = product.default_uom.id
|
||||
res['unit.rec_name'] = product.default_uom.rec_name
|
||||
res['unit_digits'] = product.default_uom.digits
|
||||
}
|
||||
if self.product:
|
||||
res['unit'] = self.product.default_uom.id
|
||||
res['unit.rec_name'] = self.product.default_uom.rec_name
|
||||
res['unit_digits'] = self.product.default_uom.digits
|
||||
return res
|
||||
|
||||
def get_unit(self, ids, name):
|
||||
res = {}
|
||||
for order in self.browse(ids):
|
||||
res[order.id] = order.product.default_uom.id
|
||||
return res
|
||||
def get_unit(self, name):
|
||||
return self.product.default_uom.id
|
||||
|
||||
def get_unit_digits(self, ids, name):
|
||||
res = {}
|
||||
for order in self.browse(ids):
|
||||
res[order.id] = order.product.default_uom.digits
|
||||
return res
|
||||
def get_unit_digits(self, name):
|
||||
return self.product.default_uom.digits
|
||||
|
||||
def check_concurrent_internal(self, ids):
|
||||
@classmethod
|
||||
def check_concurrent_internal(cls, orders):
|
||||
"""
|
||||
Ensure that there is no 'concurrent' internal order
|
||||
points. I.E. no two order point with opposite location for the
|
||||
same product and same company.
|
||||
"""
|
||||
internal_ids = self.search([
|
||||
('id', 'in', ids),
|
||||
('type', '=', 'internal'),
|
||||
])
|
||||
if not internal_ids:
|
||||
internals = cls.search([
|
||||
('id', 'in', [o.id for o in orders]),
|
||||
('type', '=', 'internal'),
|
||||
])
|
||||
if not internals:
|
||||
return True
|
||||
|
||||
query = ['OR']
|
||||
for op in self.browse(internal_ids):
|
||||
for op in internals:
|
||||
arg = ['AND',
|
||||
('provisioning_location', '=', op.storage_location.id),
|
||||
('storage_location', '=', op.provisioning_location.id),
|
||||
('company', '=', op.company.id),
|
||||
('type', '=', 'internal')]
|
||||
query.append(arg)
|
||||
ids = self.search(query)
|
||||
return not bool(ids)
|
||||
return not bool(cls.search(query))
|
||||
|
||||
def _type2field(self, type=None):
|
||||
@staticmethod
|
||||
def _type2field(type=None):
|
||||
t2f = {
|
||||
'purchase': 'warehouse_location',
|
||||
'internal': 'storage_location',
|
||||
@@ -159,36 +155,30 @@ class OrderPoint(ModelSQL, ModelView):
|
||||
else:
|
||||
return t2f[type]
|
||||
|
||||
def check_uniqueness(self, ids):
|
||||
@classmethod
|
||||
def check_uniqueness(cls, orders):
|
||||
"""
|
||||
Ensure uniqueness of order points. I.E that there is no several
|
||||
order point for the same location, the same product and the
|
||||
same company.
|
||||
"""
|
||||
query = ['OR']
|
||||
for op in self.browse(ids):
|
||||
field = self._type2field(op.type)
|
||||
for op in orders:
|
||||
field = cls._type2field(op.type)
|
||||
arg = ['AND',
|
||||
('product', '=', op.product.id),
|
||||
(field, '=', op[field].id),
|
||||
(field, '=', getattr(op, field).id),
|
||||
('id', '!=', op.id),
|
||||
('company', '=', op.company.id),
|
||||
]
|
||||
query.append(arg)
|
||||
ids = self.search(query)
|
||||
return not bool(ids)
|
||||
return not bool(cls.search(query))
|
||||
|
||||
def get_rec_name(self, ids, name):
|
||||
if not ids:
|
||||
return {}
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
res = {}
|
||||
for op in self.browse(ids):
|
||||
res[op.id] = "%s@%s" % (op.product.name, op.location.name)
|
||||
return res
|
||||
def get_rec_name(self, name):
|
||||
return "%s@%s" % (self.product.name, self.location.name)
|
||||
|
||||
def search_rec_name(self, name, clause):
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
res = []
|
||||
names = clause[2].split('@', 1)
|
||||
res.append(('product.template.name', clause[1], names[0]))
|
||||
@@ -196,27 +186,22 @@ class OrderPoint(ModelSQL, ModelView):
|
||||
res.append(('location', clause[1], names[1]))
|
||||
return res
|
||||
|
||||
def get_location(self, ids, name):
|
||||
res = {}
|
||||
for op in self.browse(ids):
|
||||
if op.type == 'purchase':
|
||||
res[op.id] = op.warehouse_location.id
|
||||
elif op.type == 'internal':
|
||||
res[op.id] = op.storage_location.id
|
||||
else:
|
||||
res[op.id] = None
|
||||
return res
|
||||
def get_location(self, name):
|
||||
if self.type == 'purchase':
|
||||
return self.warehouse_location.id
|
||||
elif self.type == 'internal':
|
||||
return self.storage_location.id
|
||||
|
||||
def search_location(self, name, domain=None):
|
||||
@classmethod
|
||||
def search_location(cls, name, domain=None):
|
||||
ids = []
|
||||
for type, field in self._type2field().iteritems():
|
||||
for type, field in cls._type2field().iteritems():
|
||||
args = [('type', '=', type)]
|
||||
for _, operator, operand in domain:
|
||||
args.append((field, operator, operand))
|
||||
ids.extend(self.search(args))
|
||||
ids.extend([o.id for o in cls.search(args)])
|
||||
return [('id', 'in', ids)]
|
||||
|
||||
def default_company(self):
|
||||
@staticmethod
|
||||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
OrderPoint()
|
||||
|
||||
+7
-6
@@ -1,12 +1,13 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.model import fields
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
__all__ = ['Product']
|
||||
__metaclass__ = PoolMeta
|
||||
|
||||
|
||||
class Product(ModelSQL, ModelView):
|
||||
_name = "product.product"
|
||||
|
||||
class Product:
|
||||
__name__ = "product.product"
|
||||
order_points = fields.One2Many(
|
||||
'stock.order_point', 'product', 'Order Points')
|
||||
|
||||
Product()
|
||||
|
||||
+215
-264
@@ -12,12 +12,14 @@ from trytond.pyson import If, In, Eval, Get
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
|
||||
__all__ = ['PurchaseRequest',
|
||||
'CreatePurchaseRequestStart', 'CreatePurchaseRequest',
|
||||
'CreatePurchaseAskTerm', 'CreatePurchaseAskParty', 'CreatePurchase']
|
||||
|
||||
|
||||
class PurchaseRequest(ModelSQL, ModelView):
|
||||
'Purchase Request'
|
||||
_name = 'purchase.request'
|
||||
_description = __doc__
|
||||
|
||||
__name__ = 'purchase.request'
|
||||
product = fields.Many2One('product.product', 'Product', required=True,
|
||||
select=True, readonly=True, domain=[('purchasable', '=', True)])
|
||||
party = fields.Many2One('party.party', 'Party', select=True)
|
||||
@@ -56,39 +58,37 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
('cancel', 'Cancel'),
|
||||
], 'State'), 'get_state')
|
||||
|
||||
def __init__(self):
|
||||
super(PurchaseRequest, self).__init__()
|
||||
self._order[0] = ('id', 'DESC')
|
||||
self._error_messages.update({
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(PurchaseRequest, cls).__setup__()
|
||||
cls._order[0] = ('id', 'DESC')
|
||||
cls._error_messages.update({
|
||||
'create_request': 'Purchase requests are only created ' \
|
||||
'by the system.',
|
||||
})
|
||||
self._sql_constraints += [
|
||||
cls._sql_constraints += [
|
||||
('check_purchase_request_quantity', 'CHECK(quantity > 0)',
|
||||
'The requested quantity must be greater than 0'),
|
||||
]
|
||||
|
||||
def init(self, module_name):
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().cursor
|
||||
super(PurchaseRequest, self).init(module_name)
|
||||
super(PurchaseRequest, cls).__register__(module_name)
|
||||
|
||||
# Migration from 2.0: empty order point origin is -1 instead of 0
|
||||
cursor.execute('UPDATE "%s" '
|
||||
'SET origin = %%s WHERE origin = %%s' % self._table,
|
||||
'SET origin = %%s WHERE origin = %%s' % cls._table,
|
||||
('stock.order_point,-1', 'stock.order_point,0'))
|
||||
|
||||
def get_rec_name(self, ids, name):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
res = {}
|
||||
for pr in self.browse(ids):
|
||||
if pr.warehouse:
|
||||
res[pr.id] = "%s@%s" % (pr.product.name, pr.warehouse.name)
|
||||
else:
|
||||
res[pr.id] = pr.product.name
|
||||
return res
|
||||
def get_rec_name(self, name):
|
||||
if self.warehouse:
|
||||
return "%s@%s" % (self.product.name, self.warehouse.name)
|
||||
else:
|
||||
return self.product.name
|
||||
|
||||
def search_rec_name(self, name, clause):
|
||||
@classmethod
|
||||
def search_rec_name(cls, name, clause):
|
||||
res = []
|
||||
names = clause[2].split('@', 1)
|
||||
res.append(('product.template.name', clause[1], names[0]))
|
||||
@@ -96,86 +96,79 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
res.append(('warehouse', clause[1], names[1]))
|
||||
return res
|
||||
|
||||
def default_company(self):
|
||||
@staticmethod
|
||||
def default_company():
|
||||
return Transaction().context.get('company')
|
||||
|
||||
def get_purchase(self, ids, name):
|
||||
res = {}
|
||||
def get_purchase(self, name):
|
||||
if self.purchase_line:
|
||||
return self.purchase_line.purchase.id
|
||||
|
||||
requests = self.browse(ids)
|
||||
for request in requests:
|
||||
if request.purchase_line:
|
||||
res[request.id] = request.purchase_line.purchase.id
|
||||
def get_state(self, name):
|
||||
if self.purchase_line:
|
||||
if self.purchase_line.purchase.state == 'cancel':
|
||||
return 'cancel'
|
||||
elif self.purchase_line.purchase.state == 'done':
|
||||
return 'done'
|
||||
else:
|
||||
res[request.id] = None
|
||||
return res
|
||||
return 'purchased'
|
||||
return 'draft'
|
||||
|
||||
def get_state(self, ids, name):
|
||||
res = {}.fromkeys(ids, 'draft')
|
||||
for request in self.browse(ids):
|
||||
if request.purchase_line:
|
||||
if request.purchase_line.purchase.state == 'cancel':
|
||||
res[request.id] = 'cancel'
|
||||
elif request.purchase_line.purchase.state == 'done':
|
||||
res[request.id] = 'done'
|
||||
else:
|
||||
res[request.id] = 'purchased'
|
||||
return res
|
||||
def get_warehouse_required(self, name):
|
||||
return self.product.type in ('goods', 'assets')
|
||||
|
||||
def get_warehouse_required(self, ids, name):
|
||||
requireds = {}
|
||||
for request in self.browse(ids):
|
||||
requireds[request.id] = request.product.type in ('goods', 'assets')
|
||||
return requireds
|
||||
|
||||
def origin_get(self):
|
||||
model_obj = Pool().get('ir.model')
|
||||
@staticmethod
|
||||
def origin_get():
|
||||
Model = Pool().get('ir.model')
|
||||
res = []
|
||||
model_ids = model_obj.search([
|
||||
('model', '=', 'stock.order_point'),
|
||||
])
|
||||
for model in model_obj.browse(model_ids):
|
||||
models = Model.search([
|
||||
('model', '=', 'stock.order_point'),
|
||||
])
|
||||
for model in models:
|
||||
res.append([model.model, model.name])
|
||||
return res
|
||||
|
||||
def generate_requests(self):
|
||||
@classmethod
|
||||
def generate_requests(cls):
|
||||
"""
|
||||
For each product compute the purchase request that must be
|
||||
create today to meet product outputs.
|
||||
"""
|
||||
pool = Pool()
|
||||
order_point_obj = pool.get('stock.order_point')
|
||||
product_obj = pool.get('product.product')
|
||||
location_obj = pool.get('stock.location')
|
||||
user_obj = pool.get('res.user')
|
||||
company = user_obj.browse(Transaction().user).company
|
||||
OrderPoint = pool.get('stock.order_point')
|
||||
Product = pool.get('product.product')
|
||||
Location = pool.get('stock.location')
|
||||
User = pool.get('res.user')
|
||||
company = User(Transaction().user).company
|
||||
|
||||
# fetch warehouses:
|
||||
warehouse_ids = location_obj.search([
|
||||
warehouses = Location.search([
|
||||
('type', '=', 'warehouse'),
|
||||
])
|
||||
warehouse_ids = [w.id for w in warehouses]
|
||||
# fetch order points
|
||||
order_point_ids = order_point_obj.search([
|
||||
order_points = OrderPoint.search([
|
||||
('type', '=', 'purchase'),
|
||||
])
|
||||
# index them by product
|
||||
product2ops = {}
|
||||
for order_point in order_point_obj.browse(order_point_ids):
|
||||
for order_point in order_points:
|
||||
product2ops[
|
||||
(order_point.warehouse_location.id, order_point.product.id)
|
||||
] = order_point
|
||||
|
||||
# fetch goods and assets
|
||||
# ordered by ids to speedup reduce_ids in products_by_location
|
||||
product_ids = product_obj.search([
|
||||
products = Product.search([
|
||||
('type', 'in', ['goods', 'assets']),
|
||||
('consumable', '=', False),
|
||||
('purchasable', '=', True),
|
||||
], order=[('id', 'ASC')])
|
||||
product_ids = [p.id for p in products]
|
||||
#aggregate product by minimum supply date
|
||||
date2products = {}
|
||||
for product in product_obj.browse(product_ids):
|
||||
min_date, max_date = self.get_supply_dates(product)
|
||||
for product in products:
|
||||
min_date, max_date = cls.get_supply_dates(product)
|
||||
date2products.setdefault((min_date, max_date), []).append(product)
|
||||
|
||||
# compute requests
|
||||
@@ -187,13 +180,13 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
product_ids = [p.id for p in products[i:i + cursor.IN_MAX]]
|
||||
with Transaction().set_context(forecast=True,
|
||||
stock_date_end=min_date or datetime.date.max):
|
||||
pbl = product_obj.products_by_location(warehouse_ids,
|
||||
pbl = Product.products_by_location(warehouse_ids,
|
||||
product_ids, with_childs=True, skip_zero=False)
|
||||
for warehouse_id in warehouse_ids:
|
||||
min_date_qties = dict((x, pbl.pop((warehouse_id, x)))
|
||||
for x in product_ids)
|
||||
# Search for shortage between min-max
|
||||
shortages = self.get_shortage(warehouse_id, product_ids,
|
||||
shortages = cls.get_shortage(warehouse_id, product_ids,
|
||||
min_date, max_date, min_date_qties=min_date_qties,
|
||||
order_points=product2ops)
|
||||
|
||||
@@ -204,53 +197,44 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
order_point = product2ops.get(
|
||||
(warehouse_id, product.id))
|
||||
# generate request values
|
||||
request_val = self.compute_request(product,
|
||||
request = cls.compute_request(product,
|
||||
warehouse_id, shortage_date, product_quantity,
|
||||
company, order_point)
|
||||
new_requests.append(request_val)
|
||||
new_requests.append(request)
|
||||
|
||||
new_requests = self.compare_requests(new_requests)
|
||||
new_requests = cls.compare_requests(new_requests)
|
||||
|
||||
self.create_requests(new_requests)
|
||||
return {}
|
||||
|
||||
def create_requests(self, new_requests):
|
||||
request_obj = Pool().get('purchase.request')
|
||||
cls.create_requests(new_requests)
|
||||
|
||||
@classmethod
|
||||
def create_requests(cls, new_requests):
|
||||
for new_req in new_requests:
|
||||
if new_req['supply_date'] == datetime.date.max:
|
||||
new_req['supply_date'] = None
|
||||
if new_req['quantity'] > 0.0:
|
||||
new_req.update({
|
||||
'product': new_req['product'].id,
|
||||
'party': new_req['party'] and new_req['party'].id,
|
||||
'uom': new_req['uom'].id,
|
||||
'computed_uom': new_req['computed_uom'].id,
|
||||
'company': new_req['company'].id
|
||||
})
|
||||
request_obj.create(new_req)
|
||||
if new_req.supply_date == datetime.date.max:
|
||||
new_req.supply_date = None
|
||||
if new_req.quantity > 0.0:
|
||||
new_req.save()
|
||||
|
||||
def compare_requests(self, new_requests):
|
||||
@classmethod
|
||||
def compare_requests(cls, new_requests):
|
||||
"""
|
||||
Compare new_requests with already existing request to avoid
|
||||
to re-create existing requests.
|
||||
"""
|
||||
# delete purchase request without a purchase line
|
||||
pool = Pool()
|
||||
uom_obj = pool.get('product.uom')
|
||||
request_obj = pool.get('purchase.request')
|
||||
req_ids = request_obj.search([
|
||||
Uom = pool.get('product.uom')
|
||||
Request = pool.get('purchase.request')
|
||||
reqs = Request.search([
|
||||
('purchase_line', '=', None),
|
||||
('origin', 'like', 'stock.order_point,%'),
|
||||
])
|
||||
request_obj.delete(req_ids)
|
||||
Request.delete(reqs)
|
||||
|
||||
req_ids = request_obj.search([
|
||||
requests = Request.search([
|
||||
('purchase_line.moves', '=', None),
|
||||
('purchase_line.purchase.state', '!=', 'cancel'),
|
||||
('origin', 'like', 'stock.order_point,%'),
|
||||
])
|
||||
requests = request_obj.browse(req_ids)
|
||||
# Fetch data from existing requests
|
||||
existing_req = {}
|
||||
for request in requests:
|
||||
@@ -260,7 +244,7 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
request.warehouse.id != pline.purchase.warehouse.id:
|
||||
continue
|
||||
# Take smallest amount between request and purchase line
|
||||
req_qty = uom_obj.compute_qty(request.uom, request.quantity,
|
||||
req_qty = Uom.compute_qty(request.uom, request.quantity,
|
||||
pline.unit)
|
||||
if req_qty < pline.quantity:
|
||||
quantity = request.quantity
|
||||
@@ -282,46 +266,40 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
i.sort(lambda r, s: cmp(r['supply_date'], s['supply_date']))
|
||||
|
||||
# Update new requests to take existing requests into account
|
||||
new_requests.sort(key=operator.itemgetter('supply_date'))
|
||||
new_requests.sort(key=operator.attrgetter('supply_date'))
|
||||
for new_req in new_requests:
|
||||
for old_req in existing_req.get((new_req['product'].id,
|
||||
new_req['warehouse']), []):
|
||||
if old_req['supply_date'] <= new_req['supply_date']:
|
||||
quantity = uom_obj.compute_qty(old_req['uom'],
|
||||
old_req['quantity'], new_req['uom'])
|
||||
new_req['quantity'] = max(0.0,
|
||||
new_req['quantity'] - quantity)
|
||||
new_req['computed_quantity'] = new_req['quantity']
|
||||
old_req['quantity'] = uom_obj.compute_qty(new_req['uom'],
|
||||
max(0.0, quantity - new_req['quantity']),
|
||||
old_req['uom'])
|
||||
for old_req in existing_req.get(
|
||||
(new_req.product.id, new_req.warehouse.id), []):
|
||||
if old_req['supply_date'] <= new_req.supply_date:
|
||||
quantity = Uom.compute_qty(old_req['uom'],
|
||||
old_req['quantity'], new_req.uom)
|
||||
new_req.quantity = max(0.0, new_req.quantity - quantity)
|
||||
new_req.computed_quantity = new_req.quantity
|
||||
old_req['quantity'] = Uom.compute_qty(new_req.uom,
|
||||
max(0.0, quantity - new_req.quantity), old_req['uom'])
|
||||
else:
|
||||
break
|
||||
|
||||
return new_requests
|
||||
|
||||
def get_supply_dates(self, product):
|
||||
@classmethod
|
||||
def get_supply_dates(cls, product):
|
||||
"""
|
||||
Return the minimal interval of earliest supply dates for a product.
|
||||
|
||||
:param product: a BrowseRecord of the Product
|
||||
:return: a tuple with the two dates
|
||||
"""
|
||||
product_supplier_obj = Pool().get('purchase.product_supplier')
|
||||
date_obj = Pool().get('ir.date')
|
||||
Date = Pool().get('ir.date')
|
||||
|
||||
min_date = None
|
||||
max_date = None
|
||||
today = date_obj.today()
|
||||
today = Date.today()
|
||||
|
||||
for product_supplier in product.product_suppliers:
|
||||
supply_date = product_supplier_obj.compute_supply_date(
|
||||
product_supplier, date=today)
|
||||
supply_date = product_supplier.compute_supply_date(date=today)
|
||||
# TODO next_day is by default today + 1 but should depends
|
||||
# on the CRON activity
|
||||
next_day = today + datetime.timedelta(1)
|
||||
next_supply_date = product_supplier_obj.compute_supply_date(
|
||||
product_supplier, date=next_day)
|
||||
next_supply_date = product_supplier.compute_supply_date(
|
||||
date=next_day)
|
||||
if (not min_date) or supply_date < min_date:
|
||||
min_date = supply_date
|
||||
if (not max_date):
|
||||
@@ -337,20 +315,18 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
|
||||
return (min_date, max_date)
|
||||
|
||||
def find_best_supplier(self, product, date):
|
||||
@classmethod
|
||||
def find_best_supplier(cls, product, date):
|
||||
'''
|
||||
Return the best supplier and purchase_date for the product.
|
||||
'''
|
||||
pool = Pool()
|
||||
date_obj = pool.get('ir.date')
|
||||
product_supplier_obj = pool.get('purchase.product_supplier')
|
||||
Date = Pool().get('ir.date')
|
||||
|
||||
supplier = None
|
||||
timedelta = datetime.timedelta.max
|
||||
today = date_obj.today()
|
||||
today = Date.today()
|
||||
for product_supplier in product.product_suppliers:
|
||||
supply_date = product_supplier_obj.compute_supply_date(
|
||||
product_supplier, date=today)
|
||||
supply_date = product_supplier.compute_supply_date(date=today)
|
||||
sup_timedelta = date - supply_date
|
||||
if not supplier:
|
||||
supplier = product_supplier.party
|
||||
@@ -364,13 +340,13 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
timedelta = sup_timedelta
|
||||
|
||||
if supplier:
|
||||
purchase_date = product_supplier_obj.compute_purchase_date(
|
||||
product_supplier, date)
|
||||
purchase_date = product_supplier.compute_purchase_date(date)
|
||||
else:
|
||||
purchase_date = today
|
||||
return supplier, purchase_date
|
||||
|
||||
def compute_request(self, product, location_id, shortage_date,
|
||||
@classmethod
|
||||
def compute_request(cls, product, location_id, shortage_date,
|
||||
product_quantity, company, order_point=None):
|
||||
"""
|
||||
Return the value of the purchase request which will answer to
|
||||
@@ -379,13 +355,14 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
supplier.
|
||||
"""
|
||||
pool = Pool()
|
||||
uom_obj = pool.get('product.uom')
|
||||
Uom = pool.get('product.uom')
|
||||
Request = pool.get('purchase.request')
|
||||
|
||||
supplier, purchase_date = self.find_best_supplier(product,
|
||||
supplier, purchase_date = cls.find_best_supplier(product,
|
||||
shortage_date)
|
||||
|
||||
max_quantity = order_point and order_point.max_quantity or 0.0
|
||||
quantity = uom_obj.compute_qty(product.default_uom,
|
||||
quantity = Uom.compute_qty(product.default_uom,
|
||||
max_quantity - product_quantity,
|
||||
product.purchase_uom or product.default_uom)
|
||||
|
||||
@@ -393,21 +370,22 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
origin = 'stock.order_point,%s' % order_point.id
|
||||
else:
|
||||
origin = 'stock.order_point,-1'
|
||||
return {'product': product,
|
||||
'party': supplier and supplier or None,
|
||||
'quantity': quantity,
|
||||
'uom': product.purchase_uom or product.default_uom,
|
||||
'computed_quantity': quantity,
|
||||
'computed_uom': product.purchase_uom or product.default_uom,
|
||||
'purchase_date': purchase_date,
|
||||
'supply_date': shortage_date,
|
||||
'stock_level': product_quantity,
|
||||
'company': company,
|
||||
'warehouse': location_id,
|
||||
'origin': origin,
|
||||
}
|
||||
return Request(product=product,
|
||||
party=supplier and supplier or None,
|
||||
quantity=quantity,
|
||||
uom=product.purchase_uom or product.default_uom,
|
||||
computed_quantity=quantity,
|
||||
computed_uom=product.purchase_uom or product.default_uom,
|
||||
purchase_date=purchase_date,
|
||||
supply_date=shortage_date,
|
||||
stock_level=product_quantity,
|
||||
company=company,
|
||||
warehouse=location_id,
|
||||
origin=origin,
|
||||
)
|
||||
|
||||
def get_shortage(self, location_id, product_ids, min_date, max_date,
|
||||
@classmethod
|
||||
def get_shortage(cls, location_id, product_ids, min_date, max_date,
|
||||
min_date_qties, order_points):
|
||||
"""
|
||||
Return for each product the first date between min_date and max_date
|
||||
@@ -420,7 +398,7 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
min_date_qty is the quantities for each products at the min_date.
|
||||
order_points is a dictionary that links products to order point.
|
||||
"""
|
||||
product_obj = Pool().get('product.product')
|
||||
Product = Pool().get('product.product')
|
||||
|
||||
res_dates = {}
|
||||
res_qties = {}
|
||||
@@ -449,7 +427,7 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
|
||||
with Transaction().set_context(stock_date_start=current_date,
|
||||
stock_date_end=current_date):
|
||||
pbl = product_obj.products_by_location([location_id],
|
||||
pbl = Product.products_by_location([location_id],
|
||||
product_ids, with_childs=True, skip_zero=False)
|
||||
for key, qty in pbl.iteritems():
|
||||
_, product_id = key
|
||||
@@ -461,27 +439,22 @@ class PurchaseRequest(ModelSQL, ModelView):
|
||||
return dict((x, (res_dates.get(x), res_qties.get(x)))
|
||||
for x in product_ids)
|
||||
|
||||
def create(self, vals):
|
||||
@classmethod
|
||||
def create(cls, vals):
|
||||
for field_name in ('product', 'quantity', 'uom', 'company'):
|
||||
if not vals.get(field_name):
|
||||
self.raise_user_error('create_request')
|
||||
return super(PurchaseRequest, self).create(vals)
|
||||
|
||||
PurchaseRequest()
|
||||
cls.raise_user_error('create_request')
|
||||
return super(PurchaseRequest, cls).create(vals)
|
||||
|
||||
|
||||
class CreatePurchaseRequestStart(ModelView):
|
||||
'Create Purchase Request'
|
||||
_name = 'purchase.request.create.start'
|
||||
_description = __doc__
|
||||
|
||||
CreatePurchaseRequestStart()
|
||||
__name__ = 'purchase.request.create.start'
|
||||
|
||||
|
||||
class CreatePurchaseRequest(Wizard):
|
||||
'Create Purchase Request'
|
||||
_name = 'purchase.request.create'
|
||||
|
||||
__name__ = 'purchase.request.create'
|
||||
start = StateView('purchase.request.create.start',
|
||||
'stock_supply.purchase_request_create_start_view_form', [
|
||||
Button('Cancel', 'end', 'tryton-cancel'),
|
||||
@@ -489,44 +462,35 @@ class CreatePurchaseRequest(Wizard):
|
||||
])
|
||||
create_ = StateAction('stock_supply.act_purchase_request_form_draft')
|
||||
|
||||
def do_create_(self, session, action):
|
||||
purchase_request_obj = Pool().get('purchase.request')
|
||||
purchase_request_obj.generate_requests()
|
||||
def do_create_(self, action):
|
||||
PurchaseRequest = Pool().get('purchase.request')
|
||||
PurchaseRequest.generate_requests()
|
||||
return action, {}
|
||||
|
||||
def transition_create_(self, session):
|
||||
def transition_create_(self):
|
||||
return 'end'
|
||||
|
||||
CreatePurchaseRequest()
|
||||
|
||||
|
||||
class CreatePurchaseAskTerm(ModelView):
|
||||
'Create Purchase Ask Term'
|
||||
_name = 'purchase.request.create_purchase.ask_term'
|
||||
_description = __doc__
|
||||
__name__ = 'purchase.request.create_purchase.ask_term'
|
||||
party = fields.Many2One('party.party', 'Supplier', readonly=True)
|
||||
company = fields.Many2One('company.company', 'Company', readonly=True)
|
||||
payment_term = fields.Many2One(
|
||||
'account.invoice.payment_term', 'Payment Term', required=True)
|
||||
|
||||
CreatePurchaseAskTerm()
|
||||
|
||||
|
||||
class CreatePurchaseAskParty(ModelView):
|
||||
'Create Purchase Ask Party'
|
||||
_name = 'purchase.request.create_purchase.ask_party'
|
||||
_description = __doc__
|
||||
__name__ = 'purchase.request.create_purchase.ask_party'
|
||||
product = fields.Many2One('product.product', 'Product', readonly=True)
|
||||
company = fields.Many2One('company.company', 'Company', readonly=True)
|
||||
party = fields.Many2One('party.party', 'Supplier', required=True)
|
||||
|
||||
CreatePurchaseAskParty()
|
||||
|
||||
|
||||
class CreatePurchase(Wizard):
|
||||
'Create Purchase'
|
||||
_name = 'purchase.request.create_purchase'
|
||||
|
||||
__name__ = 'purchase.request.create_purchase'
|
||||
start = StateTransition()
|
||||
ask_party = StateView('purchase.request.create_purchase.ask_party',
|
||||
'stock_supply.purchase_request_create_purchase_ask_party', [
|
||||
@@ -539,17 +503,18 @@ class CreatePurchase(Wizard):
|
||||
Button('Continue', 'start', 'tryton-go-next', default=True),
|
||||
])
|
||||
|
||||
def __init__(self):
|
||||
super(CreatePurchase, self).__init__()
|
||||
self._error_messages.update({
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(CreatePurchase, cls).__setup__()
|
||||
cls._error_messages.update({
|
||||
'missing_price': 'Purchase price is missing for product: %s ' \
|
||||
'(id: %s)!',
|
||||
'please_update': 'This price is necessary for creating purchase.'
|
||||
})
|
||||
|
||||
def default_ask_party(self, session, fields):
|
||||
request_obj = Pool().get('purchase.request')
|
||||
requests = request_obj.browse(Transaction().context['active_ids'])
|
||||
def default_ask_party(self, fields):
|
||||
Request = Pool().get('purchase.request')
|
||||
requests = Request.browse(Transaction().context['active_ids'])
|
||||
for request in requests:
|
||||
if request.purchase_line:
|
||||
continue
|
||||
@@ -563,9 +528,9 @@ class CreatePurchase(Wizard):
|
||||
'company': request.company.id,
|
||||
}
|
||||
|
||||
def default_ask_term(self, session, fields):
|
||||
request_obj = Pool().get('purchase.request')
|
||||
requests = request_obj.browse(Transaction().context['active_ids'])
|
||||
def default_ask_term(self, fields):
|
||||
Request = Pool().get('purchase.request')
|
||||
requests = Request.browse(Transaction().context['active_ids'])
|
||||
for request in requests:
|
||||
if (not request.party) or request.purchase_line:
|
||||
continue
|
||||
@@ -579,75 +544,68 @@ class CreatePurchase(Wizard):
|
||||
'company': request.company.id,
|
||||
}
|
||||
|
||||
def _group_purchase_key(self, requests, request):
|
||||
@staticmethod
|
||||
def _group_purchase_key(requests, request):
|
||||
'''
|
||||
The key to group lines by purchases
|
||||
|
||||
:param requests: a list of requests
|
||||
:param request: the request
|
||||
|
||||
:return: a list of key-value as tuples of the purchase
|
||||
A list of key-value as tuples of the purchase
|
||||
'''
|
||||
party_obj = Pool().get('party.party')
|
||||
|
||||
return (
|
||||
('company', request.company.id),
|
||||
('party', request.party.id),
|
||||
('payment_term', request.party.supplier_payment_term.id),
|
||||
('warehouse', request.warehouse.id),
|
||||
('company', request.company),
|
||||
('party', request.party),
|
||||
('payment_term', request.party.supplier_payment_term),
|
||||
('warehouse', request.warehouse),
|
||||
# XXX use function field
|
||||
('currency', request.company.currency.id),
|
||||
('invoice_address', party_obj.address_get(request.party.id,
|
||||
type='invoice')),
|
||||
('currency', request.company.currency),
|
||||
('invoice_address', request.party.address_get(type='invoice')),
|
||||
)
|
||||
|
||||
def transition_start(self, session):
|
||||
def transition_start(self):
|
||||
pool = Pool()
|
||||
request_obj = pool.get('purchase.request')
|
||||
party_obj = pool.get('party.party')
|
||||
purchase_obj = pool.get('purchase.purchase')
|
||||
line_obj = pool.get('purchase.line')
|
||||
date_obj = pool.get('ir.date')
|
||||
Request = pool.get('purchase.request')
|
||||
Party = pool.get('party.party')
|
||||
Purchase = pool.get('purchase.purchase')
|
||||
Date = pool.get('ir.date')
|
||||
|
||||
request_ids = Transaction().context['active_ids']
|
||||
|
||||
if (session.ask_party.product
|
||||
and session.ask_party.party
|
||||
and session.ask_party.company):
|
||||
req_ids = request_obj.search([
|
||||
if (getattr(self.ask_party, 'product', None)
|
||||
and getattr(self.ask_party, 'party', None)
|
||||
and getattr(self.ask_party, 'company', None)):
|
||||
reqs = Request.search([
|
||||
('id', 'in', request_ids),
|
||||
('party', '=', None),
|
||||
])
|
||||
if req_ids:
|
||||
request_obj.write(req_ids, {
|
||||
'party': session.ask_party.party.id,
|
||||
if reqs:
|
||||
Request.write(reqs, {
|
||||
'party': self.ask_party.party.id,
|
||||
})
|
||||
session.ask_party.product = None
|
||||
session.ask_party.party = None
|
||||
session.ask_party.company = None
|
||||
elif (session.ask_term.payment_term
|
||||
and session.ask_term.party
|
||||
and session.ask_term.company):
|
||||
self.ask_party.product = None
|
||||
self.ask_party.party = None
|
||||
self.ask_party.company = None
|
||||
elif (getattr(self.ask_term, 'payment_term', None)
|
||||
and getattr(self.ask_term, 'party', None)
|
||||
and getattr(self.ask_term, 'company', None)):
|
||||
with Transaction().set_context(
|
||||
company=session.ask_term.company.id):
|
||||
party_obj.write(session.ask_term.party.id, {
|
||||
company=self.ask_term.company.id):
|
||||
Party.write([self.ask_term.party], {
|
||||
'supplier_payment_term': \
|
||||
session.ask_term.payment_term.id,
|
||||
self.ask_term.payment_term.id,
|
||||
})
|
||||
session.ask_term.payment_term = None
|
||||
session.ask_term.party = None
|
||||
session.ask_term.company = None
|
||||
self.ask_term.payment_term = None
|
||||
self.ask_term.party = None
|
||||
self.ask_term.company = None
|
||||
|
||||
req_ids = request_obj.search([
|
||||
reqs = Request.search([
|
||||
('id', 'in', request_ids),
|
||||
('purchase_line', '=', None),
|
||||
('party', '=', None),
|
||||
])
|
||||
if req_ids:
|
||||
if reqs:
|
||||
return 'ask_party'
|
||||
|
||||
today = date_obj.today()
|
||||
requests = request_obj.browse(request_ids)
|
||||
today = Date.today()
|
||||
requests = Request.browse(request_ids)
|
||||
|
||||
requests = [r for r in requests if not r.purchase_line]
|
||||
if any(r for r in requests if not r.party.supplier_payment_term):
|
||||
@@ -667,68 +625,61 @@ class CreatePurchase(Wizard):
|
||||
purchase_date = today
|
||||
if purchase_date < today:
|
||||
purchase_date = today
|
||||
values = {
|
||||
'purchase_date': purchase_date,
|
||||
}
|
||||
values.update(dict(key))
|
||||
purchase_id = purchase_obj.create(values)
|
||||
purchase = Purchase(purchase_date=purchase_date)
|
||||
for f, v in key:
|
||||
setattr(purchase, f, v)
|
||||
purchase.save()
|
||||
for request in grouped_requests:
|
||||
values = self.compute_purchase_line(request)
|
||||
values['purchase'] = purchase_id
|
||||
line_id = line_obj.create(values)
|
||||
request_obj.write(request.id, {
|
||||
'purchase_line': line_id,
|
||||
line = self.compute_purchase_line(request)
|
||||
line.purchase = purchase
|
||||
line.save()
|
||||
Request.write([request], {
|
||||
'purchase_line': line.id,
|
||||
})
|
||||
return 'end'
|
||||
|
||||
def _get_tax_rule_pattern(self, request):
|
||||
@staticmethod
|
||||
def _get_tax_rule_pattern(request):
|
||||
'''
|
||||
Get tax rule pattern
|
||||
|
||||
:param request: the BrowseRecord of the purchase request
|
||||
:return: a dictionary to use as pattern for tax rule
|
||||
'''
|
||||
res = {}
|
||||
return res
|
||||
return {}
|
||||
|
||||
def compute_purchase_line(self, request):
|
||||
@classmethod
|
||||
def compute_purchase_line(cls, request):
|
||||
pool = Pool()
|
||||
product_obj = pool.get('product.product')
|
||||
tax_rule_obj = pool.get('account.tax.rule')
|
||||
line_obj = pool.get('purchase.line')
|
||||
Product = pool.get('product.product')
|
||||
Line = pool.get('purchase.line')
|
||||
|
||||
line = {
|
||||
'product': request.product.id,
|
||||
'unit': request.uom.id,
|
||||
'quantity': request.quantity,
|
||||
'description': request.product.name,
|
||||
}
|
||||
line = Line(
|
||||
product=request.product,
|
||||
unit=request.uom,
|
||||
quantity=request.quantity,
|
||||
description=request.product.name,
|
||||
)
|
||||
|
||||
# XXX purchase with several lines of the same product
|
||||
with Transaction().set_context(uom=request.uom.id,
|
||||
supplier=request.party.id,
|
||||
currency=request.company.currency.id):
|
||||
product_price = product_obj.get_purchase_price(
|
||||
[request.product.id], request.quantity)[request.product.id]
|
||||
product_price = Product.get_purchase_price(
|
||||
[request.product], request.quantity)[request.product.id]
|
||||
product_price = product_price.quantize(
|
||||
Decimal(1) / 10 ** line_obj.unit_price.digits[1])
|
||||
Decimal(1) / 10 ** Line.unit_price.digits[1])
|
||||
|
||||
if product_price is None:
|
||||
self.raise_user_error('missing_price', (request.product.name,
|
||||
cls.raise_user_error('missing_price', (request.product.name,
|
||||
request.product.id), 'please_update')
|
||||
line['unit_price'] = product_price
|
||||
line.unit_price = product_price
|
||||
|
||||
taxes = []
|
||||
for tax in request.product.supplier_taxes_used:
|
||||
if request.party and request.party.supplier_tax_rule:
|
||||
pattern = self._get_tax_rule_pattern(request)
|
||||
tax_ids = tax_rule_obj.apply(request.party.supplier_tax_rule,
|
||||
tax, pattern)
|
||||
pattern = cls._get_tax_rule_pattern(request)
|
||||
tax_ids = request.party.supplier_tax_rule.apply(tax, pattern)
|
||||
if tax_ids:
|
||||
taxes.extend(tax_ids)
|
||||
continue
|
||||
taxes.append(tax.id)
|
||||
line['taxes'] = [('add', taxes)]
|
||||
line.taxes = taxes
|
||||
return line
|
||||
|
||||
CreatePurchase()
|
||||
|
||||
+39
-39
@@ -2,13 +2,17 @@
|
||||
#this repository contains the full copyright notices and license terms.
|
||||
from trytond.model import ModelView, ModelSQL
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
|
||||
__all__ = ['ShipmentInternal']
|
||||
__metaclass__ = PoolMeta
|
||||
|
||||
|
||||
class ShipmentInternal(ModelSQL, ModelView):
|
||||
_name = 'stock.shipment.internal'
|
||||
__name__ = 'stock.shipment.internal'
|
||||
|
||||
def init(self, module_name):
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
cursor = Transaction().cursor
|
||||
# Migration from 1.2: packing renamed into shipment
|
||||
cursor.execute("UPDATE ir_model_data "\
|
||||
@@ -19,26 +23,27 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
"SET model = REPLACE(model, 'packing', 'shipment') "\
|
||||
"WHERE model like '%%packing%%' AND module = %s",
|
||||
(module_name,))
|
||||
super(ShipmentInternal, self).init(module_name)
|
||||
super(ShipmentInternal, cls).__register__(module_name)
|
||||
|
||||
def generate_internal_shipment(self):
|
||||
@classmethod
|
||||
def generate_internal_shipment(cls):
|
||||
"""
|
||||
Generate internal shipments to meet order points defined on
|
||||
non-warehouse location.
|
||||
"""
|
||||
pool = Pool()
|
||||
order_point_obj = pool.get('stock.order_point')
|
||||
uom_obj = pool.get('product.uom')
|
||||
product_obj = pool.get('product.product')
|
||||
date_obj = pool.get('ir.date')
|
||||
user_obj = pool.get('res.user')
|
||||
user_record = user_obj.browse(Transaction().user)
|
||||
today = date_obj.today()
|
||||
OrderPoint = pool.get('stock.order_point')
|
||||
Uom = pool.get('product.uom')
|
||||
Product = pool.get('product.product')
|
||||
Date = pool.get('ir.date')
|
||||
User = pool.get('res.user')
|
||||
Move = pool.get('stock.move')
|
||||
user_record = User(Transaction().user)
|
||||
today = Date.today()
|
||||
# fetch quantities on order points
|
||||
op_ids = order_point_obj.search([
|
||||
order_points = OrderPoint.search([
|
||||
('type', '=', 'internal'),
|
||||
])
|
||||
order_points = order_point_obj.browse(op_ids)
|
||||
id2product = {}
|
||||
location_ids = []
|
||||
for op in order_points:
|
||||
@@ -46,7 +51,7 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
location_ids.append(op.storage_location.id)
|
||||
|
||||
with Transaction().set_context(stock_date_end=today):
|
||||
pbl = product_obj.products_by_location(location_ids,
|
||||
pbl = Product.products_by_location(location_ids,
|
||||
list(id2product.iterkeys()), with_childs=True)
|
||||
|
||||
# Create a list of move to create
|
||||
@@ -60,21 +65,21 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
moves[key] = op.max_quantity - qty
|
||||
|
||||
# Compare with existing draft shipments
|
||||
shipment_ids = self.search([
|
||||
shipments = cls.search([
|
||||
('state', '=', 'draft'),
|
||||
['OR',
|
||||
('planned_date', '<=', today),
|
||||
('planned_date', '=', None),
|
||||
],
|
||||
])
|
||||
for shipment in self.browse(shipment_ids):
|
||||
for shipment in shipments:
|
||||
for move in shipment.moves:
|
||||
key = (shipment.to_location.id,
|
||||
shipment.from_location.id,
|
||||
move.product.id)
|
||||
if key not in moves:
|
||||
continue
|
||||
quantity = uom_obj.compute_qty(move.uom, move.quantity,
|
||||
quantity = Uom.compute_qty(move.uom, move.quantity,
|
||||
id2product[move.product.id].default_uom)
|
||||
moves[key] = max(0, moves[key] - quantity)
|
||||
|
||||
@@ -85,27 +90,22 @@ class ShipmentInternal(ModelSQL, ModelView):
|
||||
shipments.setdefault(
|
||||
(from_location, to_location), []).append((product, qty))
|
||||
# Create shipments and moves
|
||||
for shipment, moves in shipments.iteritems():
|
||||
from_location, to_location = shipment
|
||||
values = {
|
||||
'from_location': from_location,
|
||||
'to_location': to_location,
|
||||
'planned_date': today,
|
||||
'moves': [],
|
||||
}
|
||||
for locations, moves in shipments.iteritems():
|
||||
from_location, to_location = locations
|
||||
shipment = cls(
|
||||
from_location=from_location,
|
||||
to_location=to_location,
|
||||
planned_date=today,
|
||||
moves=[],
|
||||
)
|
||||
for move in moves:
|
||||
product, qty = move
|
||||
values['moves'].append(
|
||||
('create',
|
||||
{
|
||||
'from_location': from_location,
|
||||
'to_location': to_location,
|
||||
'product': product,
|
||||
'quantity': qty,
|
||||
'uom': id2product[product].default_uom.id,
|
||||
'company': user_record.company.id,
|
||||
}
|
||||
shipment.moves.append(Move(
|
||||
from_location=from_location,
|
||||
to_location=to_location,
|
||||
product=product,
|
||||
quantity=qty,
|
||||
uom=id2product[product].default_uom,
|
||||
company=user_record.company,
|
||||
))
|
||||
self.create(values)
|
||||
|
||||
ShipmentInternal()
|
||||
shipment.save()
|
||||
|
||||
+25
-31
@@ -64,10 +64,7 @@ class StockSupplyTestCase(unittest.TestCase):
|
||||
'''
|
||||
for purchase_date, delivery_time, supply_date in DATES:
|
||||
with Transaction().start(DB_NAME, USER, context=CONTEXT):
|
||||
product_supplier_id = self.create_product_supplier(
|
||||
delivery_time)
|
||||
product_supplier = self.product_supplier.browse(
|
||||
product_supplier_id)
|
||||
product_supplier = self.create_product_supplier(delivery_time)
|
||||
date = self.product_supplier.compute_supply_date(
|
||||
product_supplier, purchase_date)
|
||||
self.assertEqual(date, supply_date)
|
||||
@@ -78,10 +75,7 @@ class StockSupplyTestCase(unittest.TestCase):
|
||||
'''
|
||||
for purchase_date, delivery_time, supply_date in DATES:
|
||||
with Transaction().start(DB_NAME, USER, context=CONTEXT):
|
||||
product_supplier_id = self.create_product_supplier(
|
||||
delivery_time)
|
||||
product_supplier = self.product_supplier.browse(
|
||||
product_supplier_id)
|
||||
product_supplier = self.create_product_supplier(delivery_time)
|
||||
date = self.product_supplier.compute_purchase_date(
|
||||
product_supplier, supply_date)
|
||||
self.assertEqual(date, purchase_date)
|
||||
@@ -93,48 +87,48 @@ class StockSupplyTestCase(unittest.TestCase):
|
||||
:param delivery_time: time in days needed to supply
|
||||
:return: the id of the Product Supplier
|
||||
'''
|
||||
uom_category_id = self.uom_category.create({'name': 'Test'})
|
||||
uom_id = self.uom.create({
|
||||
uom_category = self.uom_category.create({'name': 'Test'})
|
||||
uom = self.uom.create({
|
||||
'name': 'Test',
|
||||
'symbol': 'T',
|
||||
'category': uom_category_id,
|
||||
'category': uom_category.id,
|
||||
'rate': 1.0,
|
||||
'factor': 1.0,
|
||||
})
|
||||
category_id = self.category.create({'name': 'ProdCategoryTest'})
|
||||
product_id = self.product.create({
|
||||
category = self.category.create({'name': 'ProdCategoryTest'})
|
||||
product = self.product.create({
|
||||
'name': 'ProductTest',
|
||||
'default_uom': uom_id,
|
||||
'category': category_id,
|
||||
'default_uom': uom.id,
|
||||
'category': category.id,
|
||||
'account_category': True,
|
||||
'list_price': Decimal(0),
|
||||
'cost_price': Decimal(0),
|
||||
})
|
||||
company_id, = self.company.search([('name', '=', 'B2CK')])
|
||||
self.user.write(USER, {
|
||||
'main_company': company_id,
|
||||
'company': company_id,
|
||||
company, = self.company.search([('name', '=', 'B2CK')])
|
||||
self.user.write([self.user(USER)], {
|
||||
'main_company': company.id,
|
||||
'company': company.id,
|
||||
})
|
||||
receivable_id, = self.account.search([
|
||||
receivable, = self.account.search([
|
||||
('kind', '=', 'receivable'),
|
||||
('company', '=', company_id),
|
||||
('company', '=', company.id),
|
||||
])
|
||||
payable_id, = self.account.search([
|
||||
payable, = self.account.search([
|
||||
('kind', '=', 'payable'),
|
||||
('company', '=', company_id),
|
||||
('company', '=', company.id),
|
||||
])
|
||||
supplier_id = self.party.create({
|
||||
supplier = self.party.create({
|
||||
'name': 'supplier',
|
||||
'account_receivable': receivable_id,
|
||||
'account_payable': payable_id,
|
||||
'account_receivable': receivable.id,
|
||||
'account_payable': payable.id,
|
||||
})
|
||||
product_supplier_id = self.product_supplier.create({
|
||||
'product': product_id,
|
||||
'company': company_id,
|
||||
'party': supplier_id,
|
||||
product_supplier = self.product_supplier.create({
|
||||
'product': product.id,
|
||||
'company': company.id,
|
||||
'party': supplier.id,
|
||||
'delivery_time': delivery_time,
|
||||
})
|
||||
return product_supplier_id
|
||||
return product_supplier
|
||||
|
||||
|
||||
def suite():
|
||||
|
||||
Reference in New Issue
Block a user