Supply module: initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
#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 order_point import *
|
||||
from product import *
|
||||
from purchase_request import *
|
||||
from supply_schedule import *
|
||||
@@ -0,0 +1,23 @@
|
||||
#This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms.
|
||||
{
|
||||
'name': 'Supply Management',
|
||||
'version': '0.1',
|
||||
'author': 'B2CK',
|
||||
'email': 'info@b2ck.com',
|
||||
'website': 'http://www.tryton.org/',
|
||||
'category': 'Generic',
|
||||
'description': 'Supply Management Module',
|
||||
'depends': [
|
||||
'product',
|
||||
'stock',
|
||||
'purchase',
|
||||
],
|
||||
'xml': [
|
||||
'product.xml',
|
||||
'order_point.xml',
|
||||
'purchase_request.xml',
|
||||
'supply_schedule.xml',
|
||||
],
|
||||
'translation': [
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#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.osv import fields, OSV
|
||||
from trytond.wizard import Wizard, WizardOSV
|
||||
|
||||
class OrderPoint(OSV):
|
||||
"""
|
||||
Order Point: Provide a way to define a supply policy for each
|
||||
product on each locations. Order points on warehouse are
|
||||
conciderer by the supply scheduler to generate purchase requests.
|
||||
"""
|
||||
_name = 'supply.order_point'
|
||||
_description = __doc__
|
||||
|
||||
product = fields.Many2One(
|
||||
'product.product', 'Product', required=True, select=True)
|
||||
location = fields.Many2One(
|
||||
'stock.location', 'Location', required=True, select=True)
|
||||
min_quantity = fields.Float('Minimal Quantity', required=True)
|
||||
max_quantity = fields.Float('Maximal Quantity', required=True)
|
||||
company = fields.Many2One('company.company', 'Company', required=True)
|
||||
|
||||
def default_company(self, cursor, user, context=None):
|
||||
company_obj = self.pool.get('company.company')
|
||||
if context is None:
|
||||
context = {}
|
||||
if context.get('company'):
|
||||
return company_obj.name_get(cursor, user, context['company'],
|
||||
context=context)[0]
|
||||
return False
|
||||
|
||||
def __init__(self):
|
||||
super(OrderPoint, self).__init__()
|
||||
self._sql_constraints += [
|
||||
('product_location_uniq', 'UNIQUE(product,location,company)',
|
||||
'Only one order point is allowed for each product-location pair.'),
|
||||
('check_min_max_quantity',
|
||||
'CHECK( max_quantity is null or min_quantity is null or max_quantity >= min_quantity )',
|
||||
'Maximal quantity must be bigger than Minimal quantity'),
|
||||
]
|
||||
|
||||
OrderPoint()
|
||||
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="order_point_view_form">
|
||||
<field name="model">supply.order_point</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<form string="Order Point">
|
||||
<label name="product"/>
|
||||
<field name="product"/>
|
||||
<label name="location"/>
|
||||
<field name="location"/>
|
||||
<label name="company"/>
|
||||
<field name="company"/>
|
||||
<label name="min_quantity"/>
|
||||
<field name="min_quantity"/>
|
||||
<label name="max_quantity"/>
|
||||
<field name="max_quantity"/>
|
||||
</form>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="order_point_view_tree">
|
||||
<field name="model">supply.order_point</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<tree string="Locations">
|
||||
<field name="product" select="1"/>
|
||||
<field name="location" select="1"/>
|
||||
<field name="company"/>
|
||||
<field name="min_quantity" select="2"/>
|
||||
<field name="max_quantity" select="2"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_order_point_form">
|
||||
<field name="name">Order Points</field>
|
||||
<field name="res_model">supply.order_point</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_order_point_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="order_point_view_tree"/>
|
||||
<field name="act_window" ref="act_order_point_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_order_point_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="order_point_view_form"/>
|
||||
<field name="act_window" ref="act_order_point_form"/>
|
||||
</record>
|
||||
<menuitem parent="stock.menu_stock" sequence="60"
|
||||
action="act_order_point_form" id="menu_order_point_form"/>
|
||||
</data>
|
||||
</tryton>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#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.osv import fields, OSV
|
||||
import datetime
|
||||
|
||||
class Product(OSV):
|
||||
"Product"
|
||||
_name = "product.product"
|
||||
|
||||
order_points = fields.One2Many(
|
||||
'supply.order_point', 'product', 'Order Points')
|
||||
|
||||
Product()
|
||||
|
||||
class ProductSupplier(OSV):
|
||||
'Product Supplier'
|
||||
_name = 'purchase.product_supplier'
|
||||
|
||||
lead_time = fields.Integer("Lead Time") # XXX put it on the party obj ?
|
||||
|
||||
ProductSupplier()
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="product_supplier_view_form">
|
||||
<field name="model">purchase.product_supplier</field>
|
||||
<field name="type">form</field>
|
||||
<field name="inherit" ref="purchase.product_supplier_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<data>
|
||||
<xpath
|
||||
expr="/form//field[@name="code"]" position="after">
|
||||
<label name="lead_time"/>
|
||||
<field name="lead_time"/>
|
||||
</xpath>
|
||||
</data>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -0,0 +1,198 @@
|
||||
#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.osv import fields, OSV
|
||||
from trytond.wizard import Wizard, WizardOSV
|
||||
import datetime
|
||||
|
||||
# TODO ensure that the link p_request p_line is never inconsistent
|
||||
# (uom, qty, product, ...).
|
||||
class PurchaseRequest(OSV):
|
||||
'Purchase Request'
|
||||
_name = 'supply.purchase_request'
|
||||
_description = __doc__
|
||||
|
||||
product = fields.Many2One(
|
||||
'product.product', 'Product', required=True, select=True)
|
||||
party = fields.Many2One('relationship.party', 'Party', select=True)
|
||||
quantity = fields.Float('Quantity', required=True)
|
||||
uom = fields.Many2One('product.uom', 'UOM', required=True, select=True)
|
||||
purchase_date = fields.Date('Expected Purchase Date')
|
||||
supply_date = fields.Date('Expected Suplly Date')
|
||||
stock_level = fields.Float('Stock at Supply Date')
|
||||
warehouse = fields.Many2One(
|
||||
'stock.location', "Warehouse", required=True,
|
||||
domain="[('type', '=', 'warehouse')]")
|
||||
purchase_line = fields.Many2One('purchase.line', 'Purchase Line')
|
||||
company = fields.Many2One('company.company', 'Company', required=True)
|
||||
|
||||
def default_company(self, cursor, user, context=None):
|
||||
company_obj = self.pool.get('company.company')
|
||||
if context is None:
|
||||
context = {}
|
||||
if context.get('company'):
|
||||
return company_obj.name_get(cursor, user, context['company'],
|
||||
context=context)[0]
|
||||
return False
|
||||
|
||||
PurchaseRequest()
|
||||
|
||||
|
||||
class CreatePurchaseAsk(WizardOSV):
|
||||
_name = 'supply.purchase_request.create_purchase.ask'
|
||||
party = fields.Many2One('relationship.party', 'Supplier', readonly=True)
|
||||
company = fields.Many2One('company.company', 'Company', readonly=True)
|
||||
payment_term = fields.Many2One(
|
||||
'account.invoice.payment_term', 'Payment Term', required=True)
|
||||
|
||||
CreatePurchaseAsk()
|
||||
|
||||
class CreatePurchase(Wizard):
|
||||
'Create Purchase'
|
||||
_name = 'supply.purchase_request.create_purchase'
|
||||
|
||||
states = {
|
||||
|
||||
'init': {
|
||||
'result': {
|
||||
'type': 'action',
|
||||
'action': '_compute_purchase',
|
||||
'state': 'choice',
|
||||
},
|
||||
},
|
||||
|
||||
'choice': {
|
||||
'result': {
|
||||
'type': 'choice',
|
||||
'next_state': '_check_payment_term',
|
||||
},
|
||||
},
|
||||
|
||||
'ask_user': {
|
||||
'actions': ['_set_default'],
|
||||
'result': {
|
||||
'type': 'form',
|
||||
'object': 'supply.purchase_request.create_purchase.ask',
|
||||
'state': [
|
||||
('end', 'Cancel', 'tryton-cancel'),
|
||||
('choice', 'Continue', 'tryton-ok', True),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
'create': {
|
||||
'result': {
|
||||
'type': 'action',
|
||||
'action': '_create_purchase',
|
||||
'state': 'end',
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
def _set_default(self, cursor, user, data, context=None):
|
||||
|
||||
if not data.get('party_wo_pt'):
|
||||
return {}
|
||||
party, company = data['party_wo_pt'].pop()
|
||||
return {'party': party,'company': company}
|
||||
|
||||
def _check_payment_term(self, cursor, user, data, context=None):
|
||||
party_obj = self.pool.get('relationship.party')
|
||||
if 'purchases' not in data:
|
||||
return 'end'
|
||||
form = data['form']
|
||||
if form.get('payment_term') and form.get('party') and \
|
||||
form.get('company'):
|
||||
for key, val in data['purchases'].iteritems():
|
||||
if (key[0], key[1]) == (form['party'],
|
||||
form['company']):
|
||||
val['payment_term'] = form['payment_term']
|
||||
local_context = context.copy()
|
||||
local_context['company'] = form['company']
|
||||
party_obj.write(
|
||||
cursor, user, form['party'],
|
||||
{'supplier_payment_term': form['payment_term']}, context=local_context)
|
||||
if data.get('party_wo_pt'):
|
||||
return 'ask_user'
|
||||
return 'create'
|
||||
|
||||
def _compute_purchase(self, cursor, user, data, context=None):
|
||||
request_obj = self.pool.get('supply.purchase_request')
|
||||
requests = request_obj.browse(cursor, user, data['ids'], context=context)
|
||||
purchases = {}
|
||||
for request in requests:
|
||||
if (not request.party) or request.purchase_line:
|
||||
continue
|
||||
key = (request.party.id, request.company.id, request.warehouse.id)
|
||||
|
||||
if key not in purchases:
|
||||
purchase = {
|
||||
'company': request.company.id,
|
||||
'party': request.party.id,
|
||||
'purchase_date': request.purchase_date or datetime.date.today(),
|
||||
'payment_term': request.party.supplier_payment_term and \
|
||||
request.party.supplier_payment_term.id or None,
|
||||
'warehouse': request.warehouse.id,
|
||||
'currency': request.company.currency.id,
|
||||
'lines': [],
|
||||
}
|
||||
purchases[key] = purchase
|
||||
else:
|
||||
purchase = purchases[key]
|
||||
|
||||
purchase['lines'].append({
|
||||
'product': request.product.id,
|
||||
'unit': request.uom.id,
|
||||
'quantity': request.quantity,
|
||||
'request': request.id,
|
||||
})
|
||||
if request.purchase_date:
|
||||
if not purchase['purchase_date']:
|
||||
purchase['purchase_date'] = request.purchase_date
|
||||
else:
|
||||
purchase['purchase_date'] = min(purchase['purchase_date'],
|
||||
request.purchase_date)
|
||||
data['purchases'] = purchases
|
||||
data['party_wo_pt'] = set(
|
||||
k[:2] for k in purchases if not purchases[k]['payment_term'])
|
||||
return {}
|
||||
|
||||
def _create_purchase(self, cursor, user, data, context=None):
|
||||
request_obj = self.pool.get('supply.purchase_request')
|
||||
purchase_obj = self.pool.get('purchase.purchase')
|
||||
line_obj = self.pool.get('purchase.line')
|
||||
product_obj = self.pool.get('product.product')
|
||||
created_ids = []
|
||||
products = []
|
||||
# collect product names
|
||||
for purchase in data['purchases'].itervalues():
|
||||
for line in purchase['lines']:
|
||||
products.append(line['product'])
|
||||
product_name = product_obj.name_get(
|
||||
cursor, user, products, context=context)
|
||||
|
||||
# create purchases, lines and update requests
|
||||
for purchase in data['purchases'].itervalues():
|
||||
purchase_lines = purchase.pop('lines')
|
||||
purchase_id = purchase_obj.create(
|
||||
cursor, user, purchase, context=context)
|
||||
created_ids.append(purchase_id)
|
||||
for line in purchase_lines:
|
||||
request_id = line.pop('request')
|
||||
line['purchase'] = purchase_id
|
||||
line['description'] = product_name[line['product']][1]
|
||||
local_context = context.copy()
|
||||
local_context['uom'] = line['unit']
|
||||
local_context['supplier'] = purchase['party']
|
||||
local_context['currency'] = purchase['currency']
|
||||
product_price = product_obj.get_purchase_price(
|
||||
cursor, user, [line['product']], line['quantity'],
|
||||
context=local_context)[line['product']]
|
||||
line['unit_price'] = product_price
|
||||
line_id = line_obj.create(cursor, user, line, context=context)
|
||||
request_obj.write(
|
||||
cursor, user, request_id, {'purchase_line': line_id},
|
||||
context=context)
|
||||
|
||||
return {}
|
||||
|
||||
CreatePurchase()
|
||||
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="purchase_request_view_form">
|
||||
<field name="model">supply.purchase_request</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<form string="Purchase Request">
|
||||
<label name="product"/>
|
||||
<field name="product"/>
|
||||
<label name="party"/>
|
||||
<field name="party"/>
|
||||
<label name="quantity"/>
|
||||
<field name="quantity"/>
|
||||
<label name="uom"/>
|
||||
<field name="uom"/>
|
||||
<label name="warehouse"/>
|
||||
<field name="warehouse"/>
|
||||
<label name="purchase_date"/>
|
||||
<field name="purchase_date"/>
|
||||
<label name="supply_date"/>
|
||||
<field name="supply_date"/>
|
||||
<label name="purchase_line"/>
|
||||
<field name="purchase_line"/>
|
||||
<label name="stock_level"/>
|
||||
<field name="stock_level"/>
|
||||
<label name="company"/>
|
||||
<field name="company"/>
|
||||
</form>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_request_view_tree">
|
||||
<field name="model">supply.purchase_request</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<tree string="Locations">
|
||||
<field name="product" select="1"/>
|
||||
<field name="party" select="1"/>
|
||||
<field name="quantity" select="2"/>
|
||||
<field name="uom" select="2"/>
|
||||
<field name="warehouse" select="2"/>
|
||||
<field name="purchase_date"/>
|
||||
<field name="supply_date"/>
|
||||
<field name="purchase_line"/>
|
||||
<field name="stock_level"/>
|
||||
<field name="company"/>
|
||||
</tree>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_purchase_request_form">
|
||||
<field name="name">Purchase Requests</field>
|
||||
<field name="res_model">supply.purchase_request</field>
|
||||
<field name="view_type">form</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_purchase_request_form_view1">
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="view" ref="purchase_request_view_tree"/>
|
||||
<field name="act_window" ref="act_purchase_request_form"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_purchase_request_form_view2">
|
||||
<field name="sequence" eval="20"/>
|
||||
<field name="view" ref="purchase_request_view_form"/>
|
||||
<field name="act_window" ref="act_purchase_request_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem parent="purchase.menu_purchase" sequence="20"
|
||||
action="act_purchase_request_form" id="menu_purchase_request_form"/>
|
||||
|
||||
|
||||
<record model="ir.ui.view" id="create_purchase.ask_form">
|
||||
<field name="model">supply.purchase_request.create_purchase.ask</field>
|
||||
<field name="type">form</field>
|
||||
<field name="arch" type="xml">
|
||||
<![CDATA[
|
||||
<form string="Create Purchase Request: Missing Payment Term" col="2" cursor="payment_term">
|
||||
<label name="party"/>
|
||||
<field name="party"/>
|
||||
<label name="company"/>
|
||||
<field name="company"/>
|
||||
<label name="payment_term"/>
|
||||
<field name="payment_term"/>
|
||||
</form>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.action.wizard" id="wizard_create_purchase">
|
||||
<field name="name">Create Purchase</field>
|
||||
<field name="wiz_name">supply.purchase_request.create_purchase</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="create_purchase_keyword">
|
||||
<field name="keyword">form_action</field>
|
||||
<field name="model">supply.purchase_request,0</field>
|
||||
<field name="action" ref="wizard_create_purchase"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -0,0 +1,286 @@
|
||||
from trytond.wizard import Wizard, WizardOSV
|
||||
import datetime
|
||||
# * Generate for X day in advance ?
|
||||
# * Provide a way to choose how mutch day before the shortage the suplly
|
||||
# must be planned
|
||||
|
||||
class GeneratePurchaseRequest(Wizard):
|
||||
'Generate Purchase Requests'
|
||||
_name = 'supply.generate_purchase_request'
|
||||
states = {
|
||||
'init': {
|
||||
'result': {
|
||||
'type': 'action',
|
||||
'action': 'generate_requests',
|
||||
'state': 'end',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def generate_requests(self, cursor, user, data, context=None):
|
||||
"""
|
||||
For each order point compute the purchase that must be create
|
||||
today to meet product outputs.
|
||||
"""
|
||||
order_point_obj = self.pool.get('supply.order_point')
|
||||
purchase_request_obj = self.pool.get('supply.purchase_request')
|
||||
product_obj = self.pool.get('product.product')
|
||||
order_point_ids = order_point_obj.search(
|
||||
cursor, user, [], context=context)
|
||||
order_points = order_point_obj.browse( #XXX filter on company ??
|
||||
cursor, user, order_point_ids, context=context)
|
||||
|
||||
product_ids = [op.product.id for op in order_points]
|
||||
products = product_obj.browse(cursor, user, product_ids, context=context)
|
||||
local_context = context.copy()
|
||||
new_requests = []
|
||||
|
||||
|
||||
for product in products:
|
||||
# Get min and max supply dates
|
||||
min_date, max_date = self.get_supply_dates(
|
||||
cursor, user, product, context=context)
|
||||
# Fetch all locations for whose this product has an order point
|
||||
locations = [op.location.id for op in product.order_points]
|
||||
# Compute stock
|
||||
local_context.update({'stock_date_end': min_date or datetime.date.max})
|
||||
pbl = product_obj.products_by_location(
|
||||
cursor, user, locations, [product.id], with_childs=True,
|
||||
skip_zero=False, context=local_context)
|
||||
print "DATES",min_date, max_date, pbl
|
||||
for order_point in product.order_points:
|
||||
# ignore order point on other locations than warehouse
|
||||
if order_point.location.type != 'warehouse':
|
||||
continue
|
||||
print "OP",order_point.id, order_point.location.id, product.id
|
||||
# Search for shortage between min-max
|
||||
shortage_date, stock_quantity = self.get_shortage(
|
||||
cursor, user, order_point, min_date, max_date,
|
||||
min_date_stock=pbl[order_point.location.id, product.id],
|
||||
context=context)
|
||||
|
||||
print "REQUEST",order_point.product.name,order_point.location.name, shortage_date, stock_quantity
|
||||
|
||||
if shortage_date == None or stock_quantity == None:
|
||||
continue
|
||||
request_val = self.compute_request(
|
||||
cursor, user, order_point, shortage_date,
|
||||
stock_quantity, context=context)
|
||||
|
||||
new_requests.append(request_val)
|
||||
self.create_requests(cursor, user, new_requests, context=context)
|
||||
return {}
|
||||
|
||||
def create_requests(self, cursor, user, new_requests, context=None):
|
||||
"""
|
||||
Compare new_requests with already existing request and avoid
|
||||
to re-create existing requests.
|
||||
"""
|
||||
# delete purchase request without a purchase line
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
request_obj = self.pool.get('supply.purchase_request')
|
||||
product_supplier_obj = self.pool.get('purchase.product_supplier')
|
||||
req_ids = request_obj.search(
|
||||
cursor, user, [('purchase_line', '=', False)], context=context)
|
||||
request_obj.delete(cursor, user, req_ids, context=context)
|
||||
|
||||
req_ids = request_obj.search(cursor, user, [], context=context)
|
||||
requests = request_obj.browse(cursor, user, req_ids, context=context)
|
||||
# Fetch lead_times for each (product,supplier)
|
||||
sup_lead_time = {}
|
||||
for request in requests:
|
||||
product, supplier = None, None
|
||||
if request.purchase_line:
|
||||
product = request.purchase_line.product.id
|
||||
supplier = request.purchase_line.purchase.party.id
|
||||
else:
|
||||
product = request.product.id
|
||||
supplier = request.party and request.party.id
|
||||
if not supplier:
|
||||
continue
|
||||
sup_lead_time[product, supplier] = None
|
||||
|
||||
print "TUPLE",['OR', ] + [
|
||||
['AND', ('product', '=', x[0]), ('party', '=', x[1])] \
|
||||
for x in sup_lead_time.iterkeys()
|
||||
]
|
||||
|
||||
prod_sup_ids = product_supplier_obj.search(
|
||||
cursor, user,
|
||||
['OR', ] + [
|
||||
['AND', ('product', '=', x[0]), ('party', '=', x[1])] \
|
||||
for x in sup_lead_time.iterkeys()
|
||||
],
|
||||
context=context)
|
||||
for prod_sup in product_supplier_obj.browse(cursor, user, prod_sup_ids,
|
||||
context=context):
|
||||
sup_lead_time[prod_sup.product.id, prod_sup.party.id] = \
|
||||
prod_sup.lead_time
|
||||
|
||||
# Fetch data from existing requests
|
||||
existing_req = {}
|
||||
for request in requests:
|
||||
if request.purchase_line:
|
||||
product = request.purchase_line.product
|
||||
warehouse = request.purchase_line.purchase.warehouse
|
||||
purchase_date = request.purchase_line.purchase.purchase_date
|
||||
qty = line.quantity
|
||||
uom = line.unit
|
||||
supplier = line.purchase.party
|
||||
else:
|
||||
product = request.product
|
||||
warehouse = request.warehouse
|
||||
purchase_date = request.purchase_date
|
||||
qty = request.quantity
|
||||
uom = request.uom
|
||||
supplier = request.party.id
|
||||
|
||||
lead_time = sup_lead_time.get((product.id, supplier.id))
|
||||
if lead_time:
|
||||
supply_date = purchase_date + \
|
||||
datetime.timedelta(lead_time)
|
||||
else:
|
||||
supply_date = datetime.date.max
|
||||
|
||||
existing_req.setdefault((product.id, warehouse.id), []).append(
|
||||
{'supply_date': supply_date, 'quantity': qty, 'uom': uom})
|
||||
|
||||
for i in existing_req.itervalues():
|
||||
i.sort
|
||||
print "OLD_REQ",existing_req
|
||||
print "NEW_REQ", new_requests
|
||||
# Update new requests to take existing requests into account
|
||||
new_requests.sort(lambda r,s: cmp(r['supply_date'],s['supply_date']))
|
||||
for new_req in new_requests:
|
||||
for old_req in existing_req.get((new_req['product'].id,
|
||||
new_req['warehouse'].id), []):
|
||||
|
||||
print old_req['supply_date'] <= new_req['supply_date'], old_req['supply_date'], new_req['supply_date']
|
||||
if old_req['supply_date'] <= new_req['supply_date']:
|
||||
quantity = uom_obj.compute_qty(
|
||||
cursor, user, old_req['uom'], old_req['quantity'],
|
||||
new_req['uom'], context=context)
|
||||
new_req['quantity'] = max(0.0, new_req['quantity'] - quantity)
|
||||
old_req['quantity'] = uom_obj.compute_qty(
|
||||
cursor, user, new_req['uom'],
|
||||
max(0.0, quantity - new_req['quantity']),
|
||||
old_req['uom'], context=context)
|
||||
else:
|
||||
break
|
||||
|
||||
for new_req in new_requests:
|
||||
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,
|
||||
'company': new_req['company'].id
|
||||
})
|
||||
print "CREATE", new_req
|
||||
|
||||
request_obj.create(cursor, user, new_req, context=context)
|
||||
|
||||
def get_supply_dates(self, cursor, user, product, context=None):
|
||||
"""
|
||||
Return for the given product min and max values for the
|
||||
earliest supply dates across all available suppliers.
|
||||
"""
|
||||
min_date = None
|
||||
max_date = None
|
||||
today = datetime.date.today()
|
||||
|
||||
for product_supplier in product.product_suppliers:
|
||||
supply_date = today + datetime.timedelta(product_supplier.lead_time)
|
||||
if (not min_date) or supply_date < min_date:
|
||||
min_date = supply_date
|
||||
if (not max_date) or supply_date > max_date:
|
||||
max_date = supply_date
|
||||
return (min_date, max_date)
|
||||
|
||||
def compute_request(self, cursor, user, order_point, shortage_date,
|
||||
stock_quantity, context=None):
|
||||
"""
|
||||
Return the value of the purchase request which will answer to
|
||||
the needed quantity at the given date. I.e: the latest
|
||||
purchase date, the expected supply date and the prefered
|
||||
supplier.
|
||||
"""
|
||||
product = order_point.product
|
||||
supplier = None
|
||||
seq = None
|
||||
on_time = False
|
||||
today = datetime.date.today()
|
||||
|
||||
for product_supplier in product.product_suppliers:
|
||||
supply_date = today + datetime.timedelta(product_supplier.lead_time)
|
||||
sup_on_time = supply_date < shortage_date
|
||||
if not supplier:
|
||||
supplier = product_supplier.party
|
||||
on_time = sup_on_time
|
||||
seq = product_supplier.sequence
|
||||
continue
|
||||
|
||||
if (sup_on_time, product_supplier.sequence) < (on_time, seq):
|
||||
supplier = product_supplier.party
|
||||
on_time = sup_on_time
|
||||
seq = product_supplier.sequence
|
||||
|
||||
if supplier and product_supplier.lead_time:
|
||||
purchase_date = \
|
||||
shortage_date - datetime.timedelta(product_supplier.lead_time)
|
||||
else:
|
||||
purchase_date = today
|
||||
|
||||
return {'product': product,
|
||||
'party': supplier and supplier or None,
|
||||
'quantity': order_point.max_quantity - stock_quantity,
|
||||
'uom': product.default_uom,
|
||||
'purchase_date': purchase_date,
|
||||
'supply_date': shortage_date,
|
||||
'stock_level': stock_quantity,
|
||||
'company': order_point.company,
|
||||
'warehouse': order_point.location,
|
||||
}
|
||||
|
||||
def get_shortage(self, cursor, user, order_point, min_date,
|
||||
max_date, min_date_stock, context=None):
|
||||
"""
|
||||
Compute stock quantities between the two given dates. If given
|
||||
the order point, one date will be lacking in products this
|
||||
date is returned alongside the stock level at this date.
|
||||
|
||||
If no dates are given, the stock is computed for an infinire
|
||||
date and the shortage date is today.
|
||||
"""
|
||||
product_obj = self.pool.get('product.product')
|
||||
|
||||
if not min_date:
|
||||
if min_date_stock < order_point.min_quantity:
|
||||
return (
|
||||
datetime.date.today(), min_date_stock)
|
||||
else:
|
||||
return (None, None)
|
||||
if not max_date: max_date = min_date
|
||||
|
||||
current_date = min_date
|
||||
current_stock = min_date_stock
|
||||
while current_date <= max_date:
|
||||
print "SHTG", current_stock, order_point.min_quantity
|
||||
if current_stock < order_point.min_quantity:
|
||||
return current_date, current_stock
|
||||
|
||||
local_context = context.copy()
|
||||
local_context['stock_date_start'] = current_date
|
||||
local_context['stock_date_end'] = current_date
|
||||
|
||||
res = product_obj.products_by_location(
|
||||
cursor, user, [order_point.location.id],
|
||||
[order_point.location.id], with_childs=True, skip_zero=False,
|
||||
context=context)
|
||||
for qty in res.itervalues():
|
||||
current_stock += qty
|
||||
current_date += datetime.timedelta(1)
|
||||
|
||||
return (None, None)
|
||||
|
||||
GeneratePurchaseRequest()
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of this repository contains the full copyright notices and license terms. -->
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.action.wizard" id="act_generate_purchase_request">
|
||||
<field name="name">Generate Purchase Requests</field>
|
||||
<field name="wiz_name">supply.generate_purchase_request</field>
|
||||
</record>
|
||||
<menuitem parent="purchase.menu_purchase" action="act_generate_purchase_request"
|
||||
id="menu_generate_purchase_request"/>
|
||||
</data>
|
||||
</tryton>
|
||||
Reference in New Issue
Block a user