Active Record

This commit is contained in:
Cédric Krier
2012-09-24 22:18:24 +02:00
parent e34ac864a3
commit bd2b9b72a1
6 changed files with 363 additions and 414 deletions
+18
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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():