[MERGE] trunk-wms branch which include a lot of freaking features. Check the diff by yourself :-)
bzr revid: qdp-launchpad@openerp.com-20140508145015-p9fu1ni9x9ujcrhm
This commit is contained in:
commit
5b6b96e334
|
@ -57,7 +57,7 @@
|
|||
<field eval="0" name="days2"/>
|
||||
<field eval="account_payment_term_net" name="payment_id"/>
|
||||
</record>
|
||||
|
||||
|
||||
<!--
|
||||
Account Journal Sequences
|
||||
-->
|
||||
|
|
|
@ -42,7 +42,7 @@ class stock_picking(osv.osv):
|
|||
'''Return ids of created invoices for the pickings'''
|
||||
res = super(stock_picking,self).action_invoice_create(cr, uid, ids, journal_id, group, type, context=context)
|
||||
if type == 'in_refund':
|
||||
for inv in self.pool.get('account.invoice').browse(cr, uid, res.values(), context=context):
|
||||
for inv in self.pool.get('account.invoice').browse(cr, uid, res, context=context):
|
||||
for ol in inv.invoice_line:
|
||||
if ol.product_id:
|
||||
oa = ol.product_id.property_stock_account_output and ol.product_id.property_stock_account_output.id
|
||||
|
@ -54,7 +54,7 @@ class stock_picking(osv.osv):
|
|||
self.pool.get('account.invoice.line').write(cr, uid, [ol.id], {'account_id': a})
|
||||
|
||||
elif type == 'in_invoice':
|
||||
for inv in self.pool.get('account.invoice').browse(cr, uid, res.values(), context=context):
|
||||
for inv in self.pool.get('account.invoice').browse(cr, uid, res, context=context):
|
||||
for ol in inv.invoice_line:
|
||||
if ol.product_id:
|
||||
oa = ol.product_id.property_stock_account_input and ol.product_id.property_stock_account_input.id
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
I configure the product with required accounts, and cost method = standard
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
self.write(cr, uid, [ref('product.product_product_3')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_4'),'valuation': 'real_time',
|
||||
self.write(cr, uid, [ref('product.product_product_10')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_4'),'valuation': 'real_time',
|
||||
'property_account_income': ref('account_anglo_income'),'property_account_expense': ref('account_anglo_cogs'),
|
||||
'property_account_creditor_price_difference': ref('account_anglo_price_difference'),'property_stock_account_input': ref('account_anglo_stock_input'),
|
||||
'property_stock_account_output': ref('account_anglo_stock_output'), 'cost_method': 'standard'})
|
||||
|
@ -115,7 +115,7 @@
|
|||
location_id: stock.stock_location_stock
|
||||
pricelist_id: 1
|
||||
order_line:
|
||||
- product_id: product.product_product_3
|
||||
- product_id: product.product_product_10
|
||||
product_qty: 1
|
||||
price_unit: 10
|
||||
date_planned: !eval "'%s' % (time.strftime('%Y-%m-%d'))"
|
||||
|
@ -126,10 +126,9 @@
|
|||
-
|
||||
Reception is ready for process so now done the reception.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001")).picking_ids
|
||||
partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001")).picking_ids[0]
|
||||
picking_id.do_transfer(context=context)
|
||||
-
|
||||
I check the Stock Interim account (Received) is credited successfully.
|
||||
-
|
||||
|
@ -192,38 +191,37 @@
|
|||
-
|
||||
!record {model: stock.picking, id: stock_picking_out001}:
|
||||
partner_id: base.res_partner_13
|
||||
invoice_state: 2binvoiced
|
||||
move_lines:
|
||||
- company_id: base.main_company
|
||||
location_id: stock.stock_location_stock
|
||||
product_id: product.product_product_3
|
||||
product_qty: 1.0
|
||||
product_id: product.product_product_10
|
||||
product_uom_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
location_dest_id: stock.stock_location_customers
|
||||
invoice_state: 2binvoiced
|
||||
move_type: direct
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I need to check the availability of the product, So I make my picking order for processing later.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.draft_force_assign(cr, uid, [ref("stock_picking_out001")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
self.action_confirm(cr, uid, [ref('stock_picking_out001')], context=context)
|
||||
|
||||
-
|
||||
I check the product availability, Product is available in the stock and ready to be sent.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.action_assign(cr, uid, [ref("stock_picking_out001")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
picking = self.browse(cr, uid, ref("stock_picking_out001"))
|
||||
assert picking.state == "confirmed", "Picking should be confirmed."
|
||||
for move_line in picking.move_lines:
|
||||
assert move_line.state == "confirmed", "Move should be confirmed."
|
||||
|
||||
-
|
||||
I process the delivery.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model':'stock.picking','active_ids':[ref('stock_picking_out001')]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001"))
|
||||
picking.do_transfer(context=context)
|
||||
-
|
||||
I check Stock Interim account (Delivery) is debited successfully.
|
||||
-
|
||||
|
@ -240,9 +238,7 @@
|
|||
!python {model: stock.invoice.onshipping}: |
|
||||
wiz_id = self.create(cr, uid, {'journal_id': ref('account.sales_journal')},
|
||||
{'active_ids': [ref("stock_picking_out001")], "active_model": "stock.picking"})
|
||||
self.create_invoice(cr, uid, [wiz_id], {"lang": "en_US",
|
||||
"search_default_available": 1, "tz": False, "active_model": "stock.picking",
|
||||
"contact_display": "partner", "active_ids": [ref("stock_picking_out001")], "active_id": ref("stock_picking_out001")})
|
||||
self.create_invoice(cr, uid, [wiz_id], {"active_ids": [ref("stock_picking_out001")], "active_id": ref("stock_picking_out001")})
|
||||
-
|
||||
I check that the customer invoice is created successfully.
|
||||
-
|
||||
|
|
|
@ -134,10 +134,9 @@
|
|||
-
|
||||
Reception is ready for process so now done the reception.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo")).picking_ids
|
||||
partial_id = self.create(cr, uid, {},context={'active_model': 'stock.picking','active_ids': [pick_ids[0].id]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo")).picking_ids[0]
|
||||
picking_id.do_transfer(context=context)
|
||||
-
|
||||
I check the Stock Interim account (Received) is credit successfully.
|
||||
-
|
||||
|
@ -195,37 +194,34 @@
|
|||
-
|
||||
!record {model: stock.picking, id: stock_picking_out001_fifo}:
|
||||
partner_id: base.res_partner_13
|
||||
invoice_state: 2binvoiced
|
||||
move_lines:
|
||||
- company_id: base.main_company
|
||||
location_id: stock.stock_location_stock
|
||||
product_id: product_fifo_anglo_saxon
|
||||
product_qty: 1.0
|
||||
product_uom_qty: 1.0
|
||||
location_dest_id: stock.stock_location_customers
|
||||
invoice_state: 2binvoiced
|
||||
move_type: direct
|
||||
type: out
|
||||
picking_type_id: stock.picking_type_out
|
||||
-
|
||||
I need to check the availability of the product, So I make my picking order for processing later.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.draft_force_assign(cr, uid, [ref("stock_picking_out001_fifo")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
self.action_confirm(cr, uid, [ref('stock_picking_out001_fifo')], context=context)
|
||||
-
|
||||
I check the product availability, Product is available in the stock and ready to be sent.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
self.action_assign(cr, uid, [ref("stock_picking_out001_fifo")], {"lang": "en_US", "search_default_available":
|
||||
1, "tz": False, "active_model": "ir.ui.menu", "contact_display": "partner",
|
||||
"active_ids": [ref("stock.menu_action_picking_tree")], "active_id": ref("stock.menu_action_picking_tree"),
|
||||
})
|
||||
picking = self.browse(cr, uid, ref("stock_picking_out001_fifo"))
|
||||
assert picking.state == "confirmed", "Picking should be confirmed."
|
||||
for move_line in picking.move_lines:
|
||||
assert move_line.state == "confirmed", "Move should be confirmed."
|
||||
-
|
||||
I process the delivery.
|
||||
-
|
||||
!python {model: stock.partial.picking}: |
|
||||
partial_id = self.create(cr, uid, {}, context={'active_model':'stock.picking','active_ids':[ref('stock_picking_out001_fifo')]})
|
||||
self.do_partial(cr, uid, [partial_id])
|
||||
!python {model: stock.picking}: |
|
||||
picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001_fifo"))
|
||||
picking.do_transfer(context=context)
|
||||
-
|
||||
I check Stock Interim account (Delivery) is debited successfully.
|
||||
-
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<record model="res.request.link" id="request_link_claim_from_delivery">
|
||||
<field name="name">Delivery Order</field>
|
||||
<field name="object">stock.picking.out</field>
|
||||
<field name="object">stock.picking</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
<field name="res_model">crm.claim</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="crm_claim.crm_case_claims_tree_view"/>
|
||||
<field name="context">{'default_ref': 'stock.picking.out,'+str(context.get('active_id', False))}</field>
|
||||
<field name="domain">[('ref','=','stock.picking.out,'+str(context.get('active_id',False)))]</field>
|
||||
<field name="context">{'default_ref': 'stock.picking,'+str(context.get('active_id', False))}</field>
|
||||
<field name="domain">[('ref','=','stock.picking,'+str(context.get('active_id',False)))]</field>
|
||||
</record>
|
||||
<record id="crm_claim_from_delivery" model="ir.ui.view">
|
||||
<field name="name">crm.claim.from_delivery.form</field>
|
||||
<field name="model">stock.picking.out</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_out_form"/>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/sheet/h1" position="before">
|
||||
<div class="oe_right oe_button_box">
|
||||
<button class="oe_inline oe_stat_button" type="action"
|
||||
<button class="oe_inline oe_stat_button" type="action" attrs="{'invisible': [('picking_type_id.code', '!=', 'outgoing')]}"
|
||||
name="%(action_claim_from_delivery)d" icon="fa-comments" >
|
||||
<field string="Claims" name="claim_count_out" widget="statinfo"/>
|
||||
</button>
|
||||
|
|
|
@ -7,7 +7,7 @@ class stock_picking(osv.osv):
|
|||
def _claim_count_out(self, cr, uid, ids, field_name, arg, context=None):
|
||||
Claim = self.pool['crm.claim']
|
||||
return {
|
||||
id: Claim.search_count(cr, uid, [('ref', '=',('stock.picking.out,' + str(ids[0])))], context=context)
|
||||
id: Claim.search_count(cr, uid, [('ref', '=',('stock.picking,' + str(ids[0])))], context=context)
|
||||
for id in ids
|
||||
}
|
||||
|
||||
|
@ -15,16 +15,3 @@ class stock_picking(osv.osv):
|
|||
'claim_count_out': fields.function(_claim_count_out, string='Claims', type='integer'),
|
||||
}
|
||||
|
||||
# Because of the way inheritance works in the ORM (bug), and the way stock.picking.out
|
||||
# is defined (inherit from stock.picking, dispatch read to stock.picking), it is necessary
|
||||
# to add the field claim_count_out to this class, even though the _claim_count_out method
|
||||
# in stock_picking_out will not be called (but its existence will be checked).
|
||||
class stock_picking_out(osv.osv):
|
||||
_inherit = 'stock.picking.out'
|
||||
|
||||
def _claim_count_out(self, cr, uid, ids, field_name, arg, context=None):
|
||||
return super(stock_picking_out, self)._claim_count_out(cr, uid, ids, field_name, arg, context=context)
|
||||
|
||||
_columns = {
|
||||
'claim_count_out': fields.function(_claim_count_out, string='Claims', type='integer'),
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
import delivery
|
||||
import partner
|
||||
import report
|
||||
import sale
|
||||
import stock
|
||||
|
||||
|
|
|
@ -35,10 +35,10 @@ invoices from picking, OpenERP is able to add and compute the shipping line.
|
|||
'depends': ['sale_stock'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'delivery_report.xml',
|
||||
'delivery_view.xml',
|
||||
'partner_view.xml',
|
||||
'delivery_data.xml'
|
||||
'delivery_data.xml',
|
||||
'views/report_shipping.xml',
|
||||
],
|
||||
'demo': ['delivery_demo.xml'],
|
||||
'test': ['test/delivery_cost.yml'],
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
<field name="default_code">Delivery</field>
|
||||
<field name="type">service</field>
|
||||
<field name="categ_id" ref="product.product_category_3"/>
|
||||
<field name="procure_method">make_to_order</field>
|
||||
<field name="standard_price">10.0</field>
|
||||
<field name="list_price">10.0</field>
|
||||
<field name="image" type="base64" file="delivery/static/img/product_product_delivery-image.jpg"/>
|
||||
|
@ -53,12 +52,6 @@
|
|||
<field name="partner_id" ref="res_partner_23"/>
|
||||
<field name="product_id" ref="product_product_delivery"/>
|
||||
</record>
|
||||
<record id="sale.sale_order_6" model="sale.order">
|
||||
<field name="carrier_id" ref="normal_delivery_carrier"/>
|
||||
</record>
|
||||
<record id="sale.sale_order_2" model="sale.order">
|
||||
<field name="carrier_id" ref="free_delivery_carrier"/>
|
||||
</record>
|
||||
|
||||
<!-- Carrier Grids -->
|
||||
|
||||
|
@ -94,5 +87,11 @@
|
|||
<field eval="0" name="list_price"/>
|
||||
<field eval="0" name="standard_price"/>
|
||||
</record>
|
||||
|
||||
<record forcecreate="True" id="property_delivery_carrier" model="ir.property">
|
||||
<field name="name">property_delivery_carrier</field>
|
||||
<field name="fields_id" search="[('model','=','res.partner'),('name','=','property_delivery_carrier')]"/>
|
||||
<field name="value" eval="'delivery.carrier,'+str(ref('normal_delivery_carrier'))"/>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<report
|
||||
id="report_shipping"
|
||||
model="stock.picking"
|
||||
name="sale.shipping"
|
||||
multi="True"
|
||||
rml="delivery/report/shipping.rml"
|
||||
string="Delivery order"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -27,15 +27,13 @@
|
|||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group name="general">
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group name="general">
|
||||
<group string="General Information">
|
||||
<field name="partner_id"/>
|
||||
<field name="product_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</group>
|
||||
<group col="4">
|
||||
<group string="Pricing Information">
|
||||
<field name="normal_price" attrs="{'readonly':[('use_detailed_pricelist', '=', True)]}"/>
|
||||
<label for="free_if_more_than"/>
|
||||
|
@ -46,6 +44,7 @@
|
|||
<field name="use_detailed_pricelist"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<field name="pricelist_ids" attrs="{'invisible':[('use_detailed_pricelist','=',False)]}" mode="tree">
|
||||
<tree string="Delivery grids">
|
||||
<field name="sequence"/>
|
||||
|
@ -102,7 +101,7 @@
|
|||
UPS Express, UPS Standard) with a set of pricing rules attached
|
||||
to each method.
|
||||
</p><p>
|
||||
These methods allows to automaticaly compute the delivery price
|
||||
These methods allow to automatically compute the delivery price
|
||||
according to your settings; on the sales order (based on the
|
||||
quotation) or the invoice (based on the delivery orders).
|
||||
</p>
|
||||
|
@ -220,48 +219,41 @@
|
|||
</record>
|
||||
|
||||
<record id="view_picking_withcarrier_out_form" model="ir.ui.view">
|
||||
<field name="name">delivery.stock.picking_withcarrier.out.form.view</field>
|
||||
<field name="model">stock.picking.out</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="min_date" position="after">
|
||||
<field name="carrier_id"/>
|
||||
<field name="carrier_tracking_ref"/>
|
||||
<field name="number_of_packages"/>
|
||||
</field>
|
||||
<field name="company_id" position="before">
|
||||
<field name="weight"/>
|
||||
<field name="weight_net" groups="base.group_no_one"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_withweight_internal_form" model="ir.ui.view">
|
||||
<field name="name">stock.picking_withweight.internal.form.view</field>
|
||||
<field name="name">delivery.stock.picking_withcarrier.form.view</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="before">
|
||||
<label for="weight" string="Weight"/>
|
||||
<div>
|
||||
<field name="weight" class="oe_inline"/>
|
||||
<field name="weight_uom_id" nolabel="1" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="weight_net" groups="base.group_no_one" class="oe_inline"/>
|
||||
</field>
|
||||
<xpath expr="//page[@string='Additional Info']/group[last()]" position="after">
|
||||
<separator string="Carrier Information"/>
|
||||
<group>
|
||||
<group>
|
||||
<field name="carrier_id"/>
|
||||
<field name="carrier_tracking_ref"/>
|
||||
</group>
|
||||
<group>
|
||||
<label for="weight" string="Weight"/>
|
||||
<div>
|
||||
<field name="weight" class="oe_inline"/>
|
||||
<field name="weight_uom_id" nolabel="1" class="oe_inline"/>
|
||||
<field name="number_of_packages"/>
|
||||
</div>
|
||||
<field name="weight_net" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_picking_tree4" model="ir.actions.act_window">
|
||||
<record id="action_picking_tree" model="ir.actions.act_window">
|
||||
<field name="name">Picking to be invoiced</field>
|
||||
<field name="res_model">stock.picking.out</field>
|
||||
<field name="res_model">stock.picking</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('invoice_state','=','2binvoiced'),('state','=','done'),('type','=','out')]</field>
|
||||
<field name="filter" eval="True"/>
|
||||
<field name="context">{'default_invoice_state': '2binvoiced', 'default_type': 'out', 'contact_display': 'partner'}</field>
|
||||
<field name="search_view_id" ref="stock.view_picking_out_search"/>
|
||||
<field name="search_view_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_withweight_internal_move_form" model="ir.ui.view">
|
||||
|
@ -315,28 +307,5 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_delivery_order_inherit_stock" model="ir.ui.view">
|
||||
<field name="name">stock.picking.out.form</field>
|
||||
<field name="model">stock.picking.out</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="/form/header//button[@name='action_process']" position="after">
|
||||
<button name="%(report_shipping)d" string="Print Delivery Order" states="confirmed,assigned" type="action"/>
|
||||
<button name="%(report_shipping)d" string="Print Delivery Order" states="done" type="action" class="oe_highlight"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_withcarrier_in_form" model="ir.ui.view">
|
||||
<field name="name">delivery.stock.picking_withcarrier.in.form.view</field>
|
||||
<field name="model">stock.picking.in</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="company_id" position="before">
|
||||
<field name="weight"/>
|
||||
<field name="weight_net" groups="base.group_no_one"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,221 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<document filename="Delivery Order.pdf">
|
||||
<template title="Delivery Order" author="OpenERP S.A.(sales@openerp.com)" allowSplitting="20">
|
||||
<pageTemplate id="first">
|
||||
<frame id="first" x1="30.0" y1="27.0" width="508" height="815"/>
|
||||
</pageTemplate>
|
||||
</template>
|
||||
<stylesheet>
|
||||
<blockTableStyle id="Standard_Outline">
|
||||
<blockAlignment value="LEFT"/>
|
||||
<blockValign value="TOP"/>
|
||||
</blockTableStyle>
|
||||
<blockTableStyle id="Tableau1">
|
||||
<blockAlignment value="LEFT"/>
|
||||
<blockValign value="TOP"/>
|
||||
</blockTableStyle>
|
||||
<blockTableStyle id="Table1">
|
||||
<blockAlignment value="LEFT"/>
|
||||
<blockValign value="TOP"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="0,0" stop="0,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="0,0" stop="0,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="1,0" stop="1,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="1,0" stop="1,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="2,0" stop="2,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="3,0" stop="3,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="3,0" stop="3,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="4,0" stop="4,-1"/>
|
||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="4,0" stop="4,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="4,0" stop="4,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
||||
</blockTableStyle>
|
||||
<blockTableStyle id="Table2">
|
||||
<blockAlignment value="LEFT"/>
|
||||
<blockValign value="TOP"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="0,0" stop="0,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="0,0" stop="0,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="1,0" stop="1,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="1,0" stop="1,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="2,0" stop="2,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="2,0" stop="2,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="3,0" stop="3,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="3,0" stop="3,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="3,-1" stop="3,-1"/>
|
||||
<lineStyle kind="LINEBEFORE" colorName="#e6e6e6" start="4,0" stop="4,-1"/>
|
||||
<lineStyle kind="LINEAFTER" colorName="#e6e6e6" start="4,0" stop="4,-1"/>
|
||||
<lineStyle kind="LINEABOVE" colorName="#e6e6e6" start="4,0" stop="4,0"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="4,-1" stop="4,-1"/>
|
||||
</blockTableStyle>
|
||||
<blockTableStyle id="Table3">
|
||||
<blockAlignment value="LEFT"/>
|
||||
<blockValign value="TOP"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#666666" start="0,-1" stop="0,-1"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#666666" start="1,-1" stop="1,-1"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#666666" start="2,-1" stop="2,-1"/>
|
||||
</blockTableStyle>
|
||||
<blockTableStyle id="Table4">
|
||||
<blockAlignment value="LEFT"/>
|
||||
<blockValign value="TOP"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="0,-1" stop="0,-1"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="1,-1" stop="1,-1"/>
|
||||
<lineStyle kind="LINEBELOW" colorName="#e6e6e6" start="2,-1" stop="2,-1"/>
|
||||
</blockTableStyle>
|
||||
<initialize>
|
||||
<paraStyle name="all" alignment="justify"/>
|
||||
</initialize>
|
||||
<paraStyle name="Standard" fontName="Helvetica"/>
|
||||
<paraStyle name="Text body" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="Footer" fontName="Helvetica"/>
|
||||
<paraStyle name="Table Contents" fontName="Helvetica"/>
|
||||
<paraStyle name="Table Heading" fontName="Helvetica" alignment="CENTER"/>
|
||||
<paraStyle name="Horizontal Line" fontName="Helvetica" fontSize="6.0" leading="8" spaceBefore="0.0" spaceAfter="14.0"/>
|
||||
<paraStyle name="Heading" fontName="Helvetica" fontSize="15.0" leading="19" spaceBefore="12.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="List" fontName="Helvetica" spaceBefore="0.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="Caption" fontName="Helvetica-Oblique" fontSize="9.0" leading="11" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="Index" fontName="Helvetica"/>
|
||||
<paraStyle name="terp_header" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="Heading 9" fontName="Helvetica-Bold" fontSize="75%" leading="NaN" spaceBefore="12.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_tblheader_General" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_tblheader_Details" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_default_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_Bold_8" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_tblheader_General_Centre" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_tblheader_General_Right" fontName="Helvetica-Bold" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_tblheader_Details_Centre" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_tblheader_Details_Right" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="6.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_default_Right_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_Centre_8" fontName="Helvetica" fontSize="8.0" leading="10" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_header_Right" fontName="Helvetica-Bold" fontSize="15.0" leading="19" alignment="LEFT" spaceBefore="12.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_header_Centre" fontName="Helvetica-Bold" fontSize="12.0" leading="15" alignment="CENTER" spaceBefore="12.0" spaceAfter="6.0"/>
|
||||
<paraStyle name="terp_default_address" fontName="Helvetica" fontSize="10.0" leading="13" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_Bold_9" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_Centre_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="CENTER" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_Right_9" fontName="Helvetica" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_1" fontName="Helvetica" fontSize="2.0" leading="3" alignment="LEFT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<paraStyle name="terp_default_Right_9_Bold" fontName="Helvetica-Bold" fontSize="9.0" leading="11" alignment="RIGHT" spaceBefore="0.0" spaceAfter="0.0"/>
|
||||
<images/>
|
||||
</stylesheet>
|
||||
<story>
|
||||
<pto>
|
||||
<pto_header>
|
||||
<blockTable colWidths="370.0,85.0,82.0" repeatRows="1" style="Table3">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details">Description</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details_Centre">Lot</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details_Right">Quantity</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
</pto_header>
|
||||
<para style="terp_default_9">[[repeatIn(objects,'o')]]</para>
|
||||
<para style="terp_default_8">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
|
||||
<blockTable colWidths="287.0,254.0" repeatRows="1" style="Tableau1">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="terp_default_Bold_9">Invoiced to</para>
|
||||
<para style="terp_default_9">[[ o.sale_id and o.sale_id.partner_invoice_id and o.sale_id.partner_invoice_id.name or '']]</para>
|
||||
<para style="terp_default_9">[[ o.sale_id and o.sale_id.partner_invoice_id and display_address(o.sale_id.partner_invoice_id) ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_9">[[ o.partner_id and o.partner_id and o.partner_id.name or '' ]]</para>
|
||||
<para style="terp_default_9">[[ o.partner_id and o.partner_id and display_address(o.partner_id) ]]</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
<para style="terp_header">Delivery Order : [[ o.name ]]</para>
|
||||
<para style="terp_default_8">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
<blockTable colWidths="126.0,103.0,103.0,103.0,103.0" style="Table1">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="terp_tblheader_General_Centre">Order Ref.</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_General_Centre">Order Date</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_General_Centre">Shipping Date</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_General_Centre">Carrier</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_General_Centre">Weight</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
<blockTable colWidths="126.0,103.0,103.0,103.0,103.0" style="Table2">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="terp_default_Centre_8">[[ o.sale_id and o.sale_id.name ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Centre_8">[[ formatLang(o.date,date_time=True) ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Centre_8">[[ formatLang(o.min_date,date_time = True) ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Centre_8">[[ o.carrier_id and o.carrier_id.name or '' ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Centre_8">[[ formatLang(o.weight) ]]</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
<para style="Standard">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
<blockTable colWidths="370.0,85.0,82.0" repeatRows="1" style="Table3">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details">Description</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details_Centre">Lot</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details_Right">Quantity</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
<section>
|
||||
<para style="terp_default_1">[[repeatIn(o.move_lines,'line')]]</para>
|
||||
<blockTable colWidths="370.0,85.0,83.0" style="Table4">
|
||||
<tr>
|
||||
<td>
|
||||
<para style="terp_default_9">[[line.product_id.code ]] [[ line.product_id and line.product_id.name or '']]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Centre_9">[[ (line.prodlot_id and (line.prodlot_id.name + (line.prodlot_id.ref and ('/' + line.prodlot_id.ref) or ''))) or ' ' ]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Right_9">[[ formatLang(line.product_qty) ]] [[ line.product_uom and line.product_uom.name ]]</para>
|
||||
</td>
|
||||
</tr>
|
||||
</blockTable>
|
||||
<para style="terp_default_1">
|
||||
<font color="white"> </font>
|
||||
</para>
|
||||
</section>
|
||||
</pto>
|
||||
</story>
|
||||
</document>
|
|
@ -91,6 +91,5 @@ class sale_order(osv.Model):
|
|||
'product_id': grid.carrier_id.product_id.id,
|
||||
'price_unit': grid_obj.get_price(cr, uid, grid.id, order, time.strftime('%Y-%m-%d'), context),
|
||||
'tax_id': [(6, 0, taxes_ids)],
|
||||
'type': 'make_to_stock',
|
||||
'is_delivery': True
|
||||
})
|
||||
|
|
|
@ -30,7 +30,6 @@ class stock_picking(osv.osv):
|
|||
|
||||
def _cal_weight(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
for picking in self.browse(cr, uid, ids, context=context):
|
||||
total_weight = total_weight_net = 0.00
|
||||
|
||||
|
@ -51,6 +50,7 @@ class stock_picking(osv.osv):
|
|||
result[line.picking_id.id] = True
|
||||
return result.keys()
|
||||
|
||||
|
||||
_columns = {
|
||||
'carrier_id':fields.many2one("delivery.carrier","Carrier"),
|
||||
'volume': fields.float('Volume'),
|
||||
|
@ -67,7 +67,7 @@ class stock_picking(osv.osv):
|
|||
'carrier_tracking_ref': fields.char('Carrier Tracking Ref', size=32),
|
||||
'number_of_packages': fields.integer('Number of Packages'),
|
||||
'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of measurement for Weight",),
|
||||
}
|
||||
}
|
||||
|
||||
def _prepare_shipping_invoice_line(self, cr, uid, picking, invoice, context=None):
|
||||
"""Prepare the invoice line to add to the shipping costs to the shipping's
|
||||
|
@ -118,26 +118,21 @@ class stock_picking(osv.osv):
|
|||
'invoice_line_tax_id': [(6, 0, taxes_ids)],
|
||||
}
|
||||
|
||||
def action_invoice_create(self, cr, uid, ids, journal_id=False,
|
||||
group=False, type='out_invoice', context=None):
|
||||
invoice_obj = self.pool.get('account.invoice')
|
||||
picking_obj = self.pool.get('stock.picking')
|
||||
def _create_invoice_from_picking(self, cr, uid, picking, vals, context=None):
|
||||
invoice_line_obj = self.pool.get('account.invoice.line')
|
||||
result = super(stock_picking, self).action_invoice_create(cr, uid,
|
||||
ids, journal_id=journal_id, group=group, type=type,
|
||||
context=context)
|
||||
for picking in picking_obj.browse(cr, uid, result.keys(), context=context):
|
||||
invoice = invoice_obj.browse(cr, uid, result[picking.id], context=context)
|
||||
invoice_line = self._prepare_shipping_invoice_line(cr, uid, picking, invoice, context=context)
|
||||
if invoice_line:
|
||||
invoice_line_obj.create(cr, uid, invoice_line)
|
||||
invoice_obj.button_compute(cr, uid, [invoice.id], context=context)
|
||||
return result
|
||||
def _get_default_uom(self,cr,uid,c):
|
||||
uom_categ, uom_categ_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product', 'product_uom_categ_kgm')
|
||||
return self.pool.get('product.uom').search(cr, uid, [('category_id', '=', uom_categ_id),('factor','=',1)])[0]
|
||||
invoice_id = super(stock_picking, self)._create_invoice_from_picking(cr, uid, picking, vals, context=context)
|
||||
invoice = self.browse(cr, uid, invoice_id, context=context)
|
||||
invoice_line = self._prepare_shipping_invoice_line(cr, uid, picking, invoice, context=context)
|
||||
if invoice_line:
|
||||
invoice_line_obj.create(cr, uid, invoice_line)
|
||||
return invoice_id
|
||||
|
||||
def _get_default_uom(self, cr, uid, context=None):
|
||||
uom_categ_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'product.product_uom_categ_kgm')
|
||||
return self.pool.get('product.uom').search(cr, uid, [('category_id', '=', uom_categ_id), ('factor', '=', 1)])[0]
|
||||
|
||||
_defaults = {
|
||||
'weight_uom_id': lambda self,cr,uid,c: self._get_default_uom(cr,uid,c)
|
||||
'weight_uom_id': lambda self, cr, uid, c: self._get_default_uom(cr, uid, c),
|
||||
}
|
||||
|
||||
|
||||
|
@ -177,73 +172,14 @@ class stock_move(osv.osv):
|
|||
}),
|
||||
'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of Measure (Unit of Measure) is the unit of measurement for Weight",),
|
||||
}
|
||||
def _get_default_uom(self,cr,uid,c):
|
||||
uom_categ, uom_categ_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'product', 'product_uom_categ_kgm')
|
||||
|
||||
def _get_default_uom(self, cr, uid, context=None):
|
||||
uom_categ_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'product.product_uom_categ_kgm')
|
||||
return self.pool.get('product.uom').search(cr, uid, [('category_id', '=', uom_categ_id),('factor','=',1)])[0]
|
||||
|
||||
_defaults = {
|
||||
'weight_uom_id': lambda self,cr,uid,c: self._get_default_uom(cr,uid,c)
|
||||
'weight_uom_id': lambda self, cr, uid, c: self._get_default_uom(cr, uid, c),
|
||||
}
|
||||
|
||||
# Redefinition of the new fields in order to update the model stock.picking.out in the orm
|
||||
# FIXME: this is a temporary workaround because of a framework bug (ref: lp996816). It should be removed as soon as
|
||||
# the bug is fixed
|
||||
|
||||
# TODO in trunk: Remove the duplication below using a mixin class!
|
||||
|
||||
class stock_picking_out(osv.osv):
|
||||
_inherit = 'stock.picking.out'
|
||||
|
||||
def _cal_weight(self, cr, uid, ids, name, args, context=None):
|
||||
return self.pool.get('stock.picking')._cal_weight(cr, uid, ids, name, args, context=context)
|
||||
|
||||
|
||||
def _get_picking_line(self, cr, uid, ids, context=None):
|
||||
return self.pool.get('stock.picking')._get_picking_line(cr, uid, ids, context=context)
|
||||
|
||||
_columns = {
|
||||
'carrier_id':fields.many2one("delivery.carrier","Carrier"),
|
||||
'volume': fields.float('Volume'),
|
||||
'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
|
||||
store={
|
||||
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20),
|
||||
'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20),
|
||||
}),
|
||||
'weight_net': fields.function(_cal_weight, type='float', string='Net Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
|
||||
store={
|
||||
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20),
|
||||
'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20),
|
||||
}),
|
||||
'carrier_tracking_ref': fields.char('Carrier Tracking Ref', size=32),
|
||||
'number_of_packages': fields.integer('Number of Packages'),
|
||||
'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of measurement for Weight",),
|
||||
}
|
||||
|
||||
class stock_picking_in(osv.osv):
|
||||
_inherit = 'stock.picking.in'
|
||||
|
||||
def _cal_weight(self, cr, uid, ids, name, args, context=None):
|
||||
return self.pool.get('stock.picking')._cal_weight(cr, uid, ids, name, args, context=context)
|
||||
|
||||
def _get_picking_line(self, cr, uid, ids, context=None):
|
||||
return self.pool.get('stock.picking')._get_picking_line(cr, uid, ids, context=context)
|
||||
|
||||
_columns = {
|
||||
'carrier_id':fields.many2one("delivery.carrier","Carrier"),
|
||||
'volume': fields.float('Volume'),
|
||||
'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
|
||||
store={
|
||||
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20),
|
||||
'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20),
|
||||
}),
|
||||
'weight_net': fields.function(_cal_weight, type='float', string='Net Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
|
||||
store={
|
||||
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 20),
|
||||
'stock.move': (_get_picking_line, ['product_id','product_qty','product_uom','product_uos_qty'], 20),
|
||||
}),
|
||||
'carrier_tracking_ref': fields.char('Carrier Tracking Ref', size=32),
|
||||
'number_of_packages': fields.integer('Number of Packages'),
|
||||
'weight_uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True,readonly="1",help="Unit of measurement for Weight",),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -1,55 +1,85 @@
|
|||
-
|
||||
In order to test Carrier Cost,
|
||||
-
|
||||
Create sale order with Normal Delivery Charges
|
||||
-
|
||||
!record {model: sale.order, id: sale_normal_delivery_charges}:
|
||||
partner_id: base.res_partner_18
|
||||
partner_invoice_id: base.res_partner_18
|
||||
partner_shipping_id: base.res_partner_18
|
||||
pricelist_id: product.list0
|
||||
order_policy: 'picking'
|
||||
order_line:
|
||||
- name: 'PC Assamble + 2GB RAM'
|
||||
product_id: product.product_product_4
|
||||
product_uom_qty: 1
|
||||
product_uos_qty: 1
|
||||
product_uom: product.product_uom_unit
|
||||
price_unit: 750.00
|
||||
carrier_id: normal_delivery_carrier
|
||||
-
|
||||
I add delivery cost in Sale order.
|
||||
|
||||
-
|
||||
!python {model: sale.order}: |
|
||||
self.delivery_set(cr, uid, [ref("sale.sale_order_6")], context=context)
|
||||
self.delivery_set(cr, uid, [ref("sale_normal_delivery_charges")], context=context)
|
||||
|
||||
-
|
||||
I check sale order after added delivery cost.
|
||||
-
|
||||
!python {model: sale.order.line}: |
|
||||
line_ids = self.search(cr, uid, [('order_id','=', ref('sale.sale_order_6')), ('product_id','=', ref('product_product_delivery'))])
|
||||
line_ids = self.search(cr, uid, [('order_id','=', ref('sale_normal_delivery_charges')), ('product_id','=', ref('product_product_delivery'))])
|
||||
assert len(line_ids), "Delivery cost is not Added"
|
||||
line_data = self.browse(cr ,uid ,line_ids[0] ,context)
|
||||
assert line_data.price_subtotal == 10, "Delivey cost is not correspond."
|
||||
-
|
||||
I confirm the sale order.
|
||||
-
|
||||
!workflow {model: sale.order, action: order_confirm, ref: sale.sale_order_6}
|
||||
!workflow {model: sale.order, action: order_confirm, ref: sale_normal_delivery_charges}
|
||||
-
|
||||
I create Invoice from shipment.
|
||||
-
|
||||
!python {model: stock.invoice.onshipping}: |
|
||||
sale = self.pool.get('sale.order')
|
||||
sale_order = sale.browse(cr, uid, ref("sale.sale_order_6"))
|
||||
sale_order = sale.browse(cr, uid, ref("sale_normal_delivery_charges"))
|
||||
ship_ids = [x.id for x in sale_order.picking_ids]
|
||||
wiz_id = self.create(cr, uid, {'journal_id': ref('account.sales_journal')},
|
||||
{'active_ids': ship_ids, 'active_model': 'stock.picking'})
|
||||
self.create_invoice(cr, uid, [wiz_id], {"active_ids": ship_ids, "active_id": ship_ids[0]})
|
||||
-
|
||||
I print a Delivery Order report.
|
||||
-
|
||||
!python {model: stock.picking}: |
|
||||
import os
|
||||
import openerp.report
|
||||
from openerp import tools
|
||||
sale = self.pool.get('sale.order')
|
||||
sale_order = sale.browse(cr, uid, ref("sale.sale_order_6"))
|
||||
ship_ids = [x.id for x in sale_order.picking_ids]
|
||||
data, format = openerp.report.render_report(cr, uid, ship_ids, 'sale.shipping', {}, {})
|
||||
if tools.config['test_report_directory']:
|
||||
file(os.path.join(tools.config['test_report_directory'], 'delivery-shipping'+format), 'wb+').write(data)
|
||||
Create one more sale order with Free Delivery Charges
|
||||
-
|
||||
!record {model: sale.order, id: sale_free_delivery_charges}:
|
||||
partner_id: base.res_partner_7
|
||||
partner_invoice_id: base.res_partner_address_13
|
||||
partner_shipping_id: base.res_partner_address_13
|
||||
pricelist_id: product.list0
|
||||
order_policy: 'manual'
|
||||
order_line:
|
||||
- name: 'Service on demand'
|
||||
product_id: product.product_product_consultant
|
||||
product_uom_qty: 24
|
||||
product_uos_qty: 24
|
||||
product_uom: product.product_uom_hour
|
||||
price_unit: 75.00
|
||||
order_line:
|
||||
- name: 'On Site Assistance'
|
||||
product_id: product.product_product_2
|
||||
product_uom_qty: 30
|
||||
product_uos_qty: 30
|
||||
product_uom: product.product_uom_hour
|
||||
price_unit: 38.25
|
||||
carrier_id: free_delivery_carrier
|
||||
-
|
||||
I add free delivery cost in Sale order.
|
||||
-
|
||||
!python {model: sale.order}: |
|
||||
self.delivery_set(cr, uid, [ref("sale.sale_order_2")], context=context)
|
||||
self.delivery_set(cr, uid, [ref("sale_free_delivery_charges")], context=context)
|
||||
-
|
||||
I check sale order after added delivery cost.
|
||||
-
|
||||
!python {model: sale.order.line}: |
|
||||
line_ids = self.search(cr, uid, [('order_id','=', ref('sale.sale_order_2')), ('product_id','=', ref('product_product_delivery'))])
|
||||
line_ids = self.search(cr, uid, [('order_id','=', ref('sale_free_delivery_charges')), ('product_id','=', ref('product_product_delivery'))])
|
||||
assert len(line_ids), "Delivery cost is not Added"
|
||||
line_data = self.browse(cr ,uid ,line_ids[0] ,context)
|
||||
assert line_data.price_subtotal == 0, "Delivey cost is not correspond."
|
||||
|
@ -61,5 +91,4 @@
|
|||
{}
|
||||
-
|
||||
!python {model: sale.config.settings}: |
|
||||
self.execute(cr, uid, [ref('default_delivery_policy')], context=context)
|
||||
|
||||
self.execute(cr, uid, [ref('default_delivery_policy')], context=context)
|
|
@ -0,0 +1,25 @@
|
|||
<openerp>
|
||||
<data>
|
||||
<template id="report_shipping2" inherit_id="stock.report_picking">
|
||||
<xpath expr="//div[@name='partner_header']" position="after">
|
||||
<div name="invoice_partner" t-if="o.picking_type_id.code == 'outgoing' and o.sale_id and o.sale_id.partner_invoice_id">
|
||||
Will be invoiced to:
|
||||
<div t-field="o.sale_id.partner_invoice_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//th[@name='td_sched_date_h']" position="after">
|
||||
<th t-if="o.picking_type_id.code == 'outgoing'"><strong>Carrier</strong></th>
|
||||
<th><strong>Weight</strong></th>
|
||||
</xpath>
|
||||
<xpath expr="//td[@name='td_sched_date']" position="after">
|
||||
<td t-if="o.picking_type_id.code == 'outgoing'">
|
||||
<span t-field="o.carrier_id"/>
|
||||
</td>
|
||||
<td>
|
||||
<span t-field="o.weight"/>
|
||||
</td>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
|
@ -10,10 +10,10 @@
|
|||
attrs="{'readonly': [('is_only_child', '=', False)]}"/>
|
||||
<label for="event_ok"/>
|
||||
</div>
|
||||
<div name='ean' position="after">
|
||||
<field name='type' position="after">
|
||||
<field name="event_type_id" attrs="{'invisible': [('event_ok', '=', False)],
|
||||
'readonly': [('is_only_child', '=', False)]}"/>
|
||||
</div>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<field name="res_model">product.product</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context" eval="'{\'default_type\':\'service\',\'default_procure_method\':\'make_to_stock\',\'default_supply_method\':\'buy\',\'default_purchase_ok\':True, \'default_sale_ok\':False, \'default_hr_expense_ok\':True,\'default_categ_id\': ' + str(ref('cat_expense')) +'}'"/>
|
||||
<field name="context" eval="'{\'default_type\':\'service\',\'default_purchase_ok\':True, \'default_sale_ok\':False, \'default_hr_expense_ok\':True,\'default_categ_id\': ' + str(ref('cat_expense')) +'}'"/>
|
||||
<field name="domain">[('hr_expense_ok','=',True)]</field>
|
||||
<field name="view_id" ref="product_expense_installer_tree_view"/>
|
||||
<field name="help">Define one product for each expense type allowed for an employee (travel by car, hostel, restaurant, etc). If you reimburse the employees at a fixed rate, set a cost and a unit of measure on the product. If you reimburse based on real costs, set the cost at 0.00. The user will set the real price when recording his expense sheet.</field>
|
||||
|
|
|
@ -260,8 +260,6 @@ class EscposDriver(Thread):
|
|||
eprint.text(receipt['company']['name'] + '\n')
|
||||
|
||||
eprint.set(align='center',type='b')
|
||||
if check(receipt['shop']['name']):
|
||||
eprint.text(receipt['shop']['name'] + '\n')
|
||||
if check(receipt['company']['contact_address']):
|
||||
eprint.text(receipt['company']['contact_address'] + '\n')
|
||||
if check(receipt['company']['phone']):
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
</record>
|
||||
|
||||
<record id="stock.property_stock_account_input_categ" model="ir.property">
|
||||
<field name="fields_id" search="[('model','=','product.category'),('name','=','property_stock_account_input_categ')]"/>
|
||||
<field eval="'account.account,'+str(ref('chart2141_en'))" model="account.account" name="value"/>
|
||||
<field eval="'product.category,'+str(ref('product.product_category_all'))" model="product.category" name="res_id"/>
|
||||
</record>
|
||||
|
||||
<record id="stock.property_stock_account_output_categ" model="ir.property">
|
||||
<field name="fields_id" search="[('model','=','product.category'),('name','=','property_stock_account_output_categ')]"/>
|
||||
<field eval="'account.account,'+str(ref('chart1145_en'))" model="account.account" name="value"/>
|
||||
<field eval="'product.category,'+str(ref('product.product_category_all'))" model="product.category" name="res_id"/>
|
||||
</record>
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
</record>
|
||||
|
||||
<record id="stock.property_stock_account_input_categ" model="ir.property">
|
||||
<field name="fields_id" search="[('model','=','product.category'),('name','=','property_stock_account_input_categ')]"/>
|
||||
<field eval="'account.account,'+str(ref('chart2171_fr'))" model="account.account" name="value"/>
|
||||
<field eval="'product.category,'+str(ref('product.product_category_all'))" model="product.category" name="res_id"/>
|
||||
</record>
|
||||
</record>
|
||||
|
||||
<record id="stock.property_stock_account_output_categ" model="ir.property">
|
||||
<field name="fields_id" search="[('model','=','product.category'),('name','=','property_stock_account_output_categ')]"/>
|
||||
<field eval="'account.account,'+str(ref('chart1145_fr'))" model="account.account" name="value"/>
|
||||
<field eval="'product.category,'+str(ref('product.product_category_all'))" model="product.category" name="res_id"/>
|
||||
</record>
|
||||
|
|
|
@ -28,8 +28,8 @@
|
|||
'category': 'Manufacturing',
|
||||
'sequence': 18,
|
||||
'summary': 'Manufacturing Orders, Bill of Materials, Routing',
|
||||
'images': ['images/bill_of_materials.jpeg', 'images/manufacturing_order.jpeg', 'images/planning_manufacturing_order.jpeg', 'images/manufacturing_analysis.jpeg','images/routings.jpeg','images/work_centers.jpeg'],
|
||||
'depends': ['product','procurement', 'stock', 'resource', 'purchase', 'report'],
|
||||
'images': ['images/bill_of_materials.jpeg', 'images/manufacturing_order.jpeg', 'images/planning_manufacturing_order.jpeg', 'images/manufacturing_analysis.jpeg', 'images/routings.jpeg','images/work_centers.jpeg'],
|
||||
'depends': ['product', 'procurement', 'stock_account', 'resource', 'report'],
|
||||
'description': """
|
||||
Manage the Manufacturing process in OpenERP
|
||||
===========================================
|
||||
|
@ -62,12 +62,12 @@ Dashboard / Reports for MRP will include:
|
|||
'wizard/change_production_qty_view.xml',
|
||||
'wizard/mrp_price_view.xml',
|
||||
'wizard/mrp_workcenter_load_view.xml',
|
||||
'wizard/stock_move_view.xml',
|
||||
'mrp_view.xml',
|
||||
'mrp_report.xml',
|
||||
'company_view.xml',
|
||||
'report/mrp_report_view.xml',
|
||||
'res_config_view.xml',
|
||||
|
||||
'views/report_mrporder.xml',
|
||||
'views/report_mrpbomstructure.xml',
|
||||
],
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
MRP
|
||||
|
||||
In this chapter, we are going to explain briefly how to use mrp in its simplest form (without the mrp operations) and the impact on stock. We suppose you know the terms used in the warehouse documentation.
|
||||
|
||||
Order flow
|
||||
**********
|
||||
|
||||
An order can be created manually or through the creation of a procurement.
|
||||
|
||||
Important for stock management are the raw materials location and finished products location. Creating a manufacturing order manually, this is by default equal to the location of the standard warehouse. In case of a manufacturing order from a procurement, it will have the source location of the applied procurement rule as both the raw materials and finished products location.
|
||||
|
||||
For a draft manufacturing order, you can change the products to consume, but not anything else. The consumed products tab and the finished products tab will give the progress of the raw materials and finished products consumed/produced. A left table described what needs to be produced and the right table what has been produced/consumed. Behind the scenes, these are stock moves from the raw materials location to the production location (virtual) or from the production location (virtual) to the finished products location.
|
||||
|
||||
First, the system will calculate the scheduled products from the bill of material and the quantity necessary. These will be added to the scheduled products (third tab) and to the products to consume upon confirmation of the manufacturing order.
|
||||
|
||||
The manufacturing order becomes ready to produce when the different moves are assigned (ready to transfer). It is also possible to force the reservation (not recommended).
|
||||
|
||||
Then you can also Mark as Started.
|
||||
|
||||
When you click Produce, a wizard will be opened, where you can enter the quantity produced. Based upon the quantity of finished goods produced, OpenERP will give you the theoretical amount that should still be produced. The theoretical amount is actualy the ('percentage of finished products after this production' * scheduled amount of raw materials) - raw materials consumed.
|
||||
|
||||
The advantage to the wizard (instead of the arrows in v7) is in case of traceability. Actually, if the raw materials and the finished goods require traceability, you should be able to specify for which finished good the raw material has been used. (requires lot for finished product and raw material) That way, it is possible to do the full traceability in production.
|
||||
|
||||
It is possible also to cancel a production order. The products consumed/produced until then, will stay, but every move left, will be cancelled.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -20,17 +20,41 @@
|
|||
##############################################################################
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.osv import fields, osv, orm
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.tools import float_compare
|
||||
from openerp.tools.translate import _
|
||||
from openerp import tools, SUPERUSER_ID
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.addons.product import _common
|
||||
|
||||
|
||||
class mrp_property_group(osv.osv):
|
||||
"""
|
||||
Group of mrp properties.
|
||||
"""
|
||||
_name = 'mrp.property.group'
|
||||
_description = 'Property Group'
|
||||
_columns = {
|
||||
'name': fields.char('Property Group', size=64, required=True),
|
||||
'description': fields.text('Description'),
|
||||
}
|
||||
|
||||
class mrp_property(osv.osv):
|
||||
"""
|
||||
Properties of mrp.
|
||||
"""
|
||||
_name = 'mrp.property'
|
||||
_description = 'Property'
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
|
||||
'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
|
||||
'description': fields.text('Description'),
|
||||
}
|
||||
_defaults = {
|
||||
'composition': lambda *a: 'min',
|
||||
}
|
||||
#----------------------------------------------------------
|
||||
# Work Centers
|
||||
#----------------------------------------------------------
|
||||
|
@ -159,7 +183,7 @@ class mrp_bom(osv.osv):
|
|||
result[bom.id] = []
|
||||
if bom.bom_lines:
|
||||
continue
|
||||
ok = ((name=='child_complete_ids') and (bom.product_id.supply_method=='produce'))
|
||||
ok = ((name=='child_complete_ids'))
|
||||
if (bom.type=='phantom' or ok):
|
||||
sids = bom_obj.search(cr, uid, [('bom_id','=',False),('product_id','=',bom.product_id.id)])
|
||||
if sids:
|
||||
|
@ -168,35 +192,14 @@ class mrp_bom(osv.osv):
|
|||
|
||||
return result
|
||||
|
||||
def _compute_type(self, cr, uid, ids, field_name, arg, context=None):
|
||||
""" Sets particular method for the selected bom type.
|
||||
@param field_name: Name of the field
|
||||
@param arg: User defined argument
|
||||
@return: Dictionary of values
|
||||
"""
|
||||
res = dict.fromkeys(ids, False)
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
if line.type == 'phantom' and not line.bom_id:
|
||||
res[line.id] = 'set'
|
||||
continue
|
||||
if line.bom_lines or line.type == 'phantom':
|
||||
continue
|
||||
if line.product_id.supply_method == 'produce':
|
||||
if line.product_id.procure_method == 'make_to_stock':
|
||||
res[line.id] = 'stock'
|
||||
else:
|
||||
res[line.id] = 'order'
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64),
|
||||
'code': fields.char('Reference', size=16),
|
||||
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the bills of material without removing it."),
|
||||
'type': fields.selection([('normal','Normal BoM'),('phantom','Sets / Phantom')], 'BoM Type', required=True,
|
||||
'type': fields.selection([('normal', 'Normal BoM'), ('phantom', 'Sets / Phantom')], 'BoM Type', required=True,
|
||||
help= "If a by-product is used in several products, it can be useful to create its own BoM. "\
|
||||
"Though if you don't want separated production orders for this by-product, select Set/Phantom as BoM type. "\
|
||||
"If a Phantom BoM is used for a root product, it will be sold and shipped as a set of components, instead of being produced."),
|
||||
'method': fields.function(_compute_type, string='Method', type='selection', selection=[('',''),('stock','On Stock'),('order','On Order'),('set','Set / Pack')]),
|
||||
'date_start': fields.date('Valid From', help="Validity of this BoM or component. Keep empty if it's always valid."),
|
||||
'date_stop': fields.date('Valid Until', help="Validity of this BoM or component. Keep empty if it's always valid."),
|
||||
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of bills of material."),
|
||||
|
@ -211,9 +214,9 @@ class mrp_bom(osv.osv):
|
|||
'bom_lines': fields.one2many('mrp.bom', 'bom_id', 'BoM Lines'),
|
||||
'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True),
|
||||
'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
|
||||
'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id','property_id', 'Properties'),
|
||||
'property_ids': fields.many2many('mrp.property', 'mrp_bom_property_rel', 'bom_id', 'property_id', 'Properties'),
|
||||
'child_complete_ids': fields.function(_child_compute, relation='mrp.bom', string="BoM Hierarchy", type='many2many'),
|
||||
'company_id': fields.many2one('res.company','Company',required=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
}
|
||||
_defaults = {
|
||||
'active': lambda *a: 1,
|
||||
|
@ -221,20 +224,20 @@ class mrp_bom(osv.osv):
|
|||
'product_qty': lambda *a: 1.0,
|
||||
'product_rounding': lambda *a: 0.0,
|
||||
'type': lambda *a: 'normal',
|
||||
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
|
||||
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.bom', context=c),
|
||||
}
|
||||
_order = "sequence"
|
||||
_parent_name = "bom_id"
|
||||
_sql_constraints = [
|
||||
('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
|
||||
('bom_qty_zero', 'CHECK (product_qty>0)', 'All product quantities must be greater than 0.\n' \
|
||||
'You should install the mrp_byproduct module if you want to manage extra products on BoMs !'),
|
||||
]
|
||||
|
||||
def _check_recursion(self, cr, uid, ids, context=None):
|
||||
level = 100
|
||||
while len(ids):
|
||||
cr.execute('select distinct bom_id from mrp_bom where id IN %s',(tuple(ids),))
|
||||
ids = filter(None, map(lambda x:x[0], cr.fetchall()))
|
||||
cr.execute('select distinct bom_id from mrp_bom where id IN %s', (tuple(ids),))
|
||||
ids = filter(None, map(lambda x: x[0], cr.fetchall()))
|
||||
if not level:
|
||||
return False
|
||||
level -= 1
|
||||
|
@ -276,7 +279,7 @@ class mrp_bom(osv.osv):
|
|||
return res
|
||||
|
||||
def onchange_uom(self, cr, uid, ids, product_id, product_uom, context=None):
|
||||
res = {'value':{}}
|
||||
res = {'value': {}}
|
||||
if not product_uom or not product_id:
|
||||
return res
|
||||
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
|
@ -296,8 +299,8 @@ class mrp_bom(osv.osv):
|
|||
if properties is None:
|
||||
properties = []
|
||||
domain = [('product_id', '=', product_id), ('bom_id', '=', False),
|
||||
'|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
|
||||
'|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
|
||||
ids = self.search(cr, uid, domain)
|
||||
max_prop = 0
|
||||
result = False
|
||||
|
@ -333,7 +336,7 @@ class mrp_bom(osv.osv):
|
|||
newbom = self._bom_find(cr, uid, bom.product_id.id, bom.product_uom.id, properties)
|
||||
|
||||
if newbom:
|
||||
res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor*bom.product_qty, properties, addthis=True, level=level+10)
|
||||
res = self._bom_explode(cr, uid, self.browse(cr, uid, [newbom])[0], factor * bom.product_qty, properties, addthis=True, level=level + 10)
|
||||
result = result + res[0]
|
||||
result2 = result2 + res[1]
|
||||
phantom = True
|
||||
|
@ -341,8 +344,7 @@ class mrp_bom(osv.osv):
|
|||
phantom = False
|
||||
if not phantom:
|
||||
if addthis and not bom.bom_lines:
|
||||
result.append(
|
||||
{
|
||||
result.append({
|
||||
'name': bom.product_id.name,
|
||||
'product_id': bom.product_id.id,
|
||||
'product_qty': bom.product_qty * factor,
|
||||
|
@ -358,14 +360,14 @@ class mrp_bom(osv.osv):
|
|||
mult = (d + (m and 1.0 or 0.0))
|
||||
cycle = mult * wc_use.cycle_nbr
|
||||
result2.append({
|
||||
'name': tools.ustr(wc_use.name) + ' - ' + tools.ustr(bom.product_id.name),
|
||||
'name': tools.ustr(wc_use.name) + ' - ' + tools.ustr(bom.product_id.name),
|
||||
'workcenter_id': wc.id,
|
||||
'sequence': level+(wc_use.sequence or 0),
|
||||
'sequence': level + (wc_use.sequence or 0),
|
||||
'cycle': cycle,
|
||||
'hour': float(wc_use.hour_nbr*mult + ((wc.time_start or 0.0)+(wc.time_stop or 0.0)+cycle*(wc.time_cycle or 0.0)) * (wc.time_efficiency or 1.0)),
|
||||
'hour': float(wc_use.hour_nbr * mult + ((wc.time_start or 0.0) + (wc.time_stop or 0.0) + cycle * (wc.time_cycle or 0.0)) * (wc.time_efficiency or 1.0)),
|
||||
})
|
||||
for bom2 in bom.bom_lines:
|
||||
res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level+10)
|
||||
res = self._bom_explode(cr, uid, bom2, factor, properties, addthis=True, level=level + 10)
|
||||
result = result + res[0]
|
||||
result2 = result2 + res[1]
|
||||
return result, result2
|
||||
|
@ -378,21 +380,13 @@ class mrp_bom(osv.osv):
|
|||
return super(mrp_bom, self).copy_data(cr, uid, id, default, context=context)
|
||||
|
||||
|
||||
def rounding(f, r):
|
||||
# TODO for trunk: log deprecation warning
|
||||
# _logger.warning("Deprecated rounding method, please use tools.float_round to round floats.")
|
||||
import math
|
||||
if not r:
|
||||
return f
|
||||
return math.ceil(f / r) * r
|
||||
|
||||
class mrp_production(osv.osv):
|
||||
"""
|
||||
Production Orders / Manufacturing Orders
|
||||
"""
|
||||
_name = 'mrp.production'
|
||||
_description = 'Manufacturing Order'
|
||||
_date_name = 'date_planned'
|
||||
_date_name = 'date_planned'
|
||||
_inherit = ['mail.thread', 'ir.needaction_mixin']
|
||||
|
||||
def _production_calc(self, cr, uid, ids, prop, unknow_none, context=None):
|
||||
|
@ -440,15 +434,32 @@ class mrp_production(osv.osv):
|
|||
result[mrp_production.id] = done / mrp_production.product_qty * 100
|
||||
return result
|
||||
|
||||
def _moves_assigned(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Test whether all the consume lines are assigned """
|
||||
res = {}
|
||||
for production in self.browse(cr, uid, ids, context=context):
|
||||
res[production.id] = True
|
||||
states = [x.state != 'assigned' for x in production.move_lines if x]
|
||||
if any(states) or len(states) == 0:
|
||||
res[production.id] = False
|
||||
return res
|
||||
|
||||
def _mrp_from_move(self, cr, uid, ids, context=None):
|
||||
""" Return mrp"""
|
||||
res = []
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
res += self.pool.get("mrp.production").search(cr, uid, [('move_lines', 'in', move.id)], context=context)
|
||||
return res
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'origin': fields.char('Source Document', size=64, readonly=True, states={'draft': [('readonly', False)]},
|
||||
help="Reference of the document that generated this production order request."),
|
||||
'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority',
|
||||
'priority': fields.selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority',
|
||||
select=True, readonly=True, states=dict.fromkeys(['draft', 'confirmed'], [('readonly', False)])),
|
||||
|
||||
'product_id': fields.many2one('product.product', 'Product', required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uos_qty': fields.float('Product UoS Quantity', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uos': fields.many2one('product.uom', 'Product UoS', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
|
@ -456,35 +467,33 @@ class mrp_production(osv.osv):
|
|||
string='Production progress'),
|
||||
|
||||
'location_src_id': fields.many2one('stock.location', 'Raw Materials Location', required=True,
|
||||
readonly=True, states={'draft':[('readonly',False)]},
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
help="Location where the system will look for components."),
|
||||
'location_dest_id': fields.many2one('stock.location', 'Finished Products Location', required=True,
|
||||
readonly=True, states={'draft':[('readonly',False)]},
|
||||
readonly=True, states={'draft': [('readonly', False)]},
|
||||
help="Location where the system will stock the finished products."),
|
||||
'date_planned': fields.datetime('Scheduled Date', required=True, select=1, readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'date_planned': fields.datetime('Scheduled Date', required=True, select=1, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'date_start': fields.datetime('Start Date', select=True, readonly=True),
|
||||
'date_finished': fields.datetime('End Date', select=True, readonly=True),
|
||||
'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id','=',False)], readonly=True, states={'draft':[('readonly',False)]},
|
||||
'bom_id': fields.many2one('mrp.bom', 'Bill of Material', domain=[('bom_id', '=', False)], readonly=True, states={'draft': [('readonly', False)]},
|
||||
help="Bill of Materials allow you to define the list of required raw materials to make a finished product."),
|
||||
'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null', readonly=True, states={'draft':[('readonly',False)]},
|
||||
'routing_id': fields.many2one('mrp.routing', string='Routing', on_delete='set null', readonly=True, states={'draft': [('readonly', False)]},
|
||||
help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production plannification."),
|
||||
'picking_id': fields.many2one('stock.picking', 'Picking List', readonly=True, ondelete="restrict",
|
||||
help="This is the Internal Picking List that brings the finished product to the production plan"),
|
||||
'move_prod_id': fields.many2one('stock.move', 'Product Move', readonly=True),
|
||||
'move_lines': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Products to Consume',
|
||||
domain=[('state','not in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'move_lines2': fields.many2many('stock.move', 'mrp_production_move_ids', 'production_id', 'move_id', 'Consumed Products',
|
||||
domain=[('state','in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'move_lines': fields.one2many('stock.move', 'raw_material_production_id', 'Products to Consume',
|
||||
domain=[('state', 'not in', ('done', 'cancel'))], readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'move_lines2': fields.one2many('stock.move', 'raw_material_production_id', 'Consumed Products',
|
||||
domain=[('state', 'in', ('done', 'cancel'))], readonly=True),
|
||||
'move_created_ids': fields.one2many('stock.move', 'production_id', 'Products to Produce',
|
||||
domain=[('state','not in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
|
||||
domain=[('state', 'not in', ('done', 'cancel'))], readonly=True),
|
||||
'move_created_ids2': fields.one2many('stock.move', 'production_id', 'Produced Products',
|
||||
domain=[('state','in', ('done', 'cancel'))], readonly=True, states={'draft':[('readonly',False)]}),
|
||||
domain=[('state', 'in', ('done', 'cancel'))], readonly=True),
|
||||
'product_lines': fields.one2many('mrp.production.product.line', 'production_id', 'Scheduled goods',
|
||||
readonly=True, states={'draft':[('readonly',False)]}),
|
||||
readonly=True),
|
||||
'workcenter_lines': fields.one2many('mrp.production.workcenter.line', 'production_id', 'Work Centers Utilisation',
|
||||
readonly=True, states={'draft':[('readonly',False)]}),
|
||||
readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'state': fields.selection(
|
||||
[('draft', 'New'), ('cancel', 'Cancelled'), ('picking_except', 'Picking Exception'), ('confirmed', 'Awaiting Raw Materials'),
|
||||
[('draft', 'New'), ('cancel', 'Cancelled'), ('confirmed', 'Awaiting Raw Materials'),
|
||||
('ready', 'Ready to Produce'), ('in_production', 'Production Started'), ('done', 'Done')],
|
||||
string='Status', readonly=True,
|
||||
track_visibility='onchange',
|
||||
|
@ -496,24 +505,28 @@ class mrp_production(osv.osv):
|
|||
When the production is over, the status is set to 'Done'."),
|
||||
'hour_total': fields.function(_production_calc, type='float', string='Total Hours', multi='workorder', store=True),
|
||||
'cycle_total': fields.function(_production_calc, type='float', string='Total Cycles', multi='workorder', store=True),
|
||||
'user_id':fields.many2one('res.users', 'Responsible'),
|
||||
'company_id': fields.many2one('res.company','Company',required=True),
|
||||
'user_id': fields.many2one('res.users', 'Responsible'),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
'ready_production': fields.function(_moves_assigned, type='boolean', store={'stock.move': (_mrp_from_move, ['state'], 10)}),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'priority': lambda *a: '1',
|
||||
'state': lambda *a: 'draft',
|
||||
'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'product_qty': lambda *a: 1.0,
|
||||
'product_qty': lambda *a: 1.0,
|
||||
'user_id': lambda self, cr, uid, c: uid,
|
||||
'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'mrp.production') or '/',
|
||||
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
|
||||
'location_src_id': _src_id_default,
|
||||
'location_dest_id': _dest_id_default
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique(name, company_id)', 'Reference must be unique per Company!'),
|
||||
]
|
||||
_order = 'priority desc, date_planned asc';
|
||||
|
||||
_order = 'priority desc, date_planned asc'
|
||||
|
||||
def _check_qty(self, cr, uid, ids, context=None):
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
|
@ -536,13 +549,12 @@ class mrp_production(osv.osv):
|
|||
default = {}
|
||||
default.update({
|
||||
'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
|
||||
'move_lines' : [],
|
||||
'move_lines2' : [],
|
||||
'move_created_ids' : [],
|
||||
'move_created_ids2' : [],
|
||||
'product_lines' : [],
|
||||
'move_prod_id' : False,
|
||||
'picking_id' : False
|
||||
'move_lines': [],
|
||||
'move_lines2': [],
|
||||
'move_created_ids': [],
|
||||
'move_created_ids2': [],
|
||||
'product_lines': [],
|
||||
'move_prod_id': False,
|
||||
})
|
||||
return super(mrp_production, self).copy(cr, uid, id, default, context)
|
||||
|
||||
|
@ -569,7 +581,7 @@ class mrp_production(osv.osv):
|
|||
'product_uom': False,
|
||||
'bom_id': False,
|
||||
'routing_id': False,
|
||||
'product_uos_qty': 0,
|
||||
'product_uos_qty': 0,
|
||||
'product_uos': False
|
||||
}}
|
||||
bom_obj = self.pool.get('mrp.bom')
|
||||
|
@ -580,7 +592,6 @@ class mrp_production(osv.osv):
|
|||
bom_point = bom_obj.browse(cr, uid, bom_id, context=context)
|
||||
routing_id = bom_point.routing_id.id or False
|
||||
product_uom_id = product.uom_id and product.uom_id.id or False
|
||||
product_uos_id = product.uos_id and product.uos_id.id or False
|
||||
result['value'] = {'product_uos_qty': 0, 'product_uos': False, 'product_uom': product_uom_id, 'bom_id': bom_id, 'routing_id': routing_id}
|
||||
if product.uos_id.id:
|
||||
result['value']['product_uos_qty'] = product_qty * product.uos_coeff
|
||||
|
@ -603,18 +614,11 @@ class mrp_production(osv.osv):
|
|||
}
|
||||
return {'value': result}
|
||||
|
||||
def action_picking_except(self, cr, uid, ids):
|
||||
""" Changes the state to Exception.
|
||||
@return: True
|
||||
"""
|
||||
self.write(cr, uid, ids, {'state': 'picking_except'})
|
||||
return True
|
||||
|
||||
|
||||
def _action_compute_lines(self, cr, uid, ids, properties=None, context=None):
|
||||
""" Compute product_lines and workcenter_lines from BoM structure
|
||||
@return: product_lines
|
||||
"""
|
||||
|
||||
if properties is None:
|
||||
properties = []
|
||||
results = []
|
||||
|
@ -622,14 +626,11 @@ class mrp_production(osv.osv):
|
|||
uom_obj = self.pool.get('product.uom')
|
||||
prod_line_obj = self.pool.get('mrp.production.product.line')
|
||||
workcenter_line_obj = self.pool.get('mrp.production.workcenter.line')
|
||||
|
||||
for production in self.browse(cr, uid, ids, context=context):
|
||||
#unlink product_lines
|
||||
prod_line_obj.unlink(cr, SUPERUSER_ID, [line.id for line in production.product_lines], context=context)
|
||||
|
||||
#unlink workcenter_lines
|
||||
workcenter_line_obj.unlink(cr, SUPERUSER_ID, [line.id for line in production.workcenter_lines], context=context)
|
||||
|
||||
# search BoM structure and route
|
||||
bom_point = production.bom_id
|
||||
bom_id = production.bom_id.id
|
||||
|
@ -642,18 +643,17 @@ class mrp_production(osv.osv):
|
|||
|
||||
if not bom_id:
|
||||
raise osv.except_osv(_('Error!'), _("Cannot find a bill of material for this product."))
|
||||
|
||||
|
||||
# get components and workcenter_lines from BoM structure
|
||||
factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id)
|
||||
res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id)
|
||||
results = res[0] # product_lines
|
||||
results2 = res[1] # workcenter_lines
|
||||
|
||||
results = res[0] # product_lines
|
||||
results2 = res[1] # workcenter_lines
|
||||
# reset product_lines in production order
|
||||
for line in results:
|
||||
line['production_id'] = production.id
|
||||
prod_line_obj.create(cr, uid, line)
|
||||
|
||||
|
||||
#reset workcenter_lines in production order
|
||||
for line in results2:
|
||||
line['production_id'] = production.id
|
||||
|
@ -675,14 +675,15 @@ class mrp_production(osv.osv):
|
|||
context = {}
|
||||
move_obj = self.pool.get('stock.move')
|
||||
for production in self.browse(cr, uid, ids, context=context):
|
||||
if production.state == 'confirmed' and production.picking_id.state not in ('draft', 'cancel'):
|
||||
raise osv.except_osv(
|
||||
_('Cannot cancel manufacturing order!'),
|
||||
_('You must first cancel related internal picking attached to this manufacturing order.'))
|
||||
if production.move_created_ids:
|
||||
move_obj.action_cancel(cr, uid, [x.id for x in production.move_created_ids])
|
||||
move_obj.action_cancel(cr, uid, [x.id for x in production.move_lines])
|
||||
self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
# Put related procurements in exception
|
||||
proc_obj = self.pool.get("procurement.order")
|
||||
procs = proc_obj.search(cr, uid, [('production_id', 'in', ids)], context=context)
|
||||
if procs:
|
||||
proc_obj.write(cr, uid, procs, {'state': 'exception'}, context=context)
|
||||
return True
|
||||
|
||||
def action_ready(self, cr, uid, ids, context=None):
|
||||
|
@ -694,10 +695,8 @@ class mrp_production(osv.osv):
|
|||
|
||||
for production in self.browse(cr, uid, ids, context=context):
|
||||
if not production.move_created_ids:
|
||||
produce_move_id = self._make_production_produce_line(cr, uid, production, context=context)
|
||||
for scheduled in production.product_lines:
|
||||
self._make_production_line_procurement(cr, uid, scheduled, False, context=context)
|
||||
|
||||
self._make_production_produce_line(cr, uid, production, context=context)
|
||||
|
||||
if production.move_prod_id and production.move_prod_id.location_id.id != production.location_dest_id.id:
|
||||
move_obj.write(cr, uid, [production.move_prod_id.id],
|
||||
{'location_id': production.location_dest_id.id})
|
||||
|
@ -710,6 +709,10 @@ class mrp_production(osv.osv):
|
|||
for production in self.browse(cr, uid, ids):
|
||||
self._costs_generate(cr, uid, production)
|
||||
write_res = self.write(cr, uid, ids, {'state': 'done', 'date_finished': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
# Check related procurements
|
||||
proc_obj = self.pool.get("procurement.order")
|
||||
procs = proc_obj.search(cr, uid, [('production_id', 'in', ids)], context=context)
|
||||
proc_obj.check(cr, uid, procs, context=context)
|
||||
return write_res
|
||||
|
||||
def test_production_done(self, cr, uid, ids):
|
||||
|
@ -736,7 +739,92 @@ class mrp_production(osv.osv):
|
|||
"""
|
||||
return 1
|
||||
|
||||
def action_produce(self, cr, uid, production_id, production_qty, production_mode, context=None):
|
||||
def _get_produced_qty(self, cr, uid, production, context=None):
|
||||
''' returns the produced quantity of product 'production.product_id' for the given production, in the product UoM
|
||||
'''
|
||||
produced_qty = 0
|
||||
for produced_product in production.move_created_ids2:
|
||||
if (produced_product.scrapped) or (produced_product.product_id.id != production.product_id.id):
|
||||
continue
|
||||
produced_qty += produced_product.product_qty
|
||||
return produced_qty
|
||||
|
||||
def _get_consumed_data(self, cr, uid, production, context=None):
|
||||
''' returns a dictionary containing for each raw material of the given production, its quantity already consumed (in the raw material UoM)
|
||||
'''
|
||||
consumed_data = {}
|
||||
# Calculate already consumed qtys
|
||||
for consumed in production.move_lines2:
|
||||
if consumed.scrapped:
|
||||
continue
|
||||
if not consumed_data.get(consumed.product_id.id, False):
|
||||
consumed_data[consumed.product_id.id] = 0
|
||||
consumed_data[consumed.product_id.id] += consumed.product_qty
|
||||
return consumed_data
|
||||
|
||||
def _calculate_qty(self, cr, uid, production, product_qty=0.0, context=None):
|
||||
"""
|
||||
Calculates the quantity still needed to produce an extra number of products
|
||||
"""
|
||||
quant_obj = self.pool.get("stock.quant")
|
||||
produced_qty = self._get_produced_qty(cr, uid, production, context=context)
|
||||
consumed_data = self._get_consumed_data(cr, uid, production, context=context)
|
||||
|
||||
#In case no product_qty is given, take the remaining qty to produce for the given production
|
||||
if not product_qty:
|
||||
product_qty = production.product_qty - produced_qty
|
||||
|
||||
dicts = {}
|
||||
# Find product qty to be consumed and consume it
|
||||
for scheduled in production.product_lines:
|
||||
consumed_qty = consumed_data.get(scheduled.product_id.id, 0.0)
|
||||
# qty available for consume and produce
|
||||
qty_avail = scheduled.product_qty - consumed_qty
|
||||
if qty_avail <= 0.0:
|
||||
# there will be nothing to consume for this raw material
|
||||
continue
|
||||
|
||||
if not dicts.get(scheduled.product_id.id):
|
||||
dicts[scheduled.product_id.id] = {}
|
||||
|
||||
# total qty of consumed product we need after this consumption
|
||||
total_consume = ((product_qty + produced_qty) * scheduled.product_qty / production.product_qty)
|
||||
qty = total_consume - consumed_qty
|
||||
|
||||
# Search for quants related to this related move
|
||||
for move in production.move_lines:
|
||||
if qty <= 0.0:
|
||||
break
|
||||
if move.product_id.id != scheduled.product_id.id:
|
||||
continue
|
||||
product_id = scheduled.product_id.id
|
||||
|
||||
q = min(move.product_qty, qty)
|
||||
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, scheduled.product_id, q, domain=[('qty', '>', 0.0)],
|
||||
prefered_domain_list=[[('reservation_id', '=', move.id)], [('reservation_id', '=', False)]], context=context)
|
||||
for quant, quant_qty in quants:
|
||||
if quant:
|
||||
lot_id = quant.lot_id.id
|
||||
if not product_id in dicts.keys():
|
||||
dicts[product_id] = {lot_id: quant_qty}
|
||||
elif lot_id in dicts[product_id].keys():
|
||||
dicts[product_id][lot_id] += quant_qty
|
||||
else:
|
||||
dicts[product_id][lot_id] = quant_qty
|
||||
qty -= quant_qty
|
||||
if qty > 0:
|
||||
if dicts[product_id].get(False):
|
||||
dicts[product_id][False] += qty
|
||||
else:
|
||||
dicts[product_id][False] = qty
|
||||
|
||||
consume_lines = []
|
||||
for prod in dicts.keys():
|
||||
for lot, qty in dicts[prod].items():
|
||||
consume_lines.append({'product_id': prod, 'product_qty': qty, 'lot_id': lot})
|
||||
return consume_lines
|
||||
|
||||
def action_produce(self, cr, uid, production_id, production_qty, production_mode, wiz=False, context=None):
|
||||
""" To produce final product based on production mode (consume/consume&produce).
|
||||
If Production mode is consume, all stock move lines of raw materials will be done/consumed.
|
||||
If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
|
||||
|
@ -744,58 +832,18 @@ class mrp_production(osv.osv):
|
|||
@param production_id: the ID of mrp.production object
|
||||
@param production_qty: specify qty to produce
|
||||
@param production_mode: specify production mode (consume/consume&produce).
|
||||
@param wiz: the mrp produce product wizard, which will tell the amount of consumed products needed
|
||||
@return: True
|
||||
"""
|
||||
stock_mov_obj = self.pool.get('stock.move')
|
||||
production = self.browse(cr, uid, production_id, context=context)
|
||||
|
||||
if not production.move_lines and production.state == 'ready':
|
||||
# trigger workflow if not products to consume (eg: services)
|
||||
self.signal_button_produce(cr, uid, [production_id])
|
||||
|
||||
produced_qty = 0
|
||||
for produced_product in production.move_created_ids2:
|
||||
if (produced_product.scrapped) or (produced_product.product_id.id != production.product_id.id):
|
||||
continue
|
||||
produced_qty += produced_product.product_qty
|
||||
if production_mode in ['consume','consume_produce']:
|
||||
consumed_data = {}
|
||||
|
||||
# Calculate already consumed qtys
|
||||
for consumed in production.move_lines2:
|
||||
if consumed.scrapped:
|
||||
continue
|
||||
if not consumed_data.get(consumed.product_id.id, False):
|
||||
consumed_data[consumed.product_id.id] = 0
|
||||
consumed_data[consumed.product_id.id] += consumed.product_qty
|
||||
|
||||
# Find product qty to be consumed and consume it
|
||||
for scheduled in production.product_lines:
|
||||
|
||||
# total qty of consumed product we need after this consumption
|
||||
total_consume = ((production_qty + produced_qty) * scheduled.product_qty / production.product_qty)
|
||||
|
||||
# qty available for consume and produce
|
||||
qty_avail = scheduled.product_qty - consumed_data.get(scheduled.product_id.id, 0.0)
|
||||
|
||||
if float_compare(qty_avail, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
|
||||
# there will be nothing to consume for this raw material
|
||||
continue
|
||||
|
||||
raw_product = [move for move in production.move_lines if move.product_id.id==scheduled.product_id.id]
|
||||
if raw_product:
|
||||
# qtys we have to consume
|
||||
qty = total_consume - consumed_data.get(scheduled.product_id.id, 0.0)
|
||||
if float_compare(qty, qty_avail, precision_rounding=scheduled.product_id.uom_id.rounding) == 1:
|
||||
# if qtys we have to consume is more than qtys available to consume
|
||||
prod_name = scheduled.product_id.name_get()[0][1]
|
||||
raise osv.except_osv(_('Warning!'), _('You are going to consume total %s quantities of "%s".\nBut you can only consume up to total %s quantities.') % (qty, prod_name, qty_avail))
|
||||
if float_compare(qty, 0, precision_rounding=scheduled.product_id.uom_id.rounding) <= 0:
|
||||
# we already have more qtys consumed than we need
|
||||
continue
|
||||
|
||||
raw_product[0].action_consume(qty, raw_product[0].location_id.id, context=context)
|
||||
produced_qty = self._get_produced_qty(cr, uid, production, context=context)
|
||||
|
||||
main_production_move = False
|
||||
if production_mode == 'consume_produce':
|
||||
# To produce remaining qty of final product
|
||||
#vals = {'state':'confirmed'}
|
||||
|
@ -814,21 +862,41 @@ class mrp_production(osv.osv):
|
|||
produced_qty = produced_products.get(produce_product.product_id.id, 0)
|
||||
subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context)
|
||||
rest_qty = (subproduct_factor * production.product_qty) - produced_qty
|
||||
|
||||
if rest_qty < (subproduct_factor * production_qty):
|
||||
if float_compare(rest_qty, (subproduct_factor * production_qty), precision_rounding=produce_product.product_id.uom_id.rounding) < 0:
|
||||
prod_name = produce_product.product_id.name_get()[0][1]
|
||||
raise osv.except_osv(_('Warning!'), _('You are going to produce total %s quantities of "%s".\nBut you can only produce up to total %s quantities.') % ((subproduct_factor * production_qty), prod_name, rest_qty))
|
||||
if rest_qty > 0 :
|
||||
stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), context=context)
|
||||
if float_compare(rest_qty, 0, precision_rounding=produce_product.product_id.uom_id.rounding) > 0:
|
||||
lot_id = False
|
||||
if wiz:
|
||||
lot_id = wiz.lot_id.id
|
||||
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
|
||||
stock_mov_obj.write(cr, uid, new_moves, {'production_id': production_id}, context=context)
|
||||
if produce_product.product_id.id == production.product_id.id and new_moves:
|
||||
main_production_move = new_moves[0]
|
||||
|
||||
if production_mode in ['consume', 'consume_produce']:
|
||||
if wiz:
|
||||
consume_lines = []
|
||||
for cons in wiz.consume_lines:
|
||||
consume_lines.append({'product_id': cons.product_id.id, 'lot_id': cons.lot_id.id, 'product_qty': cons.product_qty})
|
||||
else:
|
||||
consume_lines = self._calculate_qty(cr, uid, production, production_qty, context=context)
|
||||
for consume in consume_lines:
|
||||
remaining_qty = consume['product_qty']
|
||||
for raw_material_line in production.move_lines:
|
||||
if remaining_qty <= 0:
|
||||
break
|
||||
if consume['product_id'] != raw_material_line.product_id.id:
|
||||
continue
|
||||
consumed_qty = min(remaining_qty, raw_material_line.product_qty)
|
||||
stock_mov_obj.action_consume(cr, uid, [raw_material_line.id], consumed_qty, raw_material_line.location_id.id, restrict_lot_id=consume['lot_id'], consumed_for=main_production_move, context=context)
|
||||
remaining_qty -= consumed_qty
|
||||
if remaining_qty:
|
||||
#consumed more in wizard than previously planned
|
||||
product = self.pool.get('product.product').browse(cr, uid, consume['product_id'], context=context)
|
||||
extra_move_id = self._make_consume_line_from_data(cr, uid, production, product, product.uom_id.id, remaining_qty, False, 0, context=context)
|
||||
stock_mov_obj.action_done(cr, uid, [extra_move_id], context=context)
|
||||
|
||||
for raw_product in production.move_lines2:
|
||||
new_parent_ids = []
|
||||
parent_move_ids = [x.id for x in raw_product.move_history_ids]
|
||||
for final_product in production.move_created_ids2:
|
||||
if final_product.id not in parent_move_ids:
|
||||
new_parent_ids.append(final_product.id)
|
||||
for new_parent_id in new_parent_ids:
|
||||
stock_mov_obj.write(cr, uid, [raw_product.id], {'move_history_ids': [(4,new_parent_id)]})
|
||||
self.message_post(cr, uid, production_id, body=_("%s produced") % self._description, context=context)
|
||||
self.signal_button_produce_done(cr, uid, [production_id])
|
||||
return True
|
||||
|
@ -861,14 +929,14 @@ class mrp_production(osv.osv):
|
|||
'product_id': wc.product_id.id,
|
||||
'unit_amount': wc_line.hour,
|
||||
'product_uom_id': wc.product_id and wc.product_id.uom_id.id or False
|
||||
} )
|
||||
})
|
||||
# Cost per cycle
|
||||
value = wc_line.cycle * wc.costs_cycle
|
||||
account = wc.costs_cycle_account_id.id
|
||||
if value and account:
|
||||
amount += value
|
||||
analytic_line_obj.create(cr, SUPERUSER_ID, {
|
||||
'name': wc_line.name+' (C)',
|
||||
'name': wc_line.name + ' (C)',
|
||||
'amount': value,
|
||||
'account_id': account,
|
||||
'general_account_id': wc.costs_general_account_id.id,
|
||||
|
@ -877,7 +945,7 @@ class mrp_production(osv.osv):
|
|||
'product_id': wc.product_id.id,
|
||||
'unit_amount': wc_line.cycle,
|
||||
'product_uom_id': wc.product_id and wc.product_id.uom_id.id or False
|
||||
} )
|
||||
})
|
||||
return amount
|
||||
|
||||
def action_in_production(self, cr, uid, ids, context=None):
|
||||
|
@ -886,109 +954,20 @@ class mrp_production(osv.osv):
|
|||
"""
|
||||
return self.write(cr, uid, ids, {'state': 'in_production', 'date_start': time.strftime('%Y-%m-%d %H:%M:%S')})
|
||||
|
||||
def test_if_product(self, cr, uid, ids):
|
||||
"""
|
||||
@return: True or False
|
||||
"""
|
||||
res = True
|
||||
for production in self.browse(cr, uid, ids):
|
||||
boms = self._action_compute_lines(cr, uid, [production.id])
|
||||
res = False
|
||||
for bom in boms:
|
||||
product = self.pool.get('product.product').browse(cr, uid, bom['product_id'])
|
||||
if product.type in ('product', 'consu'):
|
||||
res = True
|
||||
def consume_lines_get(self, cr, uid, ids, *args):
|
||||
res = []
|
||||
for order in self.browse(cr, uid, ids, context={}):
|
||||
res += [x.id for x in order.move_lines]
|
||||
return res
|
||||
|
||||
def _get_auto_picking(self, cr, uid, production):
|
||||
return True
|
||||
def test_ready(self, cr, uid, ids):
|
||||
res = False
|
||||
for production in self.browse(cr, uid, ids):
|
||||
if production.ready_production:
|
||||
res = True
|
||||
return res
|
||||
|
||||
|
||||
def _hook_create_post_procurement(self, cr, uid, production, procurement_id, context=None):
|
||||
return True
|
||||
|
||||
def _make_production_line_procurement(self, cr, uid, production_line, shipment_move_id, context=None):
|
||||
procurement_order = self.pool.get('procurement.order')
|
||||
production = production_line.production_id
|
||||
location_id = production.location_src_id.id
|
||||
date_planned = production.date_planned
|
||||
procurement_name = (production.origin or '').split(':')[0] + ':' + production.name
|
||||
procurement_id = procurement_order.create(cr, uid, {
|
||||
'name': procurement_name,
|
||||
'origin': procurement_name,
|
||||
'date_planned': date_planned,
|
||||
'product_id': production_line.product_id.id,
|
||||
'product_qty': production_line.product_qty,
|
||||
'product_uom': production_line.product_uom.id,
|
||||
'product_uos_qty': production_line.product_uos and production_line.product_qty or False,
|
||||
'product_uos': production_line.product_uos and production_line.product_uos.id or False,
|
||||
'location_id': location_id,
|
||||
'procure_method': production_line.product_id.procure_method,
|
||||
'move_id': shipment_move_id,
|
||||
'company_id': production.company_id.id,
|
||||
})
|
||||
procurement_order.signal_button_confirm(cr, uid, [procurement_id])
|
||||
return procurement_id
|
||||
|
||||
def _make_production_internal_shipment_line(self, cr, uid, production_line, shipment_id, parent_move_id, destination_location_id=False, context=None):
|
||||
stock_move = self.pool.get('stock.move')
|
||||
production = production_line.production_id
|
||||
date_planned = production.date_planned
|
||||
# Internal shipment is created for Stockable and Consumer Products
|
||||
if production_line.product_id.type not in ('product', 'consu'):
|
||||
return False
|
||||
source_location_id = production.location_src_id.id
|
||||
if not destination_location_id:
|
||||
destination_location_id = source_location_id
|
||||
return stock_move.create(cr, uid, {
|
||||
'name': production.name,
|
||||
'picking_id': shipment_id,
|
||||
'product_id': production_line.product_id.id,
|
||||
'product_qty': production_line.product_qty,
|
||||
'product_uom': production_line.product_uom.id,
|
||||
'product_uos_qty': production_line.product_uos and production_line.product_uos_qty or False,
|
||||
'product_uos': production_line.product_uos and production_line.product_uos.id or False,
|
||||
'date': date_planned,
|
||||
'move_dest_id': parent_move_id,
|
||||
'location_id': source_location_id,
|
||||
'location_dest_id': destination_location_id,
|
||||
'state': 'waiting',
|
||||
'company_id': production.company_id.id,
|
||||
})
|
||||
|
||||
def _make_production_internal_shipment(self, cr, uid, production, context=None):
|
||||
ir_sequence = self.pool.get('ir.sequence')
|
||||
stock_picking = self.pool.get('stock.picking')
|
||||
routing_loc = None
|
||||
pick_type = 'internal'
|
||||
partner_id = False
|
||||
|
||||
# Take routing address as a Shipment Address.
|
||||
# If usage of routing location is a internal, make outgoing shipment otherwise internal shipment
|
||||
if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
|
||||
routing_loc = production.bom_id.routing_id.location_id
|
||||
if routing_loc.usage != 'internal':
|
||||
pick_type = 'out'
|
||||
partner_id = routing_loc.partner_id and routing_loc.partner_id.id or False
|
||||
|
||||
# Take next Sequence number of shipment base on type
|
||||
if pick_type!='internal':
|
||||
pick_name = ir_sequence.get(cr, uid, 'stock.picking.' + pick_type)
|
||||
else:
|
||||
pick_name = ir_sequence.get(cr, uid, 'stock.picking')
|
||||
|
||||
picking_id = stock_picking.create(cr, uid, {
|
||||
'name': pick_name,
|
||||
'origin': (production.origin or '').split(':')[0] + ':' + production.name,
|
||||
'type': pick_type,
|
||||
'move_type': 'one',
|
||||
'state': 'auto',
|
||||
'partner_id': partner_id,
|
||||
'auto_picking': self._get_auto_picking(cr, uid, production),
|
||||
'company_id': production.company_id.id,
|
||||
})
|
||||
production.write({'picking_id': picking_id}, context=context)
|
||||
return picking_id
|
||||
|
||||
def _make_production_produce_line(self, cr, uid, production, context=None):
|
||||
stock_move = self.pool.get('stock.move')
|
||||
source_location_id = production.product_id.property_stock_production.id
|
||||
|
@ -997,81 +976,104 @@ class mrp_production(osv.osv):
|
|||
'name': production.name,
|
||||
'date': production.date_planned,
|
||||
'product_id': production.product_id.id,
|
||||
'product_qty': production.product_qty,
|
||||
'product_uom': production.product_uom.id,
|
||||
'product_uom_qty': production.product_qty,
|
||||
'product_uos_qty': production.product_uos and production.product_uos_qty or False,
|
||||
'product_uos': production.product_uos and production.product_uos.id or False,
|
||||
'location_id': source_location_id,
|
||||
'location_dest_id': destination_location_id,
|
||||
'move_dest_id': production.move_prod_id.id,
|
||||
'state': 'waiting',
|
||||
'company_id': production.company_id.id,
|
||||
'production_id': production.id,
|
||||
'origin': production.name,
|
||||
}
|
||||
move_id = stock_move.create(cr, uid, data, context=context)
|
||||
production.write({'move_created_ids': [(6, 0, [move_id])]}, context=context)
|
||||
return move_id
|
||||
#a phantom bom cannot be used in mrp order so it's ok to assume the list returned by action_confirm
|
||||
#is 1 element long, so we can take the first.
|
||||
return stock_move.action_confirm(cr, uid, [move_id], context=context)[0]
|
||||
|
||||
def _make_production_consume_line(self, cr, uid, production_line, parent_move_id, source_location_id=False, context=None):
|
||||
def _get_raw_material_procure_method(self, cr, uid, product, context=None):
|
||||
'''This method returns the procure_method to use when creating the stock move for the production raw materials'''
|
||||
try:
|
||||
mto_route = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'route_warehouse0_mto')[1]
|
||||
except:
|
||||
return "make_to_stock"
|
||||
routes = product.route_ids + product.categ_id.total_route_ids
|
||||
if mto_route in [x.id for x in routes]:
|
||||
return "make_to_order"
|
||||
return "make_to_stock"
|
||||
|
||||
def _make_consume_line_from_data(self, cr, uid, production, product, uom_id, qty, uos_id, uos_qty, context=None):
|
||||
stock_move = self.pool.get('stock.move')
|
||||
production = production_line.production_id
|
||||
# Internal shipment is created for Stockable and Consumer Products
|
||||
if production_line.product_id.type not in ('product', 'consu'):
|
||||
if product.type not in ('product', 'consu'):
|
||||
return False
|
||||
# Take routing location as a Source Location.
|
||||
source_location_id = production.location_src_id.id
|
||||
if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
|
||||
source_location_id = production.bom_id.routing_id.location_id.id
|
||||
|
||||
destination_location_id = production.product_id.property_stock_production.id
|
||||
if not source_location_id:
|
||||
source_location_id = production.location_src_id.id
|
||||
move_id = stock_move.create(cr, uid, {
|
||||
'name': production.name,
|
||||
'date': production.date_planned,
|
||||
'product_id': production_line.product_id.id,
|
||||
'product_qty': production_line.product_qty,
|
||||
'product_uom': production_line.product_uom.id,
|
||||
'product_uos_qty': production_line.product_uos and production_line.product_uos_qty or False,
|
||||
'product_uos': production_line.product_uos and production_line.product_uos.id or False,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': qty,
|
||||
'product_uom': uom_id,
|
||||
'product_uos_qty': uos_id and uos_qty or False,
|
||||
'product_uos': uos_id or False,
|
||||
'location_id': source_location_id,
|
||||
'location_dest_id': destination_location_id,
|
||||
'move_dest_id': parent_move_id,
|
||||
'state': 'waiting',
|
||||
'company_id': production.company_id.id,
|
||||
'procure_method': self._get_raw_material_procure_method(cr, uid, product, context=context),
|
||||
'raw_material_production_id': production.id,
|
||||
#this saves us a browse in create()
|
||||
'price_unit': product.standard_price,
|
||||
'origin': production.name,
|
||||
})
|
||||
production.write({'move_lines': [(4, move_id)]}, context=context)
|
||||
return move_id
|
||||
|
||||
def _make_production_consume_line(self, cr, uid, line, context=None):
|
||||
return self._make_consume_line_from_data(cr, uid, line.production_id, line.product_id, line.product_uom.id, line.product_qty, line.product_uos.id, line.product_uos_qty, context=context)
|
||||
|
||||
def action_confirm(self, cr, uid, ids, context=None):
|
||||
""" Confirms production order.
|
||||
@return: Newly generated Shipment Id.
|
||||
"""
|
||||
shipment_id = False
|
||||
uncompute_ids = filter(lambda x:x, [not x.product_lines and x.id or False for x in self.browse(cr, uid, ids, context=context)])
|
||||
uncompute_ids = filter(lambda x: x, [not x.product_lines and x.id or False for x in self.browse(cr, uid, ids, context=context)])
|
||||
self.action_compute(cr, uid, uncompute_ids, context=context)
|
||||
for production in self.browse(cr, uid, ids, context=context):
|
||||
shipment_id = self._make_production_internal_shipment(cr, uid, production, context=context)
|
||||
produce_move_id = self._make_production_produce_line(cr, uid, production, context=context)
|
||||
|
||||
# Take routing location as a Source Location.
|
||||
source_location_id = production.location_src_id.id
|
||||
if production.bom_id.routing_id and production.bom_id.routing_id.location_id:
|
||||
source_location_id = production.bom_id.routing_id.location_id.id
|
||||
self._make_production_produce_line(cr, uid, production, context=context)
|
||||
|
||||
stock_moves = []
|
||||
for line in production.product_lines:
|
||||
consume_move_id = self._make_production_consume_line(cr, uid, line, produce_move_id, source_location_id=source_location_id, context=context)
|
||||
if shipment_id:
|
||||
shipment_move_id = self._make_production_internal_shipment_line(cr, uid, line, shipment_id, consume_move_id,\
|
||||
destination_location_id=source_location_id, context=context)
|
||||
self._make_production_line_procurement(cr, uid, line, shipment_move_id, context=context)
|
||||
stock_move_id = self._make_production_consume_line(cr, uid, line, context=context)
|
||||
if stock_move_id:
|
||||
stock_moves.append(stock_move_id)
|
||||
if stock_moves:
|
||||
self.pool.get('stock.move').action_confirm(cr, uid, stock_moves, context=context)
|
||||
production.write({'state': 'confirmed'}, context=context)
|
||||
return 0
|
||||
|
||||
def action_assign(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Checks the availability on the consume lines of the production order
|
||||
"""
|
||||
move_obj = self.pool.get("stock.move")
|
||||
for production in self.browse(cr, uid, ids, context=context):
|
||||
move_obj.action_assign(cr, uid, [x.id for x in production.move_lines], context=context)
|
||||
|
||||
if shipment_id:
|
||||
self.pool.get('stock.picking').signal_button_confirm(cr, uid, [shipment_id])
|
||||
production.write({'state':'confirmed'}, context=context)
|
||||
return shipment_id
|
||||
|
||||
def force_production(self, cr, uid, ids, *args):
|
||||
""" Assigns products.
|
||||
@param *args: Arguments
|
||||
@return: True
|
||||
"""
|
||||
pick_obj = self.pool.get('stock.picking')
|
||||
pick_obj.force_assign(cr, uid, [prod.picking_id.id for prod in self.browse(cr, uid, ids)])
|
||||
move_obj = self.pool.get('stock.move')
|
||||
for order in self.browse(cr, uid, ids):
|
||||
move_obj.force_assign(cr, uid, [x.id for x in order.move_lines])
|
||||
return True
|
||||
|
||||
|
||||
|
@ -1084,8 +1086,8 @@ class mrp_production_workcenter_line(osv.osv):
|
|||
_columns = {
|
||||
'name': fields.char('Work Order', size=64, required=True),
|
||||
'workcenter_id': fields.many2one('mrp.workcenter', 'Work Center', required=True),
|
||||
'cycle': fields.float('Number of Cycles', digits=(16,2)),
|
||||
'hour': fields.float('Number of Hours', digits=(16,2)),
|
||||
'cycle': fields.float('Number of Cycles', digits=(16, 2)),
|
||||
'hour': fields.float('Number of Hours', digits=(16, 2)),
|
||||
'sequence': fields.integer('Sequence', required=True, help="Gives the sequence order when displaying a list of work orders."),
|
||||
'production_id': fields.many2one('mrp.production', 'Manufacturing Order',
|
||||
track_visibility='onchange', select=True, ondelete='cascade', required=True),
|
||||
|
|
|
@ -27,5 +27,19 @@ This application supports complete integration and production scheduling for sto
|
|||
<field name="number_increment">1</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Procurement rules and routes
|
||||
-->
|
||||
|
||||
<record id="route_warehouse0_manufacture" model='stock.location.route'>
|
||||
<field name="name">Manufacture</field>
|
||||
<field name="sequence">5</field>
|
||||
</record>
|
||||
|
||||
<!-- Enable the manufacturing in warehouse0 -->
|
||||
<record id='stock.warehouse0' model='stock.warehouse'>
|
||||
<field name='manufacture_to_resupply' eval='True'/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -189,7 +189,7 @@
|
|||
<field name="time_stop" widget="float_time"/>
|
||||
</group>
|
||||
<group string="Costing Information">
|
||||
<field name="product_id" on_change="on_change_product_cost(product_id)" context="{'default_supply_method':'produce'}"/>
|
||||
<field name="product_id" on_change="on_change_product_cost(product_id)"/>
|
||||
<field name="costs_hour"/>
|
||||
<field name="costs_hour_account_id" groups="analytic.group_analytic_accounting"/>
|
||||
<field name="costs_cycle"/>
|
||||
|
@ -347,7 +347,7 @@
|
|||
<form string="Bill of Material" version="7.0">
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id, name, product_qty, context)" context="{'default_supply_method':'produce'}" class="oe_inline"/>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id, name, product_qty, context)" class="oe_inline"/>
|
||||
<label for="product_qty" string="Quantity"/>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline" on_change="onchange_product_id(product_id, name, product_qty, context)"/>
|
||||
|
@ -380,7 +380,7 @@
|
|||
<page string="Components">
|
||||
<field name="bom_lines" widget="one2many_list">
|
||||
<tree string="Components" editable="bottom">
|
||||
<field name="product_id" context="{'default_supply_method':'produce'}" on_change="onchange_product_id(product_id, name)"/>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id, name)"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="product_uom" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
|
||||
<field name="name" invisible="1"/>
|
||||
|
@ -519,17 +519,58 @@
|
|||
<field name="arch" type="xml">
|
||||
<xpath expr="//filter[@string='Consumable']" position="after">
|
||||
<separator/>
|
||||
<filter string="Components" name="components" icon="terp-accessories-archiver" domain="[('bom_ids','not in',[]),('bom_ids.bom_id','!=',False)]" invisible="not context.get('search_default_filter_supply_method_produce', False)"/>
|
||||
<filter string="Components" name="components" icon="terp-accessories-archiver" domain="[('bom_ids','not in',[]),('bom_ids.bom_id','!=',False)]"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">product.template.form.view.inherited</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='warranty']" position="before">
|
||||
<label for="produce_delay"/>
|
||||
<div>
|
||||
<field name="produce_delay" class="oe_inline"/> days
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_normal_procurement_locations_form_inherited" model="ir.ui.view">
|
||||
<field name="name">product.normal.procurement.locations.inherited</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_normal_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='procurement_uom']" position="after">
|
||||
<group name="delay" string="Delays" attrs="{'invisible':[('type','=','service')]}">
|
||||
<label for="produce_delay" />
|
||||
<div attrs="{'invisible':[('type','=','service')]}">
|
||||
<field name="produce_delay" class="oe_inline" style="vertical-align:baseline"/> days
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_mrp_product_form_inherited" model="ir.ui.view">
|
||||
<field name="name">product.form.mrp.inherited</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="stock.product_form_view_procurement_button"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='lot']" position="inside">
|
||||
<field name="track_production" groups="stock.group_production_lot" attrs="{'invisible': [('track_all', '=', True)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_supply_method_produce" model="ir.actions.act_window">
|
||||
<field name="name">Products</field>
|
||||
<field name="res_model">product.product</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_filter_supply_method_produce' : 1}</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="product.product_search_form_view"/>
|
||||
</record>
|
||||
<!-- BOM menus -->
|
||||
|
@ -632,13 +673,12 @@
|
|||
<header>
|
||||
<button name="button_confirm" states="draft" string="Confirm Production" class="oe_highlight"/>
|
||||
<button name="%(act_mrp_product_produce)d" states="ready,in_production" string="Produce" type="action" class="oe_highlight"/>
|
||||
<button name="force_production" states="confirmed" string="Force Reservation" type="object" class="oe_highlight"/>
|
||||
<button name="force_production" states="picking_except" string="Force Reservation" type="object"/>
|
||||
<button name="action_assign" states="confirmed,picking_except" string="Check Availability" type="object" class="oe_highlight"/>
|
||||
<button name="force_production" states="confirmed" string="Force Reservation" type="object"/>
|
||||
<button name="button_produce" states="ready" string="Mark as Started"/>
|
||||
<button name="button_recreate" states="picking_except" string="Recreate Picking"/>
|
||||
<button name="button_cancel" states="draft,ready,in_production,picking_except" string="Cancel Production"/>
|
||||
<button name="button_cancel" states="draft,ready,in_production" string="Cancel Production"/>
|
||||
<button name="action_cancel" type="object" states="confirmed" string="Cancel Production"/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,ready,in_production,done" statusbar_colors='{"picking_except":"red","confirmed":"blue"}'/>
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,ready,in_production,done" statusbar_colors='{"confirmed":"blue"}'/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
|
@ -646,7 +686,7 @@
|
|||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="product_id" on_change="product_id_change(product_id, product_qty)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False)]" class="oe_inline" context='{"default_supply_method":"produce", "default_type": "product"}'/>
|
||||
<field name="product_id" on_change="product_id_change(product_id, product_qty)" domain="[('bom_ids','!=',False),('bom_ids.bom_id','=',False),('bom_ids.type','!=','phantom')]" class="oe_inline" context='{"default_type": "product"}'/>
|
||||
<label for="product_qty"/>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline" on_change="product_id_change(product_id, product_qty)"/>
|
||||
|
@ -679,22 +719,16 @@
|
|||
<group>
|
||||
<group string="Products to Consume">
|
||||
<field name="move_lines" nolabel="1" options="{'reload_on_button': true}">
|
||||
<tree colors="blue:state == 'draft';black:state in ('ready','assigned','in_production');gray:state in ('cancel','done');red:state in ('confirmed','picking_except','waiting')" string="Products to Consume">
|
||||
<tree colors="blue:state == 'draft';black:state in ('ready','assigned','in_production');gray:state in ('cancel','done');red:state in ('confirmed','waiting')" string="Products to Consume">
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty" string="Quantity"/>
|
||||
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="prodlot_id" groups="stock.group_production_lot" context="{'product_id': product_id}"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<button name="%(stock.move_consume)d"
|
||||
<button name="%(mrp.move_consume)d"
|
||||
string="Consume Products" type="action"
|
||||
icon="gtk-go-forward" context="{'consume': True}"
|
||||
states="draft,waiting,confirmed,assigned"/>
|
||||
<button
|
||||
name="%(stock.track_line)d"
|
||||
string="Split in Serial Numbers"
|
||||
type="action" icon="gtk-justify-fill"
|
||||
states="draft,waiting,confirmed,assigned"
|
||||
groups="stock.group_production_lot"/>
|
||||
states="assigned"
|
||||
/>
|
||||
<button name="%(stock.move_scrap)d"
|
||||
string="Scrap Products" type="action"
|
||||
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
|
||||
|
@ -704,11 +738,11 @@
|
|||
</group>
|
||||
<group string="Consumed Products">
|
||||
<field name="move_lines2" nolabel="1" options="{'reload_on_button': true}">
|
||||
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('picking_except','confirmed','ready','in_production');gray:state == 'cancel' " string="Consumed Products" editable="bottom">
|
||||
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in ('confirmed','ready','in_production');gray:state == 'cancel' " string="Consumed Products" editable="bottom">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="restrict_lot_id" context="{'product_id': product_id}" groups="stock.group_tracking_lot"/>
|
||||
<field name="product_qty" readonly="1"/>
|
||||
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="prodlot_id" context="{'product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="scrapped" invisible="1"/>
|
||||
</tree>
|
||||
|
@ -725,25 +759,20 @@
|
|||
<field name="product_qty"/>
|
||||
<field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<button name="%(stock.action_partial_move_server)d"
|
||||
string="Partial"
|
||||
type="action" states="confirmed,assigned"
|
||||
icon="gtk-justify-fill"/>
|
||||
<!--TODO: should have a partial picking here-->
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group string="Produced Products">
|
||||
<field name="move_created_ids2" nolabel="1" options="{'reload_on_button': true}">
|
||||
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('picking_except','confirmed','ready','in_production');gray:state in('cancel','done') " string="Finished Products">
|
||||
<tree colors="red:scrapped==True;blue:state == 'draft';black:state in('confirmed','ready','in_production');gray:state in('cancel','done') " string="Finished Products">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="product_qty" readonly="1"/>
|
||||
<field name="restrict_lot_id" groups="stock.group_tracking_lot"/>
|
||||
<field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
|
||||
<field name="location_dest_id" readonly="1" string="Destination Loc." widget="selection" groups="stock.group_locations"/>
|
||||
<field name="prodlot_id" context="{'product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="scrapped" invisible="1"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<button name="%(stock.track_line)d"
|
||||
string="Split in Serial Numbers" type="action" icon="gtk-justify-fill" states="done,cancel"/>
|
||||
<button name="%(stock.move_scrap)d"
|
||||
string="Scrap Products" type="action" icon="terp-gtk-jump-to-ltr"
|
||||
states="done,cancel"/>
|
||||
|
@ -789,8 +818,7 @@
|
|||
<field name="date_finished" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="picking_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection" />
|
||||
<field name="move_prod_id" groups="stock.group_locations"/>
|
||||
</group>
|
||||
</group>
|
||||
|
@ -951,11 +979,11 @@
|
|||
<field name="inherit_id" ref="procurement.procurement_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<data>
|
||||
<xpath expr="//field[@name='move_id']" position="before">
|
||||
<xpath expr="//field[@name='origin']" position="before">
|
||||
<field name="bom_id" domain="[('product_id','=',product_id),('bom_id','=',False)]"/>
|
||||
<field name="production_id" attrs="{'invisible': [('production_id','=',False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='close_move']" position="after">
|
||||
<xpath expr="//field[@name='origin']" position="after">
|
||||
<group colspan="4" groups="product.group_mrp_properties">
|
||||
<field colspan="4" name="property_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
|
@ -963,23 +991,7 @@
|
|||
</data>
|
||||
</field>
|
||||
</record>
|
||||
<record id="product_product_normal_form_supply_view" model="ir.ui.view">
|
||||
<field name="name">product.normal.form.mrp.inherit</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="procurement.product_form_view_procurement_button"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='supply_method']" position="attributes">
|
||||
<attribute name="invisible">False</attribute>
|
||||
</xpath>
|
||||
<group name="procurement_help" position="inside">
|
||||
<p attrs="{'invisible': ['|','|',('type','=','service'),('procure_method','<>','make_to_order'),('supply_method','<>','produce')]}">
|
||||
When you sell this product, OpenERP will trigger <b>a manufacturing
|
||||
order</b> using the bill of materials assigned to this product.
|
||||
The delivery order will be ready once the production is done.
|
||||
</p>
|
||||
</group>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Menu for Resource for MRP-->
|
||||
|
||||
|
@ -1055,5 +1067,16 @@
|
|||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse_inherited" model="ir.ui.view">
|
||||
<field name="name">Stock Warehouse Inherited</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="inherit_id" ref="stock.view_warehouse"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='default_resupply_wh_id']" position="before">
|
||||
<field name="manufacture_to_resupply" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -14,11 +14,10 @@
|
|||
<field name="flow_start">True</field>
|
||||
<field name="name">draft</field>
|
||||
</record>
|
||||
<record id="prod_act_picking" model="workflow.activity">
|
||||
<record id="prod_act_confirmed" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_prod"/>
|
||||
<field name="name">picking</field>
|
||||
<field name="kind">subflow</field>
|
||||
<field name="subflow_id" search="[('osv','=','stock.picking')]"/>
|
||||
<field name="name">confirmed</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_confirm()</field>
|
||||
</record>
|
||||
<record id="prod_act_ready" model="workflow.activity">
|
||||
|
@ -27,12 +26,6 @@
|
|||
<field name="kind">function</field>
|
||||
<field name="action">action_ready()</field>
|
||||
</record>
|
||||
<record id="prod_act_picking_exception" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_prod"/>
|
||||
<field name="name">picking_exception</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_picking_except()</field>
|
||||
</record>
|
||||
<record id="prod_act_in_production" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_prod"/>
|
||||
<field name="name">in_production</field>
|
||||
|
@ -43,7 +36,7 @@
|
|||
<field name="wkf_id" ref="wkf_prod"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_production_end()</field>
|
||||
<field name="action">action_production_end()</field>
|
||||
<field name="name">done</field>
|
||||
</record>
|
||||
<record id="prod_act_cancel" model="workflow.activity">
|
||||
|
@ -54,23 +47,17 @@
|
|||
<field name="action">action_cancel()</field>
|
||||
</record>
|
||||
|
||||
<record id="prod_trans_draft_ready" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_draft"/>
|
||||
<field name="act_to" ref="prod_act_ready"/>
|
||||
<field name="signal">button_confirm</field>
|
||||
<field name="condition">not test_if_product()</field>
|
||||
</record>
|
||||
<record id="prod_trans_draft_picking" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_draft"/>
|
||||
<field name="act_to" ref="prod_act_picking"/>
|
||||
<field name="act_to" ref="prod_act_confirmed"/>
|
||||
<field name="signal">button_confirm</field>
|
||||
<field name="condition">test_if_product()</field>
|
||||
</record>
|
||||
<record id="prod_trans_picking_ready" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_picking"/>
|
||||
<field name="act_from" ref="prod_act_confirmed"/>
|
||||
<field name="act_to" ref="prod_act_ready"/>
|
||||
<field name="signal"></field>
|
||||
<field name="condition">picking_id and picking_id.state=='done'</field>
|
||||
<field name="trigger_model">stock.move</field>
|
||||
<field name="trigger_expr_id">consume_lines_get()</field>
|
||||
<field name="condition">test_ready()</field>
|
||||
</record>
|
||||
<record id="prod_trans_ready_in_production" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_ready"/>
|
||||
|
@ -83,21 +70,6 @@
|
|||
<field name="signal">button_produce_done</field>
|
||||
<field name="condition">test_production_done()</field>
|
||||
</record>
|
||||
<record id="prod_trans_picking_picking_exception" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_picking"/>
|
||||
<field name="act_to" ref="prod_act_picking_exception"/>
|
||||
<field name="signal">subflow.cancel</field>
|
||||
</record>
|
||||
<record id="prod_trans_picking_exception_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_picking_exception"/>
|
||||
<field name="act_to" ref="prod_act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
<record id="prod_trans_picking_exception_picking" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_picking_exception"/>
|
||||
<field name="act_to" ref="prod_act_picking"/>
|
||||
<field name="signal">button_recreate</field>
|
||||
</record>
|
||||
<record id="prod_trans_ready_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_ready"/>
|
||||
<field name="act_to" ref="prod_act_cancel"/>
|
||||
|
@ -113,33 +85,10 @@
|
|||
<field name="act_to" ref="prod_act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
|
||||
<!-- Procurement -->
|
||||
|
||||
<record id="act_produce" model="workflow.activity">
|
||||
<field name="wkf_id" ref="procurement.wkf_procurement"/>
|
||||
<field name="name">produce</field>
|
||||
<field name="kind">subflow</field>
|
||||
<field name="subflow_id" search="[('osv','=','mrp.production')]"/>
|
||||
<field name="action">action_produce_assign_product()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_confirm_mto_need_production" model="workflow.transition">
|
||||
<field name="act_from" ref="procurement.act_confirm_mto"/>
|
||||
<field name="act_to" ref="act_produce"/>
|
||||
<field name="condition">check_produce() and is_product() and check_bom_exists()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_produce_finished_prod" model="workflow.transition">
|
||||
<field name="act_from" ref="act_produce"/>
|
||||
<field name="act_to" ref="procurement.act_make_done"/>
|
||||
<field name="signal">subflow.done</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_produce_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="act_produce"/>
|
||||
<field name="act_to" ref="procurement.act_cancel"/>
|
||||
<field name="signal">subflow.cancel</field>
|
||||
<record id="prod_trans_confirmed_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="prod_act_confirmed"/>
|
||||
<field name="act_to" ref="prod_act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
|
|
|
@ -21,9 +21,16 @@
|
|||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp import SUPERUSER_ID
|
||||
|
||||
class procurement_rule(osv.osv):
|
||||
_inherit = 'procurement.rule'
|
||||
|
||||
def _get_action(self, cr, uid, context=None):
|
||||
return [('manufacture', _('Manufacture'))] + super(procurement_rule, self)._get_action(cr, uid, context=context)
|
||||
|
||||
|
||||
class procurement_order(osv.osv):
|
||||
_inherit = 'procurement.order'
|
||||
|
@ -33,88 +40,87 @@ class procurement_order(osv.osv):
|
|||
'production_id': fields.many2one('mrp.production', 'Manufacturing Order'),
|
||||
}
|
||||
|
||||
def check_produce_product(self, cr, uid, procurement, context=None):
|
||||
''' Depict the capacity of the procurement workflow to produce products (not services)'''
|
||||
return True
|
||||
def propagate_cancel(self, cr, uid, procurement, context=None):
|
||||
if procurement.rule_id.action == 'manufacture' and procurement.production_id:
|
||||
self.pool.get('mrp.production').action_cancel(cr, uid, [procurement.production_id.id], context=context)
|
||||
return super(procurement_order, self).propagate_cancel(cr, uid, procurement, context=context)
|
||||
|
||||
def _run(self, cr, uid, procurement, context=None):
|
||||
if procurement.rule_id and procurement.rule_id.action == 'manufacture':
|
||||
#make a manufacturing order for the procurement
|
||||
return self.make_mo(cr, uid, [procurement.id], context=context)[procurement.id]
|
||||
return super(procurement_order, self)._run(cr, uid, procurement, context=context)
|
||||
|
||||
def _check(self, cr, uid, procurement, context=None):
|
||||
if procurement.production_id and procurement.production_id.state == 'done': # TOCHECK: no better method?
|
||||
return True
|
||||
return super(procurement_order, self)._check(cr, uid, procurement, context=context)
|
||||
|
||||
def check_bom_exists(self, cr, uid, ids, context=None):
|
||||
""" Finds the bill of material for the product from procurement order.
|
||||
@return: True or False
|
||||
"""
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
product = procurement.product_id
|
||||
properties = [x.id for x in procurement.property_ids]
|
||||
bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
|
||||
if not bom_id:
|
||||
cr.execute('update procurement_order set message=%s where id=%s', (_('No BoM defined for this product !'), procurement.id))
|
||||
for (id, name) in self.name_get(cr, uid, procurement.id):
|
||||
message = _("Procurement '%s' has an exception: 'No BoM defined for this product !'") % name
|
||||
self.message_post(cr, uid, [procurement.id], body=message, context=context)
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_conditions_confirm2wait(self, cr, uid, ids):
|
||||
""" condition on the transition to go from 'confirm' activity to 'confirm_wait' activity """
|
||||
res = super(procurement_order, self).check_conditions_confirm2wait(cr, uid, ids)
|
||||
return res and not self.get_phantom_bom_id(cr, uid, ids)
|
||||
|
||||
def get_phantom_bom_id(self, cr, uid, ids, context=None):
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
if procurement.move_id and procurement.move_id.product_id.supply_method=='produce' \
|
||||
and procurement.move_id.product_id.procure_method=='make_to_order':
|
||||
phantom_bom_id = self.pool.get('mrp.bom').search(cr, uid, [
|
||||
('product_id', '=', procurement.move_id.product_id.id),
|
||||
('bom_id', '=', False),
|
||||
('type', '=', 'phantom')])
|
||||
return phantom_bom_id
|
||||
return False
|
||||
|
||||
def action_produce_assign_product(self, cr, uid, ids, context=None):
|
||||
""" This is action which call from workflow to assign production order to procurements
|
||||
@return: True
|
||||
"""
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
res = procurement_obj.make_mo(cr, uid, ids, context=context)
|
||||
res = res.values()
|
||||
return len(res) and res[0] or 0
|
||||
|
||||
def make_mo(self, cr, uid, ids, context=None):
|
||||
""" Make Manufacturing(production) order from procurement
|
||||
@return: New created Production Orders procurement wise
|
||||
@return: New created Production Orders procurement wise
|
||||
"""
|
||||
res = {}
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context).company_id
|
||||
production_obj = self.pool.get('mrp.production')
|
||||
move_obj = self.pool.get('stock.move')
|
||||
bom_obj = self.pool.get('mrp.bom')
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
for procurement in procurement_obj.browse(cr, uid, ids, context=context):
|
||||
res_id = procurement.move_id.id
|
||||
newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S') - relativedelta(days=procurement.product_id.produce_delay or 0.0)
|
||||
newdate = newdate - relativedelta(days=company.manufacturing_lead)
|
||||
produce_id = production_obj.create(cr, uid, {
|
||||
'origin': procurement.origin,
|
||||
'product_id': procurement.product_id.id,
|
||||
'product_qty': procurement.product_qty,
|
||||
'product_uom': procurement.product_uom.id,
|
||||
'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
|
||||
'product_uos': procurement.product_uos and procurement.product_uos.id or False,
|
||||
'location_src_id': procurement.location_id.id,
|
||||
'location_dest_id': procurement.location_id.id,
|
||||
'bom_id': procurement.bom_id and procurement.bom_id.id or False,
|
||||
'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'move_prod_id': res_id,
|
||||
'company_id': procurement.company_id.id,
|
||||
})
|
||||
|
||||
res[procurement.id] = produce_id
|
||||
self.write(cr, uid, [procurement.id], {'state': 'running', 'production_id': produce_id})
|
||||
bom_result = production_obj.action_compute(cr, uid,
|
||||
[produce_id], properties=[x.id for x in procurement.property_ids])
|
||||
production_obj.signal_button_confirm(cr, uid, [produce_id])
|
||||
self.production_order_create_note(cr, uid, ids, context=context)
|
||||
if self.check_bom_exists(cr, uid, [procurement.id], context=context):
|
||||
if procurement.bom_id:
|
||||
bom_id = procurement.bom_id.id
|
||||
routing_id = procurement.bom_id.routing_id.id
|
||||
else:
|
||||
properties = [x.id for x in procurement.property_ids]
|
||||
bom_id = bom_obj._bom_find(cr, uid, procurement.product_id.id, procurement.product_uom.id, properties)
|
||||
bom = bom_obj.browse(cr, uid, bom_id, context=context)
|
||||
routing_id = bom.routing_id.id
|
||||
|
||||
res_id = procurement.move_dest_id and procurement.move_dest_id.id or False
|
||||
newdate = datetime.strptime(procurement.date_planned, '%Y-%m-%d %H:%M:%S') - relativedelta(days=procurement.product_id.produce_delay or 0.0)
|
||||
newdate = newdate - relativedelta(days=company.manufacturing_lead)
|
||||
#create the MO as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
|
||||
produce_id = production_obj.create(cr, SUPERUSER_ID, {
|
||||
'origin': procurement.origin,
|
||||
'product_id': procurement.product_id.id,
|
||||
'product_qty': procurement.product_qty,
|
||||
'product_uom': procurement.product_uom.id,
|
||||
'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
|
||||
'product_uos': procurement.product_uos and procurement.product_uos.id or False,
|
||||
'location_src_id': procurement.location_id.id,
|
||||
'location_dest_id': procurement.location_id.id,
|
||||
'bom_id': bom_id,
|
||||
'routing_id': routing_id,
|
||||
'date_planned': newdate.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'move_prod_id': res_id,
|
||||
'company_id': procurement.company_id.id,
|
||||
})
|
||||
|
||||
res[procurement.id] = produce_id
|
||||
self.write(cr, uid, [procurement.id], {'production_id': produce_id})
|
||||
procurement.refresh()
|
||||
self.production_order_create_note(cr, uid, procurement, context=context)
|
||||
production_obj.action_compute(cr, uid, [produce_id], properties=[x.id for x in procurement.property_ids])
|
||||
production_obj.signal_button_confirm(cr, uid, [produce_id])
|
||||
else:
|
||||
res[procurement.id] = False
|
||||
self.message_post(cr, uid, [procurement.id], body=_("No BoM exists for this product!"), context=context)
|
||||
return res
|
||||
|
||||
def production_order_create_note(self, cr, uid, ids, context=None):
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
body = _("Manufacturing Order <em>%s</em> created.") % ( procurement.production_id.name,)
|
||||
self.message_post(cr, uid, [procurement.id], body=body, context=context)
|
||||
def production_order_create_note(self, cr, uid, procurement, context=None):
|
||||
body = _("Manufacturing Order <em>%s</em> created.") % (procurement.production_id.name,)
|
||||
self.message_post(cr, uid, [procurement.id], body=body, context=context)
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -27,6 +27,12 @@ class product_product(osv.osv):
|
|||
_inherit = "product.product"
|
||||
_columns = {
|
||||
"bom_ids": fields.one2many('mrp.bom', 'product_id','Bill of Materials', domain=[('bom_id','=',False)]),
|
||||
"produce_delay": fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added."),
|
||||
'track_production': fields.boolean('Track Manufacturing Lots', help="Forces to specify a Serial Number for all moves containing this product and generated by a Manufacturing Order"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
"produce_delay": 1,
|
||||
}
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
|
|
|
@ -63,6 +63,7 @@ class report_mrp_inout(osv.osv):
|
|||
_columns = {
|
||||
'date': fields.char('Week', size=64, required=True),
|
||||
'value': fields.float('Stock value', required=True, digits=(16,2)),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
}
|
||||
|
||||
def init(self, cr):
|
||||
|
@ -72,14 +73,15 @@ class report_mrp_inout(osv.osv):
|
|||
min(sm.id) as id,
|
||||
to_char(sm.date,'YYYY:IW') as date,
|
||||
sum(case when (sl.usage='internal') then
|
||||
pt.standard_price * sm.product_qty
|
||||
sm.price_unit * sm.product_qty
|
||||
else
|
||||
0.0
|
||||
end - case when (sl2.usage='internal') then
|
||||
pt.standard_price * sm.product_qty
|
||||
sm.price_unit * sm.product_qty
|
||||
else
|
||||
0.0
|
||||
end) as value
|
||||
end) as value,
|
||||
sm.company_id
|
||||
from
|
||||
stock_move sm
|
||||
left join product_product pp
|
||||
|
@ -91,9 +93,9 @@ class report_mrp_inout(osv.osv):
|
|||
left join stock_location sl2
|
||||
on ( sl2.id = sm.location_dest_id)
|
||||
where
|
||||
sm.state in ('waiting','confirmed','assigned')
|
||||
sm.state = 'done'
|
||||
group by
|
||||
to_char(sm.date,'YYYY:IW')
|
||||
to_char(sm.date,'YYYY:IW'), sm.company_id
|
||||
)""")
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
<field name="model">report.mrp.inout</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Stock value variation">
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="date"/>
|
||||
<field name="value"/>
|
||||
</tree>
|
||||
|
@ -62,6 +63,7 @@
|
|||
<group col="4">
|
||||
<field name="date"/>
|
||||
<field name="value"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -73,6 +75,7 @@
|
|||
<field name="model">report.mrp.inout</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Stock value variation" type="bar">
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="date"/>
|
||||
<field name="value" operator="+"/>
|
||||
</graph>
|
||||
|
|
|
@ -44,17 +44,6 @@ class mrp_config_settings(osv.osv_memory):
|
|||
'Without this module: A + B + C -> D.\n'
|
||||
'With this module: A + B + C -> D + E.\n'
|
||||
'-This installs the module mrp_byproduct.'),
|
||||
'module_mrp_jit': fields.boolean("Generate procurement in real time",
|
||||
help='This allows Just In Time computation of procurement orders.\n'
|
||||
'All procurement orders will be processed immediately, which could in some '
|
||||
'cases entail a small performance impact.\n'
|
||||
'-This installs the module mrp_jit.'),
|
||||
'module_stock_no_autopicking': fields.boolean("Manage manual picking to fulfill manufacturing orders ",
|
||||
help='This module allows an intermediate picking process to provide raw materials to production orders.\n'
|
||||
'For example to manage production made by your suppliers (sub-contracting).\n'
|
||||
'To achieve this, set the assembled product which is sub-contracted to "No Auto-Picking" '
|
||||
'and put the location of the supplier in the routing of the assembly operation.\n'
|
||||
'-This installs the module stock_no_autopicking.'),
|
||||
'group_mrp_routings': fields.boolean("Manage routings and work orders ",
|
||||
implied_group='mrp.group_mrp_routings',
|
||||
help='Routings allow you to create and manage the manufacturing operations that should be followed '
|
||||
|
|
|
@ -35,19 +35,11 @@
|
|||
<field name="module_mrp_repair" class="oe_inline"/>
|
||||
<label for="module_mrp_repair"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="module_stock_no_autopicking" class="oe_inline"/>
|
||||
<label for="module_stock_no_autopicking" />
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<group >
|
||||
<label for="id" string="Planning"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="module_mrp_jit" class="oe_inline"/>
|
||||
<label for="module_mrp_jit"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="group_mrp_routings" class="oe_inline"/>
|
||||
<label for="group_mrp_routings"/>
|
||||
|
|
|
@ -1,81 +1,78 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_analytic_line_user,account.analytic.line,account.model_account_analytic_line,group_mrp_user,1,1,1,0
|
||||
access_mrp_workcenter,mrp.workcenter,model_mrp_workcenter,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_routing,mrp.routing,model_mrp_routing,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_routing_workcenter,mrp.routing.workcenter,model_mrp_routing_workcenter,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_bom,mrp.bom,model_mrp_bom,group_mrp_user,1,0,0,0
|
||||
access_mrp_production,mrp.production user,model_mrp_production,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_production_salesman,mrp.production salesman,model_mrp_production,base.group_sale_salesman,1,1,1,0
|
||||
access_mrp_production_product_line_salesman,mrp.production.product.line salesman,model_mrp_production_product_line,base.group_sale_salesman,1,0,1,0
|
||||
access_mrp_production_workcenter_line_salesman,mrp.production.workcenter.line salesman,model_mrp_production_workcenter_line,base.group_sale_salesman,1,0,1,0
|
||||
access_mrp_property_group,mrp.property.group,procurement.model_mrp_property_group,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_property,mrp.property,procurement.model_mrp_property,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_production_product_line,mrp.production.product.line,model_mrp_production_product_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_procurement,procurement.order,model_procurement_order,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_workcenter_manager,mrp.workcenter.manager,model_mrp_workcenter,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_routing_manager,mrp.routing.manager,model_mrp_routing,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_routing_workcenter_manager,mrp.routing.workcenter.manager,model_mrp_routing_workcenter,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_bom_manager,mrp.bom.manager,model_mrp_bom,mrp.group_mrp_manager,1,1,1,1
|
||||
access_stock_location_mrp_worker,stock.location mrp_worker,stock.model_stock_location,mrp.group_mrp_user,1,0,0,0
|
||||
access_stock_move_mrp_worker,stock.move mrp_worker,stock.model_stock_move,mrp.group_mrp_user,1,1,1,0
|
||||
access_stock_picking_mrp_worker,stock.picking mrp_worker,stock.model_stock_picking,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_warehouse,stock.warehouse mrp_worker,stock.model_stock_warehouse,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_analytic_journal_mrp_worker,account.analytic.journal mrp_worker,account.model_account_analytic_journal,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_account,account.account mrp_worker,account.model_account_account,mrp.group_mrp_user,1,0,0,0
|
||||
access_purchase_order_mrp_worker,purchase.order mrp_worker,purchase.model_purchase_order,mrp.group_mrp_user,1,0,0,0
|
||||
access_purchase_order_line_mrp_worker,purchase.order.line mrp_worker,purchase.model_purchase_order_line,mrp.group_mrp_user,1,0,0,0
|
||||
access_hr_timesheet_group_mrp_worker,resource.calendar mrp_manager,resource.model_resource_calendar,mrp.group_mrp_manager,1,1,1,1
|
||||
access_procurement_user,procurement.order.user,model_procurement_order,base.group_user,1,1,1,1
|
||||
access_mrp_production_stock_worker,mrp.production stock_worker,model_mrp_production,stock.group_stock_user,1,0,0,0
|
||||
access_report_workcenter_load,report.workcenter.load,model_report_workcenter_load,mrp.group_mrp_manager,1,1,1,1
|
||||
access_report_mrp_inout,report.mrp.inout,model_report_mrp_inout,mrp.group_mrp_manager,1,1,1,1
|
||||
access_ir_property_manager,ir.property manager,base.model_ir_property,mrp.group_mrp_manager,1,1,1,1
|
||||
access_account_sequence_fiscalyear,account.sequence.fiscalyear,account.model_account_sequence_fiscalyear,mrp.group_mrp_user,1,1,1,1
|
||||
access_product_product_user,product.product user,product.model_product_product,mrp.group_mrp_user,1,0,0,0
|
||||
access_product_template_user,product.template user,product.model_product_template,mrp.group_mrp_user,1,0,0,0
|
||||
access_product_uom_user,product.uom user,product.model_product_uom,mrp.group_mrp_user,1,0,0,0
|
||||
access_product_supplierinfo_user,product.supplierinfo user,product.model_product_supplierinfo,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_tracking,stock.tracking,stock.model_stock_tracking,mrp.group_mrp_user,1,1,1,0
|
||||
access_res_partner,res.partner,base.model_res_partner,mrp.group_mrp_user,1,0,0,0
|
||||
access_workcenter_user,mrp.production.workcenter.line.user,model_mrp_production_workcenter_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_leaves_user,mrp.resource.calendar.leaves.user,resource.model_resource_calendar_leaves,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_leaves_manager,mrp.resource.calendar.leaves.manager,resource.model_resource_calendar_leaves,mrp.group_mrp_manager,1,0,0,0
|
||||
access_resource_calendar_attendance_mrp_user,mrp.resource.calendar.attendance.mrp.user,resource.model_resource_calendar_attendance,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_attendance_manager,mrp.resource.calendar.attendance.manager,resource.model_resource_calendar_attendance,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_puom_categ,product.uom.categ,product.model_product_uom_categ,mrp.group_mrp_user,1,0,0,0
|
||||
access_resource_resource,resource.resource,resource.model_resource_resource,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_sequence_fiscalyear_manager,account.sequence.fiscalyear,account.model_account_sequence_fiscalyear,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_supplierinfo_manager,product.supplierinfo user,product.model_product_supplierinfo,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_tracking_manager,stock.tracking,stock.model_stock_tracking,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_production_manager,mrp.production manager,model_mrp_production,mrp.group_mrp_manager,1,0,0,0
|
||||
access_procurement_manager,procurement.order,model_procurement_order,mrp.group_mrp_manager,1,0,0,0
|
||||
access_workcenter_manager,mrp.production.workcenter.line,model_mrp_production_workcenter_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_move_mrp_manager,stock.move mrp_manager,stock.model_stock_move,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_production_product_line_manager,mrp.production.product.line manager,model_mrp_production_product_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_sequence_fiscalyear_system,account.sequence.fiscalyear.system,account.model_account_sequence_fiscalyear,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_production_lot_user,stock.production.lot,stock.model_stock_production_lot,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_warehouse_orderpoint_user,stock.warehouse.orderpoint,procurement.model_stock_warehouse_orderpoint,mrp.group_mrp_user,1,0,0,0
|
||||
access_stock_picking_mrp_manager,stock.picking mrp_manager,stock.model_stock_picking,mrp.group_mrp_manager,1,0,0,0
|
||||
access_report_mrp_inout_user,report.mrp.inout user,model_report_mrp_inout,mrp.group_mrp_user,1,0,0,0
|
||||
access_report_workcenter_load_user,report.workcenter.load.user,model_report_workcenter_load,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_bom_salesman,mrp.bom,model_mrp_bom,base.group_sale_salesman,1,0,0,0
|
||||
access_mrp_bom_stockuser,mrp.bom,model_mrp_bom,stock.group_stock_user,1,0,0,0
|
||||
access_product_uom_categ_mrp_manager,product.uom.categ mrp_manager,product.model_product_uom_categ,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_uom_mrp_manager,product.uom mrp_manager,product.model_product_uom,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_ul_mrp_manager,product.ul mrp_manager,product.model_product_ul,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_category_mrp_manager,product.category mrp_manager,product.model_product_category,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_template_mrp_manager,product.template mrp_manager,product.model_product_template,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_product_mrp_manager,product.product mrp_manager,product.model_product_product,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_packaging_mrp_manager,product.packaging mrp_manager,product.model_product_packaging,mrp.group_mrp_manager,1,1,1,1
|
||||
access_pricelist_partnerinfo_mrp_manager,pricelist.partnerinfo mrp_manager,product.model_pricelist_partnerinfo,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_price_type_mrp_manager,product.price.type mrp_manager,product.model_product_price_type,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_pricelist_type_mrp_manager,product.pricelist.type mrp_manager,product.model_product_pricelist_type,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_pricelist_mrp_manager,product.pricelist mrp_manager,product.model_product_pricelist,mrp.group_mrp_manager,1,1,1,1
|
||||
access_ir_property_group_product_mrp_manager,ir_property group_product_mrp_manager,base.model_ir_property,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_group_res_partner_mrp_manager,res_partner group_mrp_manager,base.model_res_partner,mrp.group_mrp_manager,1,1,1,0
|
||||
access_product_pricelist_version_mrp_manager,product.pricelist.version mrp_manager,product.model_product_pricelist_version,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_pricelist_item_mrp_manager,product.pricelist.item mrp_manager,product.model_product_pricelist_item,mrp.group_mrp_manager,1,1,1,1
|
||||
access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,resource.model_resource_calendar,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
|
||||
access_purchase_order_stock_user,purchase.order stock user,purchase.model_purchase_order,stock.group_stock_user,1,1,1,0
|
||||
access_mrp_bom_purchase_manager,mrp.bom,model_mrp_bom,purchase.group_purchase_manager,1,0,0,0
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_account_analytic_line_user,account.analytic.line,account.model_account_analytic_line,group_mrp_user,1,1,1,0
|
||||
access_mrp_workcenter,mrp.workcenter,model_mrp_workcenter,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_routing,mrp.routing,model_mrp_routing,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_routing_workcenter,mrp.routing.workcenter,model_mrp_routing_workcenter,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_bom,mrp.bom,model_mrp_bom,group_mrp_user,1,0,0,0
|
||||
access_mrp_production,mrp.production user,model_mrp_production,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_production_salesman,mrp.production salesman,model_mrp_production,base.group_sale_salesman,1,1,1,0
|
||||
access_mrp_production_product_line_salesman,mrp.production.product.line salesman,model_mrp_production_product_line,base.group_sale_salesman,1,0,1,0
|
||||
access_mrp_production_workcenter_line_salesman,mrp.production.workcenter.line salesman,model_mrp_production_workcenter_line,base.group_sale_salesman,1,0,1,0
|
||||
access_mrp_production_product_line,mrp.production.product.line,model_mrp_production_product_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_procurement,procurement.order,model_procurement_order,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_workcenter_manager,mrp.workcenter.manager,model_mrp_workcenter,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_routing_manager,mrp.routing.manager,model_mrp_routing,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_routing_workcenter_manager,mrp.routing.workcenter.manager,model_mrp_routing_workcenter,mrp.group_mrp_manager,1,1,1,1
|
||||
access_mrp_bom_manager,mrp.bom.manager,model_mrp_bom,mrp.group_mrp_manager,1,1,1,1
|
||||
access_stock_location_mrp_worker,stock.location mrp_worker,stock.model_stock_location,mrp.group_mrp_user,1,0,0,0
|
||||
access_stock_move_mrp_worker,stock.move mrp_worker,stock.model_stock_move,mrp.group_mrp_user,1,1,1,0
|
||||
access_stock_picking_mrp_worker,stock.picking mrp_worker,stock.model_stock_picking,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_warehouse,stock.warehouse mrp_worker,stock.model_stock_warehouse,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_analytic_journal_mrp_worker,account.analytic.journal mrp_worker,account.model_account_analytic_journal,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_account,account.account mrp_worker,account.model_account_account,mrp.group_mrp_user,1,0,0,0
|
||||
access_hr_timesheet_group_mrp_worker,resource.calendar mrp_manager,resource.model_resource_calendar,mrp.group_mrp_manager,1,1,1,1
|
||||
access_procurement_user,procurement.order.user,model_procurement_order,base.group_user,1,1,1,1
|
||||
access_mrp_production_stock_worker,mrp.production stock_worker,model_mrp_production,stock.group_stock_user,1,0,0,0
|
||||
access_report_workcenter_load,report.workcenter.load,model_report_workcenter_load,mrp.group_mrp_manager,1,1,1,1
|
||||
access_report_mrp_inout,report.mrp.inout,model_report_mrp_inout,mrp.group_mrp_manager,1,1,1,1
|
||||
access_ir_property_manager,ir.property manager,base.model_ir_property,mrp.group_mrp_manager,1,1,1,1
|
||||
access_account_sequence_fiscalyear,account.sequence.fiscalyear,account.model_account_sequence_fiscalyear,mrp.group_mrp_user,1,1,1,1
|
||||
access_product_product_user,product.product user,product.model_product_product,mrp.group_mrp_user,1,0,0,0
|
||||
access_product_template_user,product.template user,product.model_product_template,mrp.group_mrp_user,1,0,0,0
|
||||
access_product_uom_user,product.uom user,product.model_product_uom,mrp.group_mrp_user,1,0,0,0
|
||||
access_product_supplierinfo_user,product.supplierinfo user,product.model_product_supplierinfo,mrp.group_mrp_user,1,1,1,1
|
||||
access_res_partner,res.partner,base.model_res_partner,mrp.group_mrp_user,1,0,0,0
|
||||
access_workcenter_user,mrp.production.workcenter.line.user,model_mrp_production_workcenter_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_leaves_user,mrp.resource.calendar.leaves.user,resource.model_resource_calendar_leaves,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_leaves_manager,mrp.resource.calendar.leaves.manager,resource.model_resource_calendar_leaves,mrp.group_mrp_manager,1,0,0,0
|
||||
access_resource_calendar_attendance_mrp_user,mrp.resource.calendar.attendance.mrp.user,resource.model_resource_calendar_attendance,mrp.group_mrp_user,1,1,1,1
|
||||
access_resource_calendar_attendance_manager,mrp.resource.calendar.attendance.manager,resource.model_resource_calendar_attendance,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_puom_categ,product.uom.categ,product.model_product_uom_categ,mrp.group_mrp_user,1,0,0,0
|
||||
access_resource_resource,resource.resource,resource.model_resource_resource,mrp.group_mrp_user,1,0,0,0
|
||||
access_board_board_user,mrp.board.board,board.model_board_board,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_sequence_fiscalyear_manager,account.sequence.fiscalyear,account.model_account_sequence_fiscalyear,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_supplierinfo_manager,product.supplierinfo user,product.model_product_supplierinfo,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_production_manager,mrp.production manager,model_mrp_production,mrp.group_mrp_manager,1,0,0,0
|
||||
access_procurement_manager,procurement.order,model_procurement_order,mrp.group_mrp_manager,1,0,0,0
|
||||
access_workcenter_manager,mrp.production.workcenter.line,model_mrp_production_workcenter_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_move_mrp_manager,stock.move mrp_manager,stock.model_stock_move,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_production_product_line_manager,mrp.production.product.line manager,model_mrp_production_product_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_sequence_fiscalyear_system,account.sequence.fiscalyear.system,account.model_account_sequence_fiscalyear,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_production_lot_user,stock.production.lot,stock.model_stock_production_lot,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_warehouse_orderpoint_user,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,mrp.group_mrp_user,1,0,0,0
|
||||
access_stock_picking_mrp_manager,stock.picking mrp_manager,stock.model_stock_picking,mrp.group_mrp_manager,1,0,0,0
|
||||
access_report_mrp_inout_user,report.mrp.inout user,model_report_mrp_inout,mrp.group_mrp_user,1,0,0,0
|
||||
access_report_workcenter_load_user,report.workcenter.load.user,model_report_workcenter_load,mrp.group_mrp_user,1,0,0,0
|
||||
access_mrp_bom_salesman,mrp.bom,model_mrp_bom,base.group_sale_salesman,1,0,0,0
|
||||
access_mrp_bom_stockuser,mrp.bom,model_mrp_bom,stock.group_stock_user,1,0,0,0
|
||||
access_product_uom_categ_mrp_manager,product.uom.categ mrp_manager,product.model_product_uom_categ,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_uom_mrp_manager,product.uom mrp_manager,product.model_product_uom,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_ul_mrp_manager,product.ul mrp_manager,product.model_product_ul,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_category_mrp_manager,product.category mrp_manager,product.model_product_category,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_template_mrp_manager,product.template mrp_manager,product.model_product_template,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_product_mrp_manager,product.product mrp_manager,product.model_product_product,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_packaging_mrp_manager,product.packaging mrp_manager,product.model_product_packaging,mrp.group_mrp_manager,1,1,1,1
|
||||
access_pricelist_partnerinfo_mrp_manager,pricelist.partnerinfo mrp_manager,product.model_pricelist_partnerinfo,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_price_type_mrp_manager,product.price.type mrp_manager,product.model_product_price_type,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_pricelist_type_mrp_manager,product.pricelist.type mrp_manager,product.model_product_pricelist_type,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_pricelist_mrp_manager,product.pricelist mrp_manager,product.model_product_pricelist,mrp.group_mrp_manager,1,1,1,1
|
||||
access_ir_property_group_product_mrp_manager,ir_property group_product_mrp_manager,base.model_ir_property,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_group_res_partner_mrp_manager,res_partner group_mrp_manager,base.model_res_partner,mrp.group_mrp_manager,1,1,1,0
|
||||
access_product_pricelist_version_mrp_manager,product.pricelist.version mrp_manager,product.model_product_pricelist_version,mrp.group_mrp_manager,1,1,1,1
|
||||
access_product_pricelist_item_mrp_manager,product.pricelist.item mrp_manager,product.model_product_pricelist_item,mrp.group_mrp_manager,1,1,1,1
|
||||
access_resource_calendar_manufacturinguser,resource.calendar manufacturing.user,resource.model_resource_calendar,mrp.group_mrp_user,1,0,0,0
|
||||
access_account_journal_mrp_manager,account.journal mrp manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
|
||||
access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0
|
|
|
@ -19,22 +19,50 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import time
|
||||
|
||||
from openerp.osv import fields
|
||||
from openerp.osv import osv
|
||||
|
||||
from openerp.tools.translate import _
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
|
||||
class StockMove(osv.osv):
|
||||
_inherit = 'stock.move'
|
||||
|
||||
|
||||
_columns = {
|
||||
'production_id': fields.many2one('mrp.production', 'Production', select=True),
|
||||
'production_id': fields.many2one('mrp.production', 'Production Order for Produced Products', select=True),
|
||||
'raw_material_production_id': fields.many2one('mrp.production', 'Production Order for Raw Materials', select=True),
|
||||
'consumed_for': fields.many2one('stock.move', 'Consumed for', help='Technical field used to make the traceability of produced products'),
|
||||
}
|
||||
|
||||
def create_chained_picking(self, cr, uid, moves, context=None):
|
||||
new_moves = super(StockMove, self).create_chained_picking(cr, uid, moves, context=context)
|
||||
self.write(cr, uid, [x.id for x in new_moves], {'production_id': False}, context=context)
|
||||
return new_moves
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
default = {}
|
||||
default['production_id'] = False
|
||||
return super(StockMove, self).copy(cr, uid, id, default, context=context)
|
||||
|
||||
def check_tracking(self, cr, uid, move, lot_id, context=None):
|
||||
super(StockMove, self).check_tracking(cr, uid, move, lot_id, context=context)
|
||||
if move.product_id.track_production and (move.location_id.usage == 'production' or move.location_dest_id.usage == 'production') and not lot_id:
|
||||
raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name))
|
||||
if move.raw_material_production_id and move.location_dest_id.usage == 'production' and move.raw_material_production_id.product_id.track_production and not move.consumed_for:
|
||||
raise osv.except_osv(_('Warning!'), _("Because the product %s requires it, you must assign a serial number to your raw material %s to proceed further in your production. Please use the 'Produce' button to do so.") % (move.raw_material_production_id.product_id.name, move.product_id.name))
|
||||
|
||||
def _check_phantom_bom(self, cr, uid, move, context=None):
|
||||
"""check if product associated to move has a phantom bom
|
||||
return list of ids of mrp.bom for that product """
|
||||
user_company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
|
||||
#doing the search as SUPERUSER because a user with the permission to write on a stock move should be able to explode it
|
||||
#without giving him the right to read the boms.
|
||||
return self.pool.get('mrp.bom').search(cr, SUPERUSER_ID, [
|
||||
('product_id', '=', move.product_id.id),
|
||||
('bom_id', '=', False),
|
||||
('type', '=', 'phantom'),
|
||||
'|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
|
||||
('company_id', '=', user_company)], context=context)
|
||||
|
||||
def _action_explode(self, cr, uid, move, context=None):
|
||||
""" Explodes pickings.
|
||||
@param move: Stock moves
|
||||
|
@ -42,101 +70,120 @@ class StockMove(osv.osv):
|
|||
"""
|
||||
bom_obj = self.pool.get('mrp.bom')
|
||||
move_obj = self.pool.get('stock.move')
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
product_obj = self.pool.get('product.product')
|
||||
processed_ids = [move.id]
|
||||
if move.product_id.supply_method == 'produce':
|
||||
bis = bom_obj.search(cr, uid, [
|
||||
('product_id','=',move.product_id.id),
|
||||
('bom_id','=',False),
|
||||
('type','=','phantom')])
|
||||
if bis:
|
||||
factor = move.product_qty
|
||||
bom_point = bom_obj.browse(cr, uid, bis[0], context=context)
|
||||
res = bom_obj._bom_explode(cr, uid, bom_point, factor, [])
|
||||
state = 'confirmed'
|
||||
if move.state == 'assigned':
|
||||
state = 'assigned'
|
||||
for line in res[0]:
|
||||
valdef = {
|
||||
'picking_id': move.picking_id.id,
|
||||
'product_id': line['product_id'],
|
||||
'product_uom': line['product_uom'],
|
||||
'product_qty': line['product_qty'],
|
||||
'product_uos': line['product_uos'],
|
||||
'product_uos_qty': line['product_uos_qty'],
|
||||
'move_dest_id': move.id,
|
||||
'state': state,
|
||||
'name': line['name'],
|
||||
'move_history_ids': [(6,0,[move.id])],
|
||||
'move_history_ids2': [(6,0,[])],
|
||||
'procurements': [],
|
||||
}
|
||||
mid = move_obj.copy(cr, uid, move.id, default=valdef)
|
||||
processed_ids.append(mid)
|
||||
prodobj = product_obj.browse(cr, uid, line['product_id'], context=context)
|
||||
proc_id = procurement_obj.create(cr, uid, {
|
||||
'name': (move.picking_id.origin or ''),
|
||||
'origin': (move.picking_id.origin or ''),
|
||||
'date_planned': move.date,
|
||||
'product_id': line['product_id'],
|
||||
'product_qty': line['product_qty'],
|
||||
'product_uom': line['product_uom'],
|
||||
'product_uos_qty': line['product_uos'] and line['product_uos_qty'] or False,
|
||||
'product_uos': line['product_uos'],
|
||||
'location_id': move.location_id.id,
|
||||
'procure_method': prodobj.procure_method,
|
||||
'move_id': mid,
|
||||
})
|
||||
procurement_obj.signal_button_confirm(cr, uid, [proc_id])
|
||||
|
||||
move_obj.write(cr, uid, [move.id], {
|
||||
'location_dest_id': move.location_id.id, # dummy move for the kit
|
||||
'auto_validate': True,
|
||||
'picking_id': False,
|
||||
'state': 'confirmed'
|
||||
})
|
||||
procurement_ids = procurement_obj.search(cr, uid, [('move_id','=',move.id)], context)
|
||||
procurement_obj.signal_button_confirm(cr, uid, procurement_ids)
|
||||
procurement_obj.signal_button_wait_done(cr, uid, procurement_ids)
|
||||
return processed_ids
|
||||
|
||||
def action_consume(self, cr, uid, ids, product_qty, location_id=False, context=None):
|
||||
""" Consumed product with specific quatity from specific source location.
|
||||
to_explode_again_ids = []
|
||||
processed_ids = []
|
||||
bis = self._check_phantom_bom(cr, uid, move, context=context)
|
||||
if bis:
|
||||
factor = move.product_qty
|
||||
bom_point = bom_obj.browse(cr, SUPERUSER_ID, bis[0], context=context)
|
||||
res = bom_obj._bom_explode(cr, SUPERUSER_ID, bom_point, factor, [])
|
||||
state = 'confirmed'
|
||||
if move.state == 'assigned':
|
||||
state = 'assigned'
|
||||
for line in res[0]:
|
||||
valdef = {
|
||||
'picking_id': move.picking_id.id if move.picking_id else False,
|
||||
'product_id': line['product_id'],
|
||||
'product_uom': line['product_uom'],
|
||||
'product_uom_qty': line['product_qty'],
|
||||
'product_uos': line['product_uos'],
|
||||
'product_uos_qty': line['product_uos_qty'],
|
||||
'state': state,
|
||||
'name': line['name'],
|
||||
}
|
||||
mid = move_obj.copy(cr, uid, move.id, default=valdef)
|
||||
to_explode_again_ids.append(mid)
|
||||
|
||||
#delete the move with original product which is not relevant anymore
|
||||
move_obj.unlink(cr, SUPERUSER_ID, [move.id], context=context)
|
||||
#check if new moves needs to be exploded
|
||||
if to_explode_again_ids:
|
||||
for new_move in self.browse(cr, uid, to_explode_again_ids, context=context):
|
||||
processed_ids.extend(self._action_explode(cr, uid, new_move, context=context))
|
||||
#return list of newly created move or the move id otherwise
|
||||
return processed_ids or [move.id]
|
||||
|
||||
def action_confirm(self, cr, uid, ids, context=None):
|
||||
move_ids = []
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
#in order to explode a move, we must have a picking_type_id on that move because otherwise the move
|
||||
#won't be assigned to a picking and it would be weird to explode a move into several if they aren't
|
||||
#all grouped in the same picking.
|
||||
if move.picking_type_id:
|
||||
move_ids.extend(self._action_explode(cr, uid, move, context=context))
|
||||
else:
|
||||
move_ids.append(move.id)
|
||||
|
||||
#we go further with the list of ids potentially changed by action_explode
|
||||
return super(StockMove, self).action_confirm(cr, uid, move_ids, context=context)
|
||||
|
||||
def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False,
|
||||
consumed_for=False, context=None):
|
||||
""" Consumed product with specific quantity from specific source location.
|
||||
@param product_qty: Consumed product quantity
|
||||
@param location_id: Source location
|
||||
@param restrict_lot_id: optionnal parameter that allows to restrict the choice of quants on this specific lot
|
||||
@param restrict_partner_id: optionnal parameter that allows to restrict the choice of quants to this specific partner
|
||||
@param consumed_for: optionnal parameter given to this function to make the link between raw material consumed and produced product, for a better traceability
|
||||
@return: Consumed lines
|
||||
"""
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
res = []
|
||||
production_obj = self.pool.get('mrp.production')
|
||||
for move in self.browse(cr, uid, ids):
|
||||
move.action_confirm(context)
|
||||
new_moves = super(StockMove, self).action_consume(cr, uid, [move.id], product_qty, location_id, context=context)
|
||||
uom_obj = self.pool.get('product.uom')
|
||||
|
||||
if product_qty <= 0:
|
||||
raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
|
||||
#because of the action_confirm that can create extra moves in case of phantom bom, we need to make 2 loops
|
||||
ids2 = []
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
if move.state == 'draft':
|
||||
ids2.extend(self.action_confirm(cr, uid, [move.id], context=context))
|
||||
else:
|
||||
ids2.append(move.id)
|
||||
|
||||
for move in self.browse(cr, uid, ids2, context=context):
|
||||
move_qty = move.product_qty
|
||||
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, product_qty, move.product_uom.id)
|
||||
if move_qty <= 0:
|
||||
raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
|
||||
quantity_rest = move.product_qty - uom_qty
|
||||
if quantity_rest > 0:
|
||||
ctx = context.copy()
|
||||
if location_id:
|
||||
ctx['source_location_id'] = location_id
|
||||
new_mov = self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=ctx)
|
||||
self.write(cr, uid, new_mov, {'consumed_for': consumed_for}, context=context)
|
||||
res.append(new_mov)
|
||||
else:
|
||||
res.append(move.id)
|
||||
if location_id:
|
||||
self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id,
|
||||
'restrict_partner_id': restrict_partner_id,
|
||||
'consumed_for': consumed_for}, context=context)
|
||||
self.action_done(cr, uid, res, context=context)
|
||||
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
|
||||
for prod in production_obj.browse(cr, uid, production_ids, context=context):
|
||||
if prod.state == 'confirmed':
|
||||
production_obj.force_production(cr, uid, [prod.id])
|
||||
production_obj.signal_button_produce(cr, uid, production_ids)
|
||||
for new_move in new_moves:
|
||||
if new_move == move.id:
|
||||
#This move is already there in move lines of production order
|
||||
continue
|
||||
production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
|
||||
res.append(new_move)
|
||||
production_obj.signal_button_produce(cr, uid, production_ids)
|
||||
for new_move in res:
|
||||
if new_move != move.id:
|
||||
#This move is not already there in move lines of production order
|
||||
production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
|
||||
return res
|
||||
|
||||
def action_scrap(self, cr, uid, ids, product_qty, location_id, context=None):
|
||||
|
||||
def action_scrap(self, cr, uid, ids, product_qty, location_id, restrict_lot_id=False, restrict_partner_id=False, context=None):
|
||||
""" Move the scrap/damaged product into scrap location
|
||||
@param product_qty: Scraped product quantity
|
||||
@param location_id: Scrap location
|
||||
@return: Scraped lines
|
||||
"""
|
||||
"""
|
||||
res = []
|
||||
production_obj = self.pool.get('mrp.production')
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
new_moves = super(StockMove, self).action_scrap(cr, uid, [move.id], product_qty, location_id, context=context)
|
||||
new_moves = super(StockMove, self).action_scrap(cr, uid, [move.id], product_qty, location_id,
|
||||
restrict_lot_id=restrict_lot_id,
|
||||
restrict_partner_id=restrict_partner_id, context=context)
|
||||
#If we are not scrapping our whole move, tracking and lot references must not be removed
|
||||
#self.write(cr, uid, [move.id], {'prodlot_id': False, 'tracking_id': False})
|
||||
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
|
||||
for prod_id in production_ids:
|
||||
production_obj.signal_button_produce(cr, uid, [prod_id])
|
||||
|
@ -145,37 +192,92 @@ class StockMove(osv.osv):
|
|||
res.append(new_move)
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
res = super(StockMove, self).write(cr, uid, ids, vals, context=context)
|
||||
from openerp import workflow
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
if move.raw_material_production_id and move.raw_material_production_id.state == 'confirmed':
|
||||
workflow.trg_trigger(uid, 'stock.move', move.id, cr)
|
||||
return res
|
||||
|
||||
class stock_warehouse(osv.osv):
|
||||
_inherit = 'stock.warehouse'
|
||||
_columns = {
|
||||
'manufacture_to_resupply': fields.boolean('Manufacture in this Warehouse',
|
||||
help="When products are manufactured, they can be manufactured in this warehouse."),
|
||||
'manufacture_pull_id': fields.many2one('procurement.rule', 'Manufacture Rule'),
|
||||
}
|
||||
|
||||
class StockPicking(osv.osv):
|
||||
_inherit = 'stock.picking'
|
||||
def _get_manufacture_pull_rule(self, cr, uid, warehouse, context=None):
|
||||
route_obj = self.pool.get('stock.location.route')
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
try:
|
||||
manufacture_route_id = data_obj.get_object_reference(cr, uid, 'stock', 'route_warehouse0_manufacture')[1]
|
||||
except:
|
||||
manufacture_route_id = route_obj.search(cr, uid, [('name', 'like', _('Manufacture'))], context=context)
|
||||
manufacture_route_id = manufacture_route_id and manufacture_route_id[0] or False
|
||||
if not manufacture_route_id:
|
||||
raise osv.except_osv(_('Error!'), _('Can\'t find any generic Manufacture route.'))
|
||||
|
||||
#
|
||||
# Explode picking by replacing phantom BoMs
|
||||
#
|
||||
def action_explode(self, cr, uid, move_ids, *args):
|
||||
"""Explodes moves by expanding kit components"""
|
||||
move_obj = self.pool.get('stock.move')
|
||||
todo = move_ids[:]
|
||||
for move in move_obj.browse(cr, uid, move_ids):
|
||||
todo.extend(move_obj._action_explode(cr, uid, move))
|
||||
return list(set(todo))
|
||||
return {
|
||||
'name': self._format_routename(cr, uid, warehouse, _(' Manufacture'), context=context),
|
||||
'location_id': warehouse.lot_stock_id.id,
|
||||
'route_id': manufacture_route_id,
|
||||
'action': 'manufacture',
|
||||
'picking_type_id': warehouse.int_type_id.id,
|
||||
'propagate': False,
|
||||
'warehouse_id': warehouse.id,
|
||||
}
|
||||
|
||||
def create_routes(self, cr, uid, ids, warehouse, context=None):
|
||||
pull_obj = self.pool.get('procurement.rule')
|
||||
res = super(stock_warehouse, self).create_routes(cr, uid, ids, warehouse, context=context)
|
||||
if warehouse.manufacture_to_resupply:
|
||||
manufacture_pull_vals = self._get_manufacture_pull_rule(cr, uid, warehouse, context=context)
|
||||
manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context)
|
||||
res['manufacture_pull_id'] = manufacture_pull_id
|
||||
return res
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
pull_obj = self.pool.get('procurement.rule')
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
class split_in_production_lot(osv.osv_memory):
|
||||
_inherit = "stock.move.split"
|
||||
if 'manufacture_to_resupply' in vals:
|
||||
if vals.get("manufacture_to_resupply"):
|
||||
for warehouse in self.browse(cr, uid, ids, context=context):
|
||||
if not warehouse.manufacture_pull_id:
|
||||
manufacture_pull_vals = self._get_manufacture_pull_rule(cr, uid, warehouse, context=context)
|
||||
manufacture_pull_id = pull_obj.create(cr, uid, manufacture_pull_vals, context=context)
|
||||
vals['manufacture_pull_id'] = manufacture_pull_id
|
||||
else:
|
||||
for warehouse in self.browse(cr, uid, ids, context=context):
|
||||
if warehouse.manufacture_pull_id:
|
||||
pull_obj.unlink(cr, uid, warehouse.manufacture_pull_id.id, context=context)
|
||||
return super(stock_warehouse, self).write(cr, uid, ids, vals, context=None)
|
||||
|
||||
def split(self, cr, uid, ids, move_ids, context=None):
|
||||
""" Splits move lines into given quantities.
|
||||
@param move_ids: Stock moves.
|
||||
@return: List of new moves.
|
||||
"""
|
||||
new_moves = super(split_in_production_lot, self).split(cr, uid, ids, move_ids, context=context)
|
||||
production_obj = self.pool.get('mrp.production')
|
||||
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', move_ids)])
|
||||
production_obj.write(cr, uid, production_ids, {'move_lines': [(4, m) for m in new_moves]})
|
||||
return new_moves
|
||||
def get_all_routes_for_wh(self, cr, uid, warehouse, context=None):
|
||||
all_routes = super(stock_warehouse, self).get_all_routes_for_wh(cr, uid, warehouse, context=context)
|
||||
if warehouse.manufacture_to_resupply and warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id:
|
||||
all_routes += [warehouse.manufacture_pull_id.route_id.id]
|
||||
return all_routes
|
||||
|
||||
def _handle_renaming(self, cr, uid, warehouse, name, code, context=None):
|
||||
res = super(stock_warehouse, self)._handle_renaming(cr, uid, warehouse, name, code, context=context)
|
||||
pull_obj = self.pool.get('procurement.rule')
|
||||
#change the manufacture pull rule name
|
||||
if warehouse.manufacture_pull_id:
|
||||
pull_obj.write(cr, uid, warehouse.manufacture_pull_id.id, {'name': warehouse.manufacture_pull_id.name.replace(warehouse.name, name, 1)}, context=context)
|
||||
return res
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
def _get_all_products_to_resupply(self, cr, uid, warehouse, context=None):
|
||||
res = super(stock_warehouse, self)._get_all_products_to_resupply(cr, uid, warehouse, context=context)
|
||||
if warehouse.manufacture_pull_id and warehouse.manufacture_pull_id.route_id:
|
||||
for product_id in res:
|
||||
for route in self.pool.get('product.product').browse(cr, uid, product_id, context=context).route_ids:
|
||||
if route.id == warehouse.manufacture_pull_id.route_id.id:
|
||||
res.remove(product_id)
|
||||
break
|
||||
return res
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
I make the production order using BoM having one service type product and one consumable product.
|
||||
-
|
||||
!record {model: mrp.production, id: mrp_production_servicetype_mo1}:
|
||||
product_id: product.product_product_5
|
||||
product_id: product.product_product_3
|
||||
product_qty: 1.0
|
||||
bom_id: mrp_bom_test1
|
||||
date_planned: !eval time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
@ -36,31 +36,25 @@
|
|||
-
|
||||
!workflow {model: mrp.production, action: button_confirm, ref: mrp_production_servicetype_mo1}
|
||||
-
|
||||
I confirm the Consume Products.
|
||||
I reserved the product.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
order = self.browse(cr, uid, ref("mrp_production_servicetype_mo1"))
|
||||
assert order.state == 'confirmed', "Production order should be confirmed."
|
||||
for move_line in order.move_lines:
|
||||
move_line.action_consume(move_line.product_qty)
|
||||
-
|
||||
I processed the Product Entirely.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
order = self.browse(cr, uid, ref("mrp_production_servicetype_mo1"))
|
||||
assert order.state == 'in_production', 'Production order should be in production State.'
|
||||
for move_created in order.move_created_ids:
|
||||
move_created.action_done()
|
||||
self.force_production(cr, uid, [order.id])
|
||||
|
||||
-
|
||||
I produce product.
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
context.update({'active_id': ref('mrp_production_servicetype_mo1')})
|
||||
-
|
||||
!record {model: mrp.product.produce, id: mrp_product_produce_1}:
|
||||
!record {model: mrp.product.produce, id: mrp_product_produce_1, view: mrp.view_mrp_product_produce_wizard}:
|
||||
mode: 'consume_produce'
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
lines = self.on_change_qty(cr, uid, [ref('mrp_product_produce_1')], 1.0, [], context=context)
|
||||
self.write(cr, uid, [ref('mrp_product_produce_1')], lines['value'], context=context)
|
||||
self.do_produce(cr, uid, [ref('mrp_product_produce_1')], context=context)
|
||||
-
|
||||
I check production order after produced.
|
||||
|
@ -68,67 +62,3 @@
|
|||
!python {model: mrp.production}: |
|
||||
order = self.browse(cr, uid, ref("mrp_production_servicetype_mo1"))
|
||||
assert order.state == 'done', "Production order should be closed."
|
||||
-
|
||||
I create Bill of Materials with two service type products.
|
||||
-
|
||||
!record {model: mrp.bom, id: mrp_bom_test_2}:
|
||||
company_id: base.main_company
|
||||
name: PC Assemble SC234
|
||||
product_id: product.product_product_3
|
||||
product_qty: 1.0
|
||||
type: normal
|
||||
bom_lines:
|
||||
- company_id: base.main_company
|
||||
name: On Site Monitoring
|
||||
product_id: product.product_product_1
|
||||
product_qty: 1.0
|
||||
- company_id: base.main_company
|
||||
name: On Site Assistance
|
||||
product_id: product.product_product_2
|
||||
product_qty: 1.0
|
||||
-
|
||||
I make the production order using BoM having two service type products.
|
||||
-
|
||||
!record {model: mrp.production, id: mrp_production_servicetype_2}:
|
||||
product_id: product.product_product_5
|
||||
product_qty: 1.0
|
||||
bom_id: mrp_bom_test_2
|
||||
date_planned: !eval time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
-
|
||||
I compute the data of production order.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
self.action_compute(cr, uid, [ref("mrp_production_servicetype_2")], {"lang": "en_US", "tz": False, "search_default_Current": 1,
|
||||
"active_model": "ir.ui.menu", "active_ids": [ref("mrp.menu_mrp_production_action")],
|
||||
"active_id": ref("mrp.menu_mrp_production_action"), })
|
||||
-
|
||||
I confirm the production order.
|
||||
-
|
||||
!workflow {model: mrp.production, action: button_confirm, ref: mrp_production_servicetype_2}
|
||||
-
|
||||
Now I start production.
|
||||
-
|
||||
!workflow {model: mrp.production, action: button_produce, ref: mrp_production_servicetype_2}
|
||||
-
|
||||
I check that production order in production state after start production.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
order = self.browse(cr, uid, ref("mrp_production_servicetype_2"))
|
||||
assert order.state == 'in_production', 'Production order should be in production State.'
|
||||
-
|
||||
I produce product.
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
context.update({'active_id': ref('mrp_production_servicetype_2')})
|
||||
-
|
||||
!record {model: mrp.product.produce, id: mrp_product_produce_2}:
|
||||
mode: 'consume_produce'
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
self.do_produce(cr, uid, [ref('mrp_product_produce_2')], context=context)
|
||||
-
|
||||
I check production order after produced.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
order = self.browse(cr, uid, ref("mrp_production_servicetype_2"))
|
||||
assert order.state == 'done', "Production order should be closed."
|
||||
|
|
|
@ -7,12 +7,6 @@
|
|||
I first confirm order for PC Assemble SC349.
|
||||
-
|
||||
!workflow {model: mrp.production, action: button_confirm, ref: mrp_production_test1}
|
||||
-
|
||||
In order to cancel the production order, I first cancel its picking.
|
||||
-
|
||||
!function {model: stock.picking, name: action_cancel}:
|
||||
- model: mrp.production
|
||||
eval: "[obj(ref('mrp_production_test1')).picking_id.id]"
|
||||
-
|
||||
Now I cancel the production order.
|
||||
-
|
||||
|
|
|
@ -39,31 +39,31 @@
|
|||
move = order.move_created_ids[0]
|
||||
source_location_id = order.product_id.property_stock_production.id
|
||||
assert move.date == order.date_planned, "Planned date is not correspond."
|
||||
assert move.product_id.id == order.product_id.id, "Product is not correspond."
|
||||
assert move.product_uom.id == order.product_uom.id, "UOM is not correspond."
|
||||
assert move.product_qty == order.product_qty, "Qty is not correspond."
|
||||
assert move.product_uos_qty == order.product_uos and order.product_uos_qty or order.product_qty, "UOS qty is not correspond."
|
||||
assert move.product_id.id == order.product_id.id, "Product does not correspond."
|
||||
assert move.product_uom.id == order.product_uom.id, "UOM does not correspond."
|
||||
assert move.product_qty == order.product_qty, "Qty does not correspond."
|
||||
assert move.product_uos_qty == order.product_uos and order.product_uos_qty or order.product_qty, "UOS qty does not correspond."
|
||||
if order.product_uos:
|
||||
assert move.product_uos.id == order.product_uos.id, "UOS is not correspond."
|
||||
assert move.location_id.id == source_location_id, "Source Location is not correspond."
|
||||
assert move.location_dest_id.id == order.location_dest_id.id, "Destination Location is not correspond."
|
||||
assert move.product_uos.id == order.product_uos.id, "UOS does not correspond."
|
||||
assert move.location_id.id == source_location_id, "Source Location does not correspond."
|
||||
assert move.location_dest_id.id == order.location_dest_id.id, "Destination Location does not correspond."
|
||||
routing_loc = None
|
||||
if order.bom_id.routing_id and order.bom_id.routing_id.location_id:
|
||||
routing_loc = order.bom_id.routing_id.location_id.id
|
||||
date_planned = order.date_planned
|
||||
for move_line in order.move_lines:
|
||||
for order_line in order.product_lines:
|
||||
if move_line.product_id.type not in ('product', 'consu'):
|
||||
continue
|
||||
if move_line.product_id.id == order_line.product_id.id:
|
||||
assert move_line.date == date_planned, "Planned date is not correspond in 'To consume line'."
|
||||
assert move_line.product_qty == order_line.product_qty, "Qty is not correspond in 'To consume line'."
|
||||
assert move_line.product_uom.id == order_line.product_uom.id, "UOM is not correspond in 'To consume line'."
|
||||
assert move_line.product_uos_qty == order_line.product_uos and order_line.product_uos_qty or order_line.product_qty, "UOS qty is not correspond in 'To consume line'."
|
||||
if order_line.product_uos:
|
||||
assert move_line.product_uos.id == order_line.product_uos.id, "UOS is not correspond in 'To consume line'."
|
||||
assert move_line.location_id.id == routing_loc or order.location_src_id.id, "Source location is not correspond in 'To consume line'."
|
||||
assert move_line.location_dest_id.id == source_location_id, "Destination Location is not correspond in 'To consume line'."
|
||||
if move_line.product_id.type not in ('product', 'consu'):
|
||||
continue
|
||||
if move_line.product_id.id == order_line.product_id.id:
|
||||
assert move_line.date == date_planned, "Planned date does not correspond in 'To consume line'."
|
||||
assert move_line.product_qty == order_line.product_qty, "Qty does not correspond in 'To consume line'."
|
||||
assert move_line.product_uom.id == order_line.product_uom.id, "UOM does not correspond in 'To consume line'."
|
||||
assert move_line.product_uos_qty == order_line.product_uos and order_line.product_uos_qty or order_line.product_qty, "UOS qty is not correspond in 'To consume line'."
|
||||
if order_line.product_uos:
|
||||
assert move_line.product_uos.id == order_line.product_uos.id, "UOS is not correspond in 'To consume line'."
|
||||
assert move_line.location_id.id == routing_loc or order.location_src_id.id, "Source location is not correspond in 'To consume line'."
|
||||
assert move_line.location_dest_id.id == source_location_id, "Destination Location is not correspond in 'To consume line'."
|
||||
-
|
||||
I consume raw materials and put one material in scrap location due to waste it.
|
||||
-
|
||||
|
@ -75,55 +75,30 @@
|
|||
if move.product_id.id == ref("product.product_product_6"):
|
||||
move.action_scrap(5.0, scrap_location_id)
|
||||
-
|
||||
I check details of an Internal Shipment after confirmed production order to bring components in Raw Materials Location.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
procurement = self.pool.get('procurement.order')
|
||||
order = self.browse(cr, uid, ref("mrp_production_test1"))
|
||||
assert order.picking_id, 'Internal Shipment should be created!'
|
||||
|
||||
routing_loc = None
|
||||
pick_type = 'internal'
|
||||
partner_id = False
|
||||
if order.bom_id.routing_id and order.bom_id.routing_id.location_id:
|
||||
routing_loc = order.bom_id.routing_id.location_id
|
||||
if routing_loc.usage != 'internal':
|
||||
pick_type = 'out'
|
||||
partner_id = routing_loc.partner_id and routing_loc.partner_id.id or False
|
||||
routing_loc = routing_loc.id
|
||||
assert order.picking_id.type == pick_type, "Shipment should be Internal."
|
||||
assert order.picking_id.partner_id.id == partner_id, "Shipment Address is not correspond with Adderss of Routing Location."
|
||||
date_planned = order.date_planned
|
||||
for move_line in order.picking_id.move_lines:
|
||||
for order_line in order.product_lines:
|
||||
if move_line.product_id.type not in ('product', 'consu'):
|
||||
continue
|
||||
if move_line.product_id.id == order_line.product_id.id:
|
||||
assert move_line.date == date_planned, "Planned date is not correspond."
|
||||
assert move_line.product_qty == order_line.product_qty, "Qty is not correspond."
|
||||
assert move_line.product_uom.id == order_line.product_uom.id, "UOM is not correspond."
|
||||
assert move_line.product_uos_qty == order_line.product_uos and order_line.product_uos_qty or order_line.product_qty, "UOS qty is not correspond."
|
||||
if order_line.product_uos:
|
||||
assert move_line.product_uos.id == order_line.product_uos.id, "UOS is not correspond."
|
||||
assert move_line.location_id.id == order.location_src_id.id, "Source location is not correspond."
|
||||
assert move_line.location_dest_id.id == routing_loc or order.location_src_id.id, "Destination Location is not correspond."
|
||||
procurement_ids = procurement.search(cr, uid, [('move_id','=',move_line.id)])
|
||||
assert procurement_ids, "Procurement should be created for shipment line of raw materials."
|
||||
shipment_procurement = procurement.browse(cr, uid, procurement_ids[0], context=context)
|
||||
# procurement state should be `confirmed` at this stage, except if mrp_jit is installed, in which
|
||||
# case it could already be in `running` or `exception` state (not enough stock)
|
||||
expected_states = ('confirmed', 'running', 'exception')
|
||||
assert shipment_procurement.state in expected_states, 'Procurement state is `%s` for %s, expected one of %s' % \
|
||||
(shipment_procurement.state, shipment_procurement.product_id.name, expected_states)
|
||||
assert shipment_procurement.date_planned == date_planned, "Planned date is not correspond in procurement."
|
||||
assert shipment_procurement.product_id.id == order_line.product_id.id, "Product is not correspond in procurement."
|
||||
assert shipment_procurement.product_qty == order_line.product_qty, "Qty is not correspond in procurement."
|
||||
assert shipment_procurement.product_uom.id == order_line.product_uom.id, "UOM is not correspond in procurement."
|
||||
assert shipment_procurement.product_uos_qty == order_line.product_uos and order_line.product_uos_qty or order_line.product_qty, "UOS qty is not correspond in procurement."
|
||||
if order_line.product_uos:
|
||||
assert shipment_procurement.product_uos.id == order_line.product_uos.id, "UOS is not correspond in procurement."
|
||||
assert shipment_procurement.location_id.id == order.location_src_id.id, "Location is not correspond in procurement."
|
||||
assert shipment_procurement.procure_method == order_line.product_id.procure_method, "Procure method is not correspond in procurement."
|
||||
I check procurements have been generated for every consume line
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
order = self.pool.get("mrp.production").browse(cr, uid, ref("mrp_production_test1"))
|
||||
move_line_ids = [x.id for x in order.move_lines]
|
||||
procurements = self.search(cr, uid, [('move_dest_id', 'in', move_line_ids)])
|
||||
for proc in self.browse(cr, uid, procurements):
|
||||
for order_line in order.product_lines:
|
||||
date_planned = order.date_planned
|
||||
if proc.product_id.type not in ('product', 'consu'):
|
||||
continue
|
||||
if proc.product_id.id == order_line.product_id.id:
|
||||
assert proc.date_planned == date_planned, "Planned date does not correspond"
|
||||
assert proc.product_qty == order_line.product_qty, "Qty does not correspond"
|
||||
assert proc.location_id.id == order.location_src_id.id, "Input location and procurement location do not correspond"
|
||||
assert proc.product_uom.id == order_line.product_uom.id, "UOM does not correspond in procurement."
|
||||
assert proc.product_uos_qty == order_line.product_uos and order_line.product_uos_qty or order_line.product_qty, "UOS qty does not correspond in procurement."
|
||||
# procurement state should be `confirmed` at this stage, except if procurement_jit is installed, in which
|
||||
# case it could already be in `running` or `exception` state (not enough stock)
|
||||
expected_states = ('confirmed', 'running', 'exception')
|
||||
assert proc.state in expected_states, 'Procurement state is `%s` for %s, expected one of %s' % \
|
||||
(proc.state, proc.product_id.name, expected_states)
|
||||
if order_line.product_uos:
|
||||
assert proc.product_uos.id == order_line.product_uos.id, "UOS is not correspond in procurement."
|
||||
-
|
||||
I change production qty with 3 PC Assemble SC349.
|
||||
-
|
||||
|
@ -149,17 +124,16 @@
|
|||
!python {model: procurement.order}: |
|
||||
self.run_scheduler(cr, uid)
|
||||
-
|
||||
The production order is Waiting Goods, I forcefully done internal shipment.
|
||||
The production order is Waiting Goods, will force production which should set consume lines as available
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
self.force_production(cr, uid, [ref("mrp_production_test1")])
|
||||
-
|
||||
I check that production order in ready state after forcefully done internal shipment.
|
||||
I check that production order in ready state after forcing production.
|
||||
-
|
||||
!python {model: mrp.production}: |
|
||||
order = self.browse(cr, uid, ref("mrp_production_test1"))
|
||||
assert order.state == 'ready', 'Production order should be in Ready State.'
|
||||
assert order.picking_id.state == 'done', 'Internal shipment should be done.'
|
||||
-
|
||||
Now I start production.
|
||||
-
|
||||
|
@ -171,7 +145,6 @@
|
|||
order = self.browse(cr, uid, ref("mrp_production_test1"))
|
||||
assert order.state == 'in_production', 'Production order should be in production State.'
|
||||
-
|
||||
move.action_consume(move.product_qty)
|
||||
I produce product.
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
|
@ -181,6 +154,9 @@
|
|||
mode: 'consume_produce'
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
qty = self.browse(cr, uid, ref('mrp_product_produce1')).product_qty
|
||||
lines = self.on_change_qty(cr, uid, [ref('mrp_product_produce1')], qty, [], context=context)
|
||||
self.write(cr, uid, [ref('mrp_product_produce1')], lines['value'], context=context)
|
||||
self.do_produce(cr, uid, [ref('mrp_product_produce1')], context=context)
|
||||
-
|
||||
I check production order after produced.
|
||||
|
@ -222,8 +198,8 @@
|
|||
raise AssertionError('unknown cost line: %s' % line)
|
||||
assert line.general_account_id.id == wc.costs_general_account_id.id, "General Account is not correspond."
|
||||
assert line.journal_id.id == wc.costs_journal_id.id, "Account Journal is not correspond."
|
||||
assert line.product_id.id == wc.product_id.id, "Product is not correspond."
|
||||
assert line.product_uom_id.id == wc.product_id.uom_id.id, "UOM is not correspond."
|
||||
assert line.product_id.id == wc.product_id.id, "Product does not correspond."
|
||||
assert line.product_uom_id.id == wc.product_id.uom_id.id, "UOM does not correspond."
|
||||
-
|
||||
I print "Work Center Load Report".
|
||||
-
|
||||
|
@ -233,4 +209,3 @@
|
|||
data_dict = {'time_unit': 'day', 'measure_unit': 'hours'}
|
||||
from openerp.tools import test_reports
|
||||
test_reports.try_report_action(cr, uid, 'action_mrp_workcenter_load_wizard',wiz_data=data_dict, context=ctx, our_module='mrp')
|
||||
|
||||
|
|
|
@ -33,9 +33,9 @@ class TestMrpMulticompany(common.TransactionCase):
|
|||
self.res_users = self.registry('res.users')
|
||||
self.stock_location = self.registry('stock.location')
|
||||
|
||||
model, group_user_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
model, group_stock_manager_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'group_stock_manager')
|
||||
model, company_2_id = self.registry('ir.model.data').get_object_reference(cr, uid, 'stock', 'res_company_2')
|
||||
group_user_id = self.registry('ir.model.data').xmlid_to_res_id(cr, uid, 'base.group_user')
|
||||
group_stock_manager_id = self.registry('ir.model.data').xmlid_to_res_id(cr, uid, 'stock.group_stock_manager')
|
||||
company_2_id = self.registry('ir.model.data').xmlid_to_res_id(cr, uid, 'stock.res_company_1')
|
||||
self.multicompany_user_id = self.res_users.create(cr, uid,
|
||||
{'name': 'multicomp', 'login': 'multicomp',
|
||||
'groups_id': [(6, 0, [group_user_id, group_stock_manager_id])],
|
||||
|
|
|
@ -23,6 +23,7 @@ import mrp_product_produce
|
|||
import mrp_price
|
||||
import mrp_workcenter_load
|
||||
import change_production_qty
|
||||
import stock_move
|
||||
#import mrp_change_standard_price
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -52,7 +52,7 @@ class change_production_qty(osv.osv_memory):
|
|||
def _update_product_to_produce(self, cr, uid, prod, qty, context=None):
|
||||
move_lines_obj = self.pool.get('stock.move')
|
||||
for m in prod.move_created_ids:
|
||||
move_lines_obj.write(cr, uid, [m.id], {'product_qty': qty})
|
||||
move_lines_obj.write(cr, uid, [m.id], {'product_uom_qty': qty})
|
||||
|
||||
def change_prod_qty(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
|
@ -90,14 +90,11 @@ class change_production_qty(osv.osv_memory):
|
|||
factor = prod.product_qty * prod.product_uom.factor / bom_point.product_uom.factor
|
||||
product_details, workcenter_details = \
|
||||
bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, [])
|
||||
product_move = dict((mv.product_id.id, mv.id) for mv in prod.picking_id.move_lines)
|
||||
for r in product_details:
|
||||
if r['product_id'] == move.product_id.id:
|
||||
move_obj.write(cr, uid, [move.id], {'product_qty': r['product_qty']})
|
||||
if r['product_id'] in product_move:
|
||||
move_obj.write(cr, uid, [product_move[r['product_id']]], {'product_qty': r['product_qty']})
|
||||
move_obj.write(cr, uid, [move.id], {'product_uom_qty': r['product_qty']})
|
||||
if prod.move_prod_id:
|
||||
move_obj.write(cr, uid, [prod.move_prod_id.id], {'product_qty' : wiz_qty.product_qty})
|
||||
move_obj.write(cr, uid, [prod.move_prod_id.id], {'product_uom_qty' : wiz_qty.product_qty})
|
||||
self._update_product_to_produce(cr, uid, prod, wiz_qty.product_qty, context=context)
|
||||
return {}
|
||||
|
||||
|
|
|
@ -22,19 +22,56 @@
|
|||
from openerp.osv import fields, osv
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
|
||||
class mrp_product_produce_line(osv.osv_memory):
|
||||
_name="mrp.product.produce.line"
|
||||
_description = "Product Produce Consume lines"
|
||||
|
||||
_columns = {
|
||||
'product_id': fields.many2one('product.product', 'Product'),
|
||||
'product_qty': fields.float('Quantity (in default UoM)'),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
|
||||
'produce_id': fields.many2one('mrp.product.produce'),
|
||||
'track_production': fields.related('product_id', 'track_production', type='boolean'),
|
||||
}
|
||||
|
||||
class mrp_product_produce(osv.osv_memory):
|
||||
_name = "mrp.product.produce"
|
||||
_description = "Product Produce"
|
||||
|
||||
_columns = {
|
||||
'product_id': fields.many2one('product.product', type='many2one'),
|
||||
'product_qty': fields.float('Select Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
|
||||
'mode': fields.selection([('consume_produce', 'Consume & Produce'),
|
||||
('consume', 'Consume Only')], 'Mode', required=True,
|
||||
help="'Consume only' mode will only consume the products with the quantity selected.\n"
|
||||
"'Consume & Produce' mode will consume as well as produce the products with the quantity selected "
|
||||
"and it will finish the production order when total ordered quantities are produced."),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Lot'), #Should only be visible when it is consume and produce mode
|
||||
'consume_lines': fields.one2many('mrp.product.produce.line', 'produce_id', 'Products Consumed'),
|
||||
'track_production': fields.boolean('Track production'),
|
||||
}
|
||||
|
||||
def on_change_qty(self, cr, uid, ids, product_qty, consume_lines, context=None):
|
||||
"""
|
||||
When changing the quantity of products to be produced it will
|
||||
recalculate the number of raw materials needed according
|
||||
to the scheduled products and the already consumed/produced products
|
||||
It will return the consume lines needed for the products to be produced
|
||||
which the user can still adapt
|
||||
"""
|
||||
prod_obj = self.pool.get("mrp.production")
|
||||
production = prod_obj.browse(cr, uid, context['active_id'], context=context)
|
||||
consume_lines = []
|
||||
new_consume_lines = []
|
||||
if product_qty > 0.0:
|
||||
consume_lines = prod_obj._calculate_qty(cr, uid, production, product_qty=product_qty, context=context)
|
||||
|
||||
for consume in consume_lines:
|
||||
new_consume_lines.append([0, False, consume])
|
||||
return {'value': {'consume_lines': new_consume_lines}}
|
||||
|
||||
|
||||
def _get_product_qty(self, cr, uid, context=None):
|
||||
""" To obtain product quantity
|
||||
@param self: The object pointer.
|
||||
|
@ -54,9 +91,26 @@ class mrp_product_produce(osv.osv_memory):
|
|||
done += move.product_qty
|
||||
return (prod.product_qty - done) or prod.product_qty
|
||||
|
||||
def _get_product_id(self, cr, uid, context=None):
|
||||
""" To obtain product id
|
||||
@return: id
|
||||
"""
|
||||
prod=False
|
||||
if context and context.get("active_id"):
|
||||
prod = self.pool.get('mrp.production').browse(cr, uid,
|
||||
context['active_id'], context=context)
|
||||
return prod and prod.product_id.id or False
|
||||
|
||||
def _get_track(self, cr, uid, context=None):
|
||||
prod = self._get_product_id(cr, uid, context=context)
|
||||
prod_obj = self.pool.get("product.product")
|
||||
return prod and prod_obj.browse(cr, uid, prod, context=context).track_production or False
|
||||
|
||||
_defaults = {
|
||||
'product_qty': _get_product_qty,
|
||||
'mode': lambda *x: 'consume_produce'
|
||||
'mode': lambda *x: 'consume_produce',
|
||||
'product_id': _get_product_id,
|
||||
'track_production': _get_track,
|
||||
}
|
||||
|
||||
def do_produce(self, cr, uid, ids, context=None):
|
||||
|
@ -64,7 +118,7 @@ class mrp_product_produce(osv.osv_memory):
|
|||
assert production_id, "Production Id should be specified in context as a Active ID."
|
||||
data = self.browse(cr, uid, ids[0], context=context)
|
||||
self.pool.get('mrp.production').action_produce(cr, uid, production_id,
|
||||
data.product_qty, data.mode, context=context)
|
||||
data.product_qty, data.mode, data, context=context)
|
||||
return {}
|
||||
|
||||
|
||||
|
|
|
@ -12,8 +12,25 @@
|
|||
<form string="Produce" version="7.0">
|
||||
<group string="Produce">
|
||||
<field name="mode"/>
|
||||
<field name="product_qty" colspan="2"/>
|
||||
</group>
|
||||
<field name="product_qty" colspan="2" on_change="on_change_qty(product_qty, consume_lines, context)"/>
|
||||
<field name="product_id" invisible="1"/>
|
||||
<field name="track_production" invisible="1"/>
|
||||
<field name="lot_id" domain="[('product_id', '=', product_id)]"
|
||||
context="{'default_product_id':product_id}"
|
||||
attrs="{'required': [('track_production', '=', True), ('mode', '=', 'consume_produce')]}"
|
||||
groups="stock.group_tracking_lot"/>
|
||||
</group>
|
||||
<group string="To Consume">
|
||||
<field name="consume_lines">
|
||||
<tree string="Consume Lines" editable="top">
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="lot_id" domain="[('product_id', '=', product_id)]"
|
||||
context="{'default_product_id':product_id}"
|
||||
groups="stock.group_tracking_lot"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="do_produce" type="object" string="Confirm" class="oe_highlight"/>
|
||||
or
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
class stock_move_consume(osv.osv_memory):
|
||||
_name = "stock.move.consume"
|
||||
_description = "Consume Products"
|
||||
|
||||
_columns = {
|
||||
'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
|
||||
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
|
||||
'location_id': fields.many2one('stock.location', 'Location', required=True),
|
||||
'restrict_lot_id': fields.many2one('stock.production.lot', 'Lot'),
|
||||
}
|
||||
|
||||
#TOFIX: product_uom should not have different category of default UOM of product. Qty should be convert into UOM of original move line before going in consume and scrap
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
res = super(stock_move_consume, self).default_get(cr, uid, fields, context=context)
|
||||
move = self.pool.get('stock.move').browse(cr, uid, context['active_id'], context=context)
|
||||
if 'product_id' in fields:
|
||||
res.update({'product_id': move.product_id.id})
|
||||
if 'product_uom' in fields:
|
||||
res.update({'product_uom': move.product_uom.id})
|
||||
if 'product_qty' in fields:
|
||||
res.update({'product_qty': move.product_qty})
|
||||
if 'location_id' in fields:
|
||||
res.update({'location_id': move.location_id.id})
|
||||
return res
|
||||
|
||||
|
||||
|
||||
def do_move_consume(self, cr, uid, ids, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
move_obj = self.pool.get('stock.move')
|
||||
move_ids = context['active_ids']
|
||||
for data in self.browse(cr, uid, ids, context=context):
|
||||
move_obj.action_consume(cr, uid, move_ids,
|
||||
data.product_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id,
|
||||
context=context)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- Consume, scrap move -->
|
||||
|
||||
<record id="view_stock_move_consume_wizard" model="ir.ui.view">
|
||||
<field name="name">Consume Move</field>
|
||||
<field name="model">stock.move.consume</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Consume Move" version="7.0">
|
||||
<group string="Consume Products">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<label for="product_qty"/>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline"/>
|
||||
<field name="product_uom" class="oe_inline" readonly="1" groups="product.group_uom"/>
|
||||
</div>
|
||||
<field name="restrict_lot_id" domain="[('product_id','=',product_id)]" groups="stock.group_tracking_lot"
|
||||
context="{'default_product_id': product_id}"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="do_move_consume" string="Ok" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="move_consume" model="ir.actions.act_window">
|
||||
<field name="name">Consume Move</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">stock.move.consume</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -106,7 +106,7 @@ class mrp_production(osv.osv):
|
|||
'name': 'PROD:'+production.name,
|
||||
'date': production.date_planned,
|
||||
'product_id': sub_product.product_id.id,
|
||||
'product_qty': qty1,
|
||||
'product_uom_qty': qty1,
|
||||
'product_uom': sub_product.product_uom.id,
|
||||
'product_uos_qty': qty2,
|
||||
'product_uos': production.product_uos and production.product_uos.id or False,
|
||||
|
@ -151,12 +151,12 @@ class change_production_qty(osv.osv_memory):
|
|||
prod_obj = self.pool.get('mrp.production')
|
||||
for m in prod.move_created_ids:
|
||||
if m.product_id.id == prod.product_id.id:
|
||||
move_lines_obj.write(cr, uid, [m.id], {'product_qty': qty})
|
||||
move_lines_obj.write(cr, uid, [m.id], {'product_uom_qty': qty})
|
||||
else:
|
||||
for sub_product_line in prod.bom_id.sub_products:
|
||||
if sub_product_line.product_id.id == m.product_id.id:
|
||||
factor = prod_obj._get_subproduct_factor(cr, uid, prod.id, m.id, context=context)
|
||||
subproduct_qty = sub_product_line.subproduct_type == 'variable' and qty * factor or sub_product_line.product_qty
|
||||
move_lines_obj.write(cr, uid, [m.id], {'product_qty': subproduct_qty})
|
||||
move_lines_obj.write(cr, uid, [m.id], {'product_uom_qty': subproduct_qty})
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -64,6 +64,11 @@
|
|||
I finish the production order.
|
||||
-
|
||||
!python {model: mrp.product.produce}: |
|
||||
qty = self.browse(cr, uid, ref('mrp_product_produce0')).product_qty
|
||||
ctx = context.copy()
|
||||
ctx['active_id'] = ref("mrp_production_mo0")
|
||||
lines = self.on_change_qty(cr, uid, [ref('mrp_product_produce0')], qty, [], context=ctx)
|
||||
self.write(cr, uid, [ref('mrp_product_produce0')], lines['value'], context=context)
|
||||
self.do_produce(cr, uid, [ref("mrp_product_produce0")], {"active_model": "mrp.production", "active_ids":[ref("mrp_production_mo0")], "active_id": ref("mrp_production_mo0")})
|
||||
-
|
||||
I see that stock moves of External Hard Disk including Headset USB are done now.
|
||||
|
|
|
@ -97,7 +97,7 @@ class mrp_production_workcenter_line(osv.osv):
|
|||
'delay': fields.float('Working Hours',help="The elapsed time between operation start and stop in this Work Center",readonly=True),
|
||||
'production_state':fields.related('production_id','state',
|
||||
type='selection',
|
||||
selection=[('draft','Draft'),('picking_except', 'Picking Exception'),('confirmed','Waiting Goods'),('ready','Ready to Produce'),('in_production','In Production'),('cancel','Canceled'),('done','Done')],
|
||||
selection=[('draft','Draft'),('confirmed','Waiting Goods'),('ready','Ready to Produce'),('in_production','In Production'),('cancel','Canceled'),('done','Done')],
|
||||
string='Production Status', readonly=True),
|
||||
'product':fields.related('production_id','product_id',type='many2one',relation='product.product',string='Product',
|
||||
readonly=True),
|
||||
|
@ -279,7 +279,8 @@ class mrp_production(osv.osv):
|
|||
i = self.pool.get('resource.calendar').interval_get(
|
||||
cr,
|
||||
uid,
|
||||
wc.workcenter_id.calendar_id and wc.workcenter_id.calendar_id.id or False,
|
||||
#passing False makes resource_resource._schedule_hours run 1000 iterations doing nothing
|
||||
wc.workcenter_id.calendar_id and wc.workcenter_id.calendar_id.id or None,
|
||||
dt,
|
||||
wc.hour or 0.0
|
||||
)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
!record {model: res.users, id: res_mrp_operation_user}:
|
||||
groups_id:
|
||||
- mrp.group_mrp_user
|
||||
- stock.group_stock_user
|
||||
-
|
||||
In order to test mrp_operations with OpenERP, I refer created production order of PC Assemble SC349
|
||||
with routing - Manual Component's Assembly to test complete production process with respect of workcenter with giving access rights of MRP User.
|
||||
|
@ -34,7 +35,6 @@
|
|||
#TODO: to check start date of next line should be end of date of previous line.
|
||||
assert line.date_planned, "Planned Start date is not computed: %s" %(line)
|
||||
assert line.date_planned_end, "Planned End date is not computed: %s" %(line)
|
||||
|
||||
-
|
||||
I confirm the Production Order.
|
||||
-
|
||||
|
|
|
@ -19,9 +19,8 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.osv import fields,osv
|
||||
from openerp.osv import fields, osv
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openerp.tools.translate import _
|
||||
import openerp.addons.decimal_precision as dp
|
||||
|
||||
|
@ -75,7 +74,7 @@ class mrp_repair(osv.osv):
|
|||
val += c['amount']
|
||||
for line in repair.fees_lines:
|
||||
if line.to_invoice:
|
||||
tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, line.product_id, repair.partner_id)
|
||||
tax_calculate = tax_obj.compute_all(cr, uid, line.tax_id, line.price_unit, line.product_uom_qty, line.product_id, repair.partner_id)
|
||||
for c in tax_calculate['taxes']:
|
||||
val += c['amount']
|
||||
res[repair.id] = cur_obj.round(cr, uid, cur, val)
|
||||
|
@ -108,29 +107,29 @@ class mrp_repair(osv.osv):
|
|||
return res
|
||||
|
||||
def _get_lines(self, cr, uid, ids, context=None):
|
||||
return self.pool['mrp.repair'].search(
|
||||
cr, uid, [('operations', 'in', ids)], context=context)
|
||||
return self.pool['mrp.repair'].search(cr, uid, [('operations', 'in', ids)], context=context)
|
||||
|
||||
def _get_fee_lines(self, cr, uid, ids, context=None):
|
||||
return self.pool['mrp.repair'].search(
|
||||
cr, uid, [('fees_lines', 'in', ids)], context=context)
|
||||
return self.pool['mrp.repair'].search(cr, uid, [('fees_lines', 'in', ids)], context=context)
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Repair Reference',size=24, required=True, states={'confirmed':[('readonly',True)]}),
|
||||
'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'partner_id' : fields.many2one('res.partner', 'Partner', select=True, help='Choose partner for whom the order will be invoiced and delivered.', states={'confirmed':[('readonly',True)]}),
|
||||
'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]", states={'confirmed':[('readonly',True)]}),
|
||||
'name': fields.char('Repair Reference', size=24, required=True, states={'confirmed': [('readonly', True)]}),
|
||||
'product_id': fields.many2one('product.product', string='Product to Repair', required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_qty': fields.float('Product Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
|
||||
required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'partner_id': fields.many2one('res.partner', 'Partner', select=True, help='Choose partner for whom the order will be invoiced and delivered.', states={'confirmed': [('readonly', True)]}),
|
||||
'address_id': fields.many2one('res.partner', 'Delivery Address', domain="[('parent_id','=',partner_id)]", states={'confirmed': [('readonly', True)]}),
|
||||
'default_address_id': fields.function(_get_default_address, type="many2one", relation="res.partner"),
|
||||
'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number', select=True, states={'draft':[('readonly',False)]},domain="[('product_id','=',product_id)]"),
|
||||
'state': fields.selection([
|
||||
('draft','Quotation'),
|
||||
('cancel','Cancelled'),
|
||||
('confirmed','Confirmed'),
|
||||
('under_repair','Under Repair'),
|
||||
('ready','Ready to Repair'),
|
||||
('2binvoiced','To be Invoiced'),
|
||||
('invoice_except','Invoice Exception'),
|
||||
('done','Repaired')
|
||||
('draft', 'Quotation'),
|
||||
('cancel', 'Cancelled'),
|
||||
('confirmed', 'Confirmed'),
|
||||
('under_repair', 'Under Repair'),
|
||||
('ready', 'Ready to Repair'),
|
||||
('2binvoiced', 'To be Invoiced'),
|
||||
('invoice_except', 'Invoice Exception'),
|
||||
('done', 'Repaired')
|
||||
], 'Status', readonly=True, track_visibility='onchange',
|
||||
help=' * The \'Draft\' status is used when a user is encoding a new and unconfirmed repair order. \
|
||||
\n* The \'Confirmed\' status is used when a user confirms the repair order. \
|
||||
|
@ -138,26 +137,25 @@ class mrp_repair(osv.osv):
|
|||
\n* The \'To be Invoiced\' status is used to generate the invoice before or after repairing done. \
|
||||
\n* The \'Done\' status is set when repairing is completed.\
|
||||
\n* The \'Cancelled\' status is used when user cancel repair order.'),
|
||||
'location_id': fields.many2one('stock.location', 'Current Location', select=True, readonly=True, states={'draft':[('readonly',False)], 'confirmed':[('readonly',True)]}),
|
||||
'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, states={'draft':[('readonly',False)], 'confirmed':[('readonly',True)]}),
|
||||
'move_id': fields.many2one('stock.move', 'Move',required=True, domain="[('product_id','=',product_id)]", readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'guarantee_limit': fields.date('Warranty Expiration', help="The warranty expiration limit is computed as: last move date + warranty defined on selected product. If the current date is below the warranty expiration limit, each operation and fee you will add will be set as 'not to invoiced' by default. Note that you can change manually afterwards.", states={'confirmed':[('readonly',True)]}),
|
||||
'operations' : fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft':[('readonly',False)]}),
|
||||
'location_id': fields.many2one('stock.location', 'Current Location', select=True, required=True, readonly=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
|
||||
'location_dest_id': fields.many2one('stock.location', 'Delivery Location', readonly=True, required=True, states={'draft': [('readonly', False)], 'confirmed': [('readonly', True)]}),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Repaired Lot', domain="[('product_id','=', product_id)]", help="Products repaired are all belonging to this lot"),
|
||||
'guarantee_limit': fields.date('Warranty Expiration', help="The warranty expiration limit is computed as: last move date + warranty defined on selected product. If the current date is below the warranty expiration limit, each operation and fee you will add will be set as 'not to invoiced' by default. Note that you can change manually afterwards.", states={'confirmed': [('readonly', True)]}),
|
||||
'operations': fields.one2many('mrp.repair.line', 'repair_id', 'Operation Lines', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', help='Pricelist of the selected partner.'),
|
||||
'partner_invoice_id':fields.many2one('res.partner', 'Invoicing Address'),
|
||||
'invoice_method':fields.selection([
|
||||
("none","No Invoice"),
|
||||
("b4repair","Before Repair"),
|
||||
("after_repair","After Repair")
|
||||
'partner_invoice_id': fields.many2one('res.partner', 'Invoicing Address'),
|
||||
'invoice_method': fields.selection([
|
||||
("none", "No Invoice"),
|
||||
("b4repair", "Before Repair"),
|
||||
("after_repair", "After Repair")
|
||||
], "Invoice Method",
|
||||
select=True, required=True, states={'draft':[('readonly',False)]}, readonly=True, help='Selecting \'Before Repair\' or \'After Repair\' will allow you to generate invoice before or after the repair is done respectively. \'No invoice\' means you don\'t want to generate invoice for this repair order.'),
|
||||
'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True),
|
||||
'picking_id': fields.many2one('stock.picking', 'Picking',readonly=True),
|
||||
'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees Lines', readonly=True, states={'draft':[('readonly',False)]}),
|
||||
select=True, required=True, states={'draft': [('readonly', False)]}, readonly=True, help='Selecting \'Before Repair\' or \'After Repair\' will allow you to generate invoice before or after the repair is done respectively. \'No invoice\' means you don\'t want to generate invoice for this repair order.'),
|
||||
'invoice_id': fields.many2one('account.invoice', 'Invoice', readonly=True, track_visibility="onchange"),
|
||||
'move_id': fields.many2one('stock.move', 'Move', readonly=True, help="Move created by the repair order", track_visibility="onchange"),
|
||||
'fees_lines': fields.one2many('mrp.repair.fee', 'repair_id', 'Fees', readonly=True, states={'draft': [('readonly', False)]}),
|
||||
'internal_notes': fields.text('Internal Notes'),
|
||||
'quotation_notes': fields.text('Quotation Notes'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
'deliver_bool': fields.boolean('Deliver', help="Check this box if you want to manage the delivery once the product is repaired and create a picking with selected product. Note that you can select the locations in the Info tab, if you have the extended view.", states={'confirmed':[('readonly',True)]}),
|
||||
'invoiced': fields.boolean('Invoiced', readonly=True),
|
||||
'repaired': fields.boolean('Repaired', readonly=True),
|
||||
'amount_untaxed': fields.function(_amount_untaxed, string='Untaxed Amount',
|
||||
|
@ -180,24 +178,36 @@ class mrp_repair(osv.osv):
|
|||
}),
|
||||
}
|
||||
|
||||
def _default_stock_location(self, cr, uid, context=None):
|
||||
try:
|
||||
warehouse = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'warehouse0')
|
||||
return warehouse.lot_stock_id.id
|
||||
except:
|
||||
return False
|
||||
|
||||
_defaults = {
|
||||
'state': lambda *a: 'draft',
|
||||
'deliver_bool': lambda *a: True,
|
||||
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
|
||||
'invoice_method': lambda *a: 'none',
|
||||
'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.repair', context=context),
|
||||
'pricelist_id': lambda self, cr, uid,context : self.pool.get('product.pricelist').search(cr, uid, [('type','=','sale')])[0]
|
||||
'pricelist_id': lambda self, cr, uid, context: self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale')])[0],
|
||||
'product_qty': 1.0,
|
||||
'location_id': _default_stock_location,
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
('name', 'unique (name)', 'The name of the Repair Order must be unique!'),
|
||||
]
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
default = {}
|
||||
default.update({
|
||||
'state':'draft',
|
||||
'repaired':False,
|
||||
'invoiced':False,
|
||||
'state': 'draft',
|
||||
'repaired': False,
|
||||
'invoiced': False,
|
||||
'invoice_id': False,
|
||||
'picking_id': False,
|
||||
'move_id': False,
|
||||
'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.repair'),
|
||||
})
|
||||
return super(mrp_repair, self).copy(cr, uid, id, default, context)
|
||||
|
@ -207,41 +217,31 @@ class mrp_repair(osv.osv):
|
|||
@param product_id: Changed product
|
||||
@return: Dictionary of values.
|
||||
"""
|
||||
product = False
|
||||
if product_id:
|
||||
product = self.pool.get("product.product").browse(cr, uid, product_id)
|
||||
return {'value': {
|
||||
'prodlot_id': False,
|
||||
'move_id': False,
|
||||
'guarantee_limit' :False,
|
||||
'location_id': False,
|
||||
'location_dest_id': False,
|
||||
'guarantee_limit': False,
|
||||
'lot_id': False,
|
||||
'product_uom': product and product.uom_id.id or False,
|
||||
}
|
||||
}
|
||||
|
||||
def onchange_move_id(self, cr, uid, ids, prod_id=False, move_id=False):
|
||||
""" On change of move id sets values of guarantee limit, source location,
|
||||
destination location, partner and partner address.
|
||||
@param prod_id: Id of product in current record.
|
||||
@param move_id: Changed move.
|
||||
@return: Dictionary of values.
|
||||
def onchange_product_uom(self, cr, uid, ids, product_id, product_uom, context=None):
|
||||
res = {'value': {}}
|
||||
if not product_uom or not product_id:
|
||||
return res
|
||||
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
uom = self.pool.get('product.uom').browse(cr, uid, product_uom, context=context)
|
||||
if uom.category_id.id != product.uom_id.category_id.id:
|
||||
res['warning'] = {'title': _('Warning'), 'message': _('The Product Unit of Measure you chose has a different category than in the product form.')}
|
||||
res['value'].update({'product_uom': product.uom_id.id})
|
||||
return res
|
||||
|
||||
def onchange_location_id(self, cr, uid, ids, location_id=None):
|
||||
""" On change of location
|
||||
"""
|
||||
data = {}
|
||||
data['value'] = {'guarantee_limit': False, 'location_id': False, 'prodlot_id': False, 'partner_id': False}
|
||||
if not prod_id:
|
||||
return data
|
||||
if move_id:
|
||||
move = self.pool.get('stock.move').browse(cr, uid, move_id)
|
||||
product = self.pool.get('product.product').browse(cr, uid, prod_id)
|
||||
limit = datetime.strptime(move.date_expected, '%Y-%m-%d %H:%M:%S') + relativedelta(months=int(product.warranty))
|
||||
data['value']['guarantee_limit'] = limit.strftime('%Y-%m-%d')
|
||||
data['value']['location_id'] = move.location_dest_id.id
|
||||
data['value']['location_dest_id'] = move.location_dest_id.id
|
||||
data['value']['prodlot_id'] = move.prodlot_id.id
|
||||
if move.partner_id:
|
||||
data['value']['partner_id'] = move.partner_id.id
|
||||
else:
|
||||
data['value']['partner_id'] = False
|
||||
d = self.onchange_partner_id(cr, uid, ids, data['value']['partner_id'], data['value']['partner_id'])
|
||||
data['value'].update(d['value'])
|
||||
return data
|
||||
return {'value': {'location_dest_id': location_id}}
|
||||
|
||||
def button_dummy(self, cr, uid, ids, context=None):
|
||||
return True
|
||||
|
@ -259,7 +259,7 @@ class mrp_repair(osv.osv):
|
|||
return {'value': {
|
||||
'address_id': False,
|
||||
'partner_invoice_id': False,
|
||||
'pricelist_id': pricelist_obj.search(cr, uid, [('type','=','sale')])[0]
|
||||
'pricelist_id': pricelist_obj.search(cr, uid, [('type', '=', 'sale')])[0]
|
||||
}
|
||||
}
|
||||
addr = part_obj.address_get(cr, uid, [part], ['delivery', 'invoice', 'default'])
|
||||
|
@ -272,41 +272,6 @@ class mrp_repair(osv.osv):
|
|||
}
|
||||
}
|
||||
|
||||
def onchange_lot_id(self, cr, uid, ids, lot, product_id):
|
||||
""" On change of Serial Number sets the values of source location,
|
||||
destination location, move and guarantee limit.
|
||||
@param lot: Changed id of Serial Number.
|
||||
@param product_id: Product id from current record.
|
||||
@return: Dictionary of values.
|
||||
"""
|
||||
move_obj = self.pool.get('stock.move')
|
||||
data = {}
|
||||
data['value'] = {
|
||||
'location_id': False,
|
||||
'location_dest_id': False,
|
||||
'move_id': False,
|
||||
'guarantee_limit': False
|
||||
}
|
||||
|
||||
if not lot:
|
||||
return data
|
||||
move_ids = move_obj.search(cr, uid, [('prodlot_id', '=', lot)])
|
||||
|
||||
if not len(move_ids):
|
||||
return data
|
||||
|
||||
def get_last_move(lst_move):
|
||||
while lst_move.move_dest_id and lst_move.move_dest_id.state == 'done':
|
||||
lst_move = lst_move.move_dest_id
|
||||
return lst_move
|
||||
|
||||
move_id = move_ids[0]
|
||||
move = get_last_move(move_obj.browse(cr, uid, move_id))
|
||||
data['value']['move_id'] = move.id
|
||||
d = self.onchange_move_id(cr, uid, ids, product_id, move.id)
|
||||
data['value'].update(d['value'])
|
||||
return data
|
||||
|
||||
def action_cancel_draft(self, cr, uid, ids, *args):
|
||||
""" Cancels repair order when it is in 'Draft' state.
|
||||
@param *arg: Arguments
|
||||
|
@ -317,9 +282,8 @@ class mrp_repair(osv.osv):
|
|||
mrp_line_obj = self.pool.get('mrp.repair.line')
|
||||
for repair in self.browse(cr, uid, ids):
|
||||
mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'draft'})
|
||||
self.write(cr, uid, ids, {'state':'draft'})
|
||||
self.create_workflow(cr, uid, ids)
|
||||
return True
|
||||
self.write(cr, uid, ids, {'state': 'draft'})
|
||||
return self.create_workflow(cr, uid, ids)
|
||||
|
||||
def action_confirm(self, cr, uid, ids, *args):
|
||||
""" Repair order state is set to 'To be invoiced' when invoice method
|
||||
|
@ -334,7 +298,7 @@ class mrp_repair(osv.osv):
|
|||
else:
|
||||
self.write(cr, uid, [o.id], {'state': 'confirmed'})
|
||||
for line in o.operations:
|
||||
if line.product_id.track_production and not line.prodlot_id:
|
||||
if line.product_id.track_production:
|
||||
raise osv.except_osv(_('Warning!'), _("Serial number is required for operation line with product '%s'") % (line.product_id.name))
|
||||
mrp_line_obj.write(cr, uid, [l.id for l in o.operations], {'state': 'confirmed'})
|
||||
return True
|
||||
|
@ -348,13 +312,13 @@ class mrp_repair(osv.osv):
|
|||
if not repair.invoiced:
|
||||
mrp_line_obj.write(cr, uid, [l.id for l in repair.operations], {'state': 'cancel'}, context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Warning!'),_('Repair order is already invoiced.'))
|
||||
return self.write(cr,uid,ids,{'state':'cancel'})
|
||||
raise osv.except_osv(_('Warning!'), _('Repair order is already invoiced.'))
|
||||
return self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
|
||||
def wkf_invoice_create(self, cr, uid, ids, *args):
|
||||
self.action_invoice_create(cr, uid, ids)
|
||||
return True
|
||||
|
||||
|
||||
def action_invoice_create(self, cr, uid, ids, group=False, context=None):
|
||||
""" Creates invoice(s) for repair order.
|
||||
@param group: It is set to true when group invoice is to be generated.
|
||||
|
@ -368,28 +332,28 @@ class mrp_repair(osv.osv):
|
|||
repair_fee_obj = self.pool.get('mrp.repair.fee')
|
||||
for repair in self.browse(cr, uid, ids, context=context):
|
||||
res[repair.id] = False
|
||||
if repair.state in ('draft','cancel') or repair.invoice_id:
|
||||
if repair.state in ('draft', 'cancel') or repair.invoice_id:
|
||||
continue
|
||||
if not (repair.partner_id.id and repair.partner_invoice_id.id):
|
||||
raise osv.except_osv(_('No partner!'),_('You have to select a Partner Invoice Address in the repair form!'))
|
||||
raise osv.except_osv(_('No partner!'), _('You have to select a Partner Invoice Address in the repair form!'))
|
||||
comment = repair.quotation_notes
|
||||
if (repair.invoice_method != 'none'):
|
||||
if group and repair.partner_invoice_id.id in invoices_group:
|
||||
inv_id = invoices_group[repair.partner_invoice_id.id]
|
||||
invoice = inv_obj.browse(cr, uid, inv_id)
|
||||
invoice_vals = {
|
||||
'name': invoice.name +', '+repair.name,
|
||||
'origin': invoice.origin+', '+repair.name,
|
||||
'comment':(comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
|
||||
'name': invoice.name + ', ' + repair.name,
|
||||
'origin': invoice.origin + ', ' + repair.name,
|
||||
'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
|
||||
}
|
||||
inv_obj.write(cr, uid, [inv_id], invoice_vals, context=context)
|
||||
else:
|
||||
if not repair.partner_id.property_account_receivable:
|
||||
raise osv.except_osv(_('Error!'), _('No account defined for partner "%s".') % repair.partner_id.name )
|
||||
raise osv.except_osv(_('Error!'), _('No account defined for partner "%s".') % repair.partner_id.name)
|
||||
account_id = repair.partner_id.property_account_receivable.id
|
||||
inv = {
|
||||
'name': repair.name,
|
||||
'origin':repair.name,
|
||||
'origin': repair.name,
|
||||
'type': 'out_invoice',
|
||||
'account_id': account_id,
|
||||
'partner_id': repair.partner_id.id,
|
||||
|
@ -402,7 +366,7 @@ class mrp_repair(osv.osv):
|
|||
self.write(cr, uid, repair.id, {'invoiced': True, 'invoice_id': inv_id})
|
||||
|
||||
for operation in repair.operations:
|
||||
if operation.to_invoice == True:
|
||||
if operation.to_invoice:
|
||||
if group:
|
||||
name = repair.name + '-' + operation.name
|
||||
else:
|
||||
|
@ -413,7 +377,7 @@ class mrp_repair(osv.osv):
|
|||
elif operation.product_id.categ_id.property_account_income_categ:
|
||||
account_id = operation.product_id.categ_id.property_account_income_categ.id
|
||||
else:
|
||||
raise osv.except_osv(_('Error!'), _('No account defined for product "%s".') % operation.product_id.name )
|
||||
raise osv.except_osv(_('Error!'), _('No account defined for product "%s".') % operation.product_id.name)
|
||||
|
||||
invoice_line_id = inv_line_obj.create(cr, uid, {
|
||||
'invoice_id': inv_id,
|
||||
|
@ -421,15 +385,15 @@ class mrp_repair(osv.osv):
|
|||
'origin': repair.name,
|
||||
'account_id': account_id,
|
||||
'quantity': operation.product_uom_qty,
|
||||
'invoice_line_tax_id': [(6,0,[x.id for x in operation.tax_id])],
|
||||
'invoice_line_tax_id': [(6, 0, [x.id for x in operation.tax_id])],
|
||||
'uos_id': operation.product_uom.id,
|
||||
'price_unit': operation.price_unit,
|
||||
'price_subtotal': operation.product_uom_qty*operation.price_unit,
|
||||
'price_subtotal': operation.product_uom_qty * operation.price_unit,
|
||||
'product_id': operation.product_id and operation.product_id.id or False
|
||||
})
|
||||
repair_line_obj.write(cr, uid, [operation.id], {'invoiced': True, 'invoice_line_id': invoice_line_id})
|
||||
for fee in repair.fees_lines:
|
||||
if fee.to_invoice == True:
|
||||
if fee.to_invoice:
|
||||
if group:
|
||||
name = repair.name + '-' + fee.name
|
||||
else:
|
||||
|
@ -450,11 +414,11 @@ class mrp_repair(osv.osv):
|
|||
'origin': repair.name,
|
||||
'account_id': account_id,
|
||||
'quantity': fee.product_uom_qty,
|
||||
'invoice_line_tax_id': [(6,0,[x.id for x in fee.tax_id])],
|
||||
'invoice_line_tax_id': [(6, 0, [x.id for x in fee.tax_id])],
|
||||
'uos_id': fee.product_uom.id,
|
||||
'product_id': fee.product_id and fee.product_id.id or False,
|
||||
'price_unit': fee.price_unit,
|
||||
'price_subtotal': fee.product_uom_qty*fee.price_unit
|
||||
'price_subtotal': fee.product_uom_qty * fee.price_unit
|
||||
})
|
||||
repair_fee_obj.write(cr, uid, [fee.id], {'invoiced': True, 'invoice_line_id': invoice_fee_id})
|
||||
res[repair.id] = inv_id
|
||||
|
@ -489,9 +453,9 @@ class mrp_repair(osv.osv):
|
|||
for order in self.browse(cr, uid, ids, context=context):
|
||||
val = {}
|
||||
val['repaired'] = True
|
||||
if (not order.invoiced and order.invoice_method=='after_repair'):
|
||||
if (not order.invoiced and order.invoice_method == 'after_repair'):
|
||||
val['state'] = '2binvoiced'
|
||||
elif (not order.invoiced and order.invoice_method=='b4repair'):
|
||||
elif (not order.invoiced and order.invoice_method == 'b4repair'):
|
||||
val['state'] = 'ready'
|
||||
else:
|
||||
pass
|
||||
|
@ -503,59 +467,42 @@ class mrp_repair(osv.osv):
|
|||
return True
|
||||
|
||||
def action_repair_done(self, cr, uid, ids, context=None):
|
||||
""" Creates stock move and picking for repair order.
|
||||
@return: Picking ids.
|
||||
""" Creates stock move for operation and stock move for final product of repair order.
|
||||
@return: Move ids of final products
|
||||
"""
|
||||
res = {}
|
||||
move_obj = self.pool.get('stock.move')
|
||||
repair_line_obj = self.pool.get('mrp.repair.line')
|
||||
seq_obj = self.pool.get('ir.sequence')
|
||||
pick_obj = self.pool.get('stock.picking')
|
||||
for repair in self.browse(cr, uid, ids, context=context):
|
||||
move_ids = []
|
||||
for move in repair.operations:
|
||||
move_id = move_obj.create(cr, uid, {
|
||||
'name': move.name,
|
||||
'product_id': move.product_id.id,
|
||||
'product_qty': move.product_uom_qty,
|
||||
'restrict_lot_id': move.lot_id.id,
|
||||
'product_uom_qty': move.product_uom_qty,
|
||||
'product_uom': move.product_uom.id,
|
||||
'partner_id': repair.address_id and repair.address_id.id or False,
|
||||
'location_id': move.location_id.id,
|
||||
'location_dest_id': move.location_dest_id.id,
|
||||
'tracking_id': False,
|
||||
'prodlot_id': move.prodlot_id and move.prodlot_id.id or False,
|
||||
'state': 'assigned',
|
||||
})
|
||||
move_obj.action_done(cr, uid, [move_id], context=context)
|
||||
move_ids.append(move_id)
|
||||
repair_line_obj.write(cr, uid, [move.id], {'move_id': move_id, 'state': 'done'}, context=context)
|
||||
if repair.deliver_bool:
|
||||
pick_name = seq_obj.get(cr, uid, 'stock.picking.out')
|
||||
picking = pick_obj.create(cr, uid, {
|
||||
'name': pick_name,
|
||||
'origin': repair.name,
|
||||
'state': 'draft',
|
||||
'move_type': 'one',
|
||||
'partner_id': repair.address_id and repair.address_id.id or False,
|
||||
'note': repair.internal_notes,
|
||||
'invoice_state': 'none',
|
||||
'type': 'out',
|
||||
})
|
||||
move_id = move_obj.create(cr, uid, {
|
||||
'name': repair.name,
|
||||
'picking_id': picking,
|
||||
'product_id': repair.product_id.id,
|
||||
'product_uom': repair.product_id.uom_id.id,
|
||||
'prodlot_id': repair.prodlot_id and repair.prodlot_id.id or False,
|
||||
'partner_id': repair.address_id and repair.address_id.id or False,
|
||||
'location_id': repair.location_id.id,
|
||||
'location_dest_id': repair.location_dest_id.id,
|
||||
'tracking_id': False,
|
||||
'state': 'assigned',
|
||||
})
|
||||
pick_obj.signal_button_confirm(cr, uid, [picking])
|
||||
self.write(cr, uid, [repair.id], {'state': 'done', 'picking_id': picking})
|
||||
res[repair.id] = picking
|
||||
else:
|
||||
self.write(cr, uid, [repair.id], {'state': 'done'})
|
||||
move_id = move_obj.create(cr, uid, {
|
||||
'name': repair.name,
|
||||
'product_id': repair.product_id.id,
|
||||
'product_uom': repair.product_uom.id or repair.product_id.uom_id.id,
|
||||
'product_uom_qty': repair.product_qty,
|
||||
'partner_id': repair.address_id and repair.address_id.id or False,
|
||||
'location_id': repair.location_id.id,
|
||||
'location_dest_id': repair.location_dest_id.id,
|
||||
'restrict_lot_id': repair.lot_id.id,
|
||||
})
|
||||
move_ids.append(move_id)
|
||||
move_obj.action_done(cr, uid, move_ids, context=context)
|
||||
self.write(cr, uid, [repair.id], {'state': 'done', 'move_id': move_id}, context=context)
|
||||
res[repair.id] = move_id
|
||||
return res
|
||||
|
||||
|
||||
|
@ -589,24 +536,24 @@ class ProductChangeMixin(object):
|
|||
result['product_uom'] = product_obj.uom_id and product_obj.uom_id.id or False
|
||||
if not pricelist:
|
||||
warning = {
|
||||
'title':'No Pricelist!',
|
||||
'title': _('No Pricelist!'),
|
||||
'message':
|
||||
'You have to select a pricelist in the Repair form !\n'
|
||||
'Please set one before choosing a product.'
|
||||
_('You have to select a pricelist in the Repair form !\n'
|
||||
'Please set one before choosing a product.')
|
||||
}
|
||||
else:
|
||||
price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
|
||||
product, product_uom_qty, partner_id, {'uom': uom,})[pricelist]
|
||||
product, product_uom_qty, partner_id, {'uom': uom})[pricelist]
|
||||
|
||||
if price is False:
|
||||
warning = {
|
||||
'title':'No valid pricelist line found !',
|
||||
warning = {
|
||||
'title': _('No valid pricelist line found !'),
|
||||
'message':
|
||||
"Couldn't find a pricelist line matching this product and quantity.\n"
|
||||
"You have to change either the product, the quantity or the pricelist."
|
||||
_("Couldn't find a pricelist line matching this product and quantity.\n"
|
||||
"You have to change either the product, the quantity or the pricelist.")
|
||||
}
|
||||
else:
|
||||
result.update({'price_unit': price, 'price_subtotal': price*product_uom_qty})
|
||||
result.update({'price_unit': price, 'price_subtotal': price * product_uom_qty})
|
||||
|
||||
return {'value': result, 'warning': warning}
|
||||
|
||||
|
@ -616,8 +563,9 @@ class mrp_repair_line(osv.osv, ProductChangeMixin):
|
|||
_description = 'Repair Line'
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
if not default: default = {}
|
||||
default.update( {'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
|
||||
if not default:
|
||||
default = {}
|
||||
default.update({'invoice_line_id': False, 'move_id': False, 'invoiced': False, 'state': 'draft'})
|
||||
return super(mrp_repair_line, self).copy_data(cr, uid, id, default, context)
|
||||
|
||||
def _amount_line(self, cr, uid, ids, field_name, arg, context=None):
|
||||
|
@ -627,7 +575,7 @@ class mrp_repair_line(osv.osv, ProductChangeMixin):
|
|||
@return: Dictionary of values.
|
||||
"""
|
||||
res = {}
|
||||
cur_obj=self.pool.get('res.currency')
|
||||
cur_obj = self.pool.get('res.currency')
|
||||
for line in self.browse(cr, uid, ids, context=context):
|
||||
res[line.id] = line.to_invoice and line.price_unit * line.product_uom_qty or 0
|
||||
cur = line.repair_id.pricelist_id.currency_id
|
||||
|
@ -635,35 +583,35 @@ class mrp_repair_line(osv.osv, ProductChangeMixin):
|
|||
return res
|
||||
|
||||
_columns = {
|
||||
'name' : fields.char('Description',size=64,required=True),
|
||||
'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference',ondelete='cascade', select=True),
|
||||
'type': fields.selection([('add','Add'),('remove','Remove')],'Type', required=True),
|
||||
'name': fields.char('Description', size=64, required=True),
|
||||
'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', ondelete='cascade', select=True),
|
||||
'type': fields.selection([('add', 'Add'), ('remove', 'Remove')], 'Type', required=True),
|
||||
'to_invoice': fields.boolean('To Invoice'),
|
||||
'product_id': fields.many2one('product.product', 'Product', required=True),
|
||||
'invoiced': fields.boolean('Invoiced',readonly=True),
|
||||
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
|
||||
'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Account')),
|
||||
'invoiced': fields.boolean('Invoiced', readonly=True),
|
||||
'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Product Price')),
|
||||
'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute=dp.get_precision('Account')),
|
||||
'tax_id': fields.many2many('account.tax', 'repair_operation_line_tax', 'repair_operation_line_id', 'tax_id', 'Taxes'),
|
||||
'product_uom_qty': fields.float('Quantity', digits_compute= dp.get_precision('Product Unit of Measure'), required=True),
|
||||
'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
|
||||
'prodlot_id': fields.many2one('stock.production.lot', 'Lot Number',domain="[('product_id','=',product_id)]"),
|
||||
'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
|
||||
'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True),
|
||||
'location_dest_id': fields.many2one('stock.location', 'Dest. Location', required=True, select=True),
|
||||
'move_id': fields.many2one('stock.move', 'Inventory Move', readonly=True),
|
||||
'lot_id': fields.many2one('stock.production.lot', 'Lot'),
|
||||
'state': fields.selection([
|
||||
('draft','Draft'),
|
||||
('confirmed','Confirmed'),
|
||||
('done','Done'),
|
||||
('cancel','Cancelled')], 'Status', required=True, readonly=True,
|
||||
('draft', 'Draft'),
|
||||
('confirmed', 'Confirmed'),
|
||||
('done', 'Done'),
|
||||
('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
|
||||
help=' * The \'Draft\' status is set automatically as draft when repair order in draft status. \
|
||||
\n* The \'Confirmed\' status is set automatically as confirm when repair order in confirm status. \
|
||||
\n* The \'Done\' status is set automatically when repair order is completed.\
|
||||
\n* The \'Cancelled\' status is set automatically when user cancel repair order.'),
|
||||
}
|
||||
_defaults = {
|
||||
'state': lambda *a: 'draft',
|
||||
'product_uom_qty': lambda *a: 1,
|
||||
'state': lambda *a: 'draft',
|
||||
'product_uom_qty': lambda *a: 1,
|
||||
}
|
||||
|
||||
def onchange_operation_type(self, cr, uid, ids, type, guarantee_limit, company_id=False, context=None):
|
||||
|
@ -680,7 +628,7 @@ class mrp_repair_line(osv.osv, ProductChangeMixin):
|
|||
}}
|
||||
location_obj = self.pool.get('stock.location')
|
||||
warehouse_obj = self.pool.get('stock.warehouse')
|
||||
location_id = location_obj.search(cr, uid, [('usage','=','production')], context=context)
|
||||
location_id = location_obj.search(cr, uid, [('usage', '=', 'production')], context=context)
|
||||
location_id = location_id and location_id[0] or False
|
||||
|
||||
if type == 'add':
|
||||
|
@ -712,7 +660,8 @@ class mrp_repair_fee(osv.osv, ProductChangeMixin):
|
|||
_description = 'Repair Fees Line'
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
if not default: default = {}
|
||||
if not default:
|
||||
default = {}
|
||||
default.update({'invoice_line_id': False, 'invoiced': False})
|
||||
return super(mrp_repair_fee, self).copy_data(cr, uid, id, default, context)
|
||||
|
||||
|
@ -732,17 +681,18 @@ class mrp_repair_fee(osv.osv, ProductChangeMixin):
|
|||
|
||||
_columns = {
|
||||
'repair_id': fields.many2one('mrp.repair', 'Repair Order Reference', required=True, ondelete='cascade', select=True),
|
||||
'name': fields.char('Description', size=64, select=True,required=True),
|
||||
'name': fields.char('Description', size=64, select=True, required=True),
|
||||
'product_id': fields.many2one('product.product', 'Product'),
|
||||
'product_uom_qty': fields.float('Quantity', digits_compute= dp.get_precision('Product Unit of Measure'), required=True),
|
||||
'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
|
||||
'price_unit': fields.float('Unit Price', required=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
|
||||
'price_subtotal': fields.function(_amount_line, string='Subtotal',digits_compute= dp.get_precision('Account')),
|
||||
'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute=dp.get_precision('Account')),
|
||||
'tax_id': fields.many2many('account.tax', 'repair_fee_line_tax', 'repair_fee_line_id', 'tax_id', 'Taxes'),
|
||||
'invoice_line_id': fields.many2one('account.invoice.line', 'Invoice Line', readonly=True),
|
||||
'to_invoice': fields.boolean('To Invoice'),
|
||||
'invoiced': fields.boolean('Invoiced',readonly=True),
|
||||
'invoiced': fields.boolean('Invoiced', readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'to_invoice': lambda *a: True,
|
||||
}
|
||||
|
|
|
@ -1,25 +1,13 @@
|
|||
-
|
||||
!record {model: stock.move, id: stock_move_pcbasicpc0}:
|
||||
company_id: base.main_company
|
||||
date: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||
date_expected: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_stock
|
||||
name: '[PCSC234] PC Assemble SC234'
|
||||
product_id: product.product_product_3
|
||||
product_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
product_uos_qty: 1.0
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp1}:
|
||||
address_id: base.res_partner_address_1
|
||||
guarantee_limit: !eval datetime.today().strftime("%Y-%m-%d")
|
||||
invoice_method: 'none'
|
||||
product_id: product.product_product_3
|
||||
product_uom: product.product_uom_unit
|
||||
partner_invoice_id: base.res_partner_address_1
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_14
|
||||
move_id: 'stock_move_pcbasicpc0'
|
||||
name: RMA00004
|
||||
location_id: stock.stock_location_stock
|
||||
operations:
|
||||
- location_dest_id: stock.location_production
|
||||
location_id: stock.stock_location_stock
|
||||
|
@ -38,29 +26,16 @@
|
|||
product_uom: product.product_uom_unit
|
||||
price_unit: 50.0
|
||||
partner_id: base.res_partner_9
|
||||
product_id: product.product_product_3
|
||||
-
|
||||
!record {model: stock.move, id: stock.stock_move_stockmvmrp1}:
|
||||
company_id: base.main_company
|
||||
date: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||
date_expected: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_stock
|
||||
name: '[PC-DEM] PC on Demand'
|
||||
product_id: product.product_product_5
|
||||
product_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
product_uos_qty: 1.0
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp0}:
|
||||
product_id: product.product_product_5
|
||||
product_uom: product.product_uom_unit
|
||||
address_id: base.res_partner_address_1
|
||||
guarantee_limit: !eval datetime.today().strftime("%Y-%m-%d")
|
||||
invoice_method: 'after_repair'
|
||||
partner_invoice_id: base.res_partner_address_1
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_14
|
||||
move_id: 'stock.stock_move_stockmvmrp1'
|
||||
name: RMA-00007
|
||||
location_id: stock.stock_location_stock
|
||||
operations:
|
||||
- location_dest_id: stock.location_production
|
||||
location_id: stock.stock_location_stock
|
||||
|
@ -79,29 +54,16 @@
|
|||
product_uom: product.product_uom_unit
|
||||
price_unit: 50.0
|
||||
partner_id: base.res_partner_9
|
||||
product_id: product.product_product_5
|
||||
-
|
||||
!record {model: stock.move, id: stock.stock_move_stockmvmrp2}:
|
||||
company_id: base.main_company
|
||||
date: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||
date_expected: !eval datetime.today().strftime("%Y-%m-%d %H:%M:%S")
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_id: stock.stock_location_stock
|
||||
name: '[LCD15] 15” LCD Monitor'
|
||||
product_id: product.product_product_6
|
||||
product_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
product_uos_qty: 1.0
|
||||
-
|
||||
!record {model: mrp.repair, id: mrp_repair_rmrp2}:
|
||||
product_id: product.product_product_6
|
||||
product_uom: product.product_uom_unit
|
||||
address_id: base.res_partner_address_1
|
||||
guarantee_limit: !eval datetime.today().strftime("%Y-%m-%d")
|
||||
invoice_method: 'b4repair'
|
||||
partner_invoice_id: base.res_partner_address_1
|
||||
location_dest_id: stock.stock_location_14
|
||||
location_dest_id: stock.stock_location_stock
|
||||
location_id: stock.stock_location_14
|
||||
move_id: 'stock.stock_move_stockmvmrp2'
|
||||
name: RMA-00011
|
||||
operations:
|
||||
- location_dest_id: stock.location_production
|
||||
location_id: stock.stock_location_stock
|
||||
|
@ -119,5 +81,4 @@
|
|||
product_uom_qty: 1.0
|
||||
product_uom: product.product_uom_unit
|
||||
price_unit: 50.0
|
||||
partner_id: base.res_partner_9
|
||||
product_id: product.product_product_6
|
||||
partner_id: base.res_partner_9
|
|
@ -9,7 +9,6 @@
|
|||
<tree string="Repairs order" colors="gray:state in ('done','cancel');black:state not in ('done','cancel');blue:state=='draft'">
|
||||
<field name="name" />
|
||||
<field name="product_id" />
|
||||
<field name="move_id"/>
|
||||
<field name="partner_id"/>
|
||||
<field name="address_id"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
|
@ -45,22 +44,25 @@
|
|||
<group>
|
||||
<group>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id)" domain="[('type','!=','service')]"/>
|
||||
<label for="product_qty"/>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline"/>
|
||||
<field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(product_id, product_uom)" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="partner_id" on_change="onchange_partner_id(partner_id,address_id)" attrs="{'required':[('invoice_method','!=','none')]}"/>
|
||||
<field name="address_id" groups="sale.group_delivery_invoice_address"/>
|
||||
<field name="move_id" on_change="onchange_move_id(product_id, move_id)" context="{'default_product_id':product_id}"/>
|
||||
<field name="location_id" attrs="{'required':[('deliver_bool','=', True)]}" groups="stock.group_locations"/>
|
||||
<field name="prodlot_id" on_change="onchange_lot_id(prodlot_id,product_id)" groups="stock.group_production_lot" string="Serial Number"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="location_id" on_change="onchange_location_id(location_id)" groups="stock.group_locations" domain="[('usage', 'in', ('internal', 'customer'))]"/>
|
||||
<field name="location_dest_id" groups="stock.group_locations" domain="[('usage', 'in', ('internal', 'customer'))]"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="guarantee_limit"/>
|
||||
<field name="deliver_bool"/>
|
||||
<field name="repaired" groups="base.group_no_one"/>
|
||||
<field name="invoiced" groups="base.group_no_one"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Operations">
|
||||
<field name="operations">
|
||||
<field name="operations" context="{'default_product_uom_qty': product_qty}">
|
||||
<form string="Operations" version="7.0">
|
||||
<notebook>
|
||||
<page string="Repair Line">
|
||||
|
@ -78,7 +80,6 @@
|
|||
<field name="to_invoice"/>
|
||||
<field name="tax_id" widget="many2many_tags" domain="[('parent_id','=',False),('type_tax_use','<>','purchase')]"/>
|
||||
<field name="invoiced"/>
|
||||
<field name='prodlot_id' groups="stock.group_production_lot" string="Serial Number"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
<field name="location_dest_id" groups="stock.group_locations"/>
|
||||
</group>
|
||||
|
@ -95,7 +96,7 @@
|
|||
<field name="type" on_change="onchange_operation_type(type,parent.guarantee_limit,parent.company_id,context)"/>
|
||||
<field name="product_id" on_change="product_id_change(parent.pricelist_id,product_id,product_uom,product_uom_qty, parent.partner_id)"/>
|
||||
<field name='name'/>
|
||||
<field name="prodlot_id" groups="stock.group_production_lot" string="Serial Number"/>
|
||||
<field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
<field name="location_dest_id" groups="stock.group_locations"/>
|
||||
<field name="product_uom_qty" string="Quantity"/>
|
||||
|
@ -121,13 +122,15 @@
|
|||
<page string="Invoicing">
|
||||
<group col="4">
|
||||
<field name="invoice_method"/>
|
||||
<field name="invoice_id" context="{'form_view_ref': 'account.invoice_form'}"/>
|
||||
<field name="partner_invoice_id" attrs="{'readonly':[('invoice_method','=', 'none')],'required':[('invoice_method','!=','none')]}" groups="sale.group_delivery_invoice_address"/>
|
||||
<field
|
||||
name="pricelist_id" groups="product.group_sale_pricelist" context="{'product_id':product_id}"
|
||||
attrs="{'readonly':[('invoice_method','=', 'none')]}"/>
|
||||
</group>
|
||||
<!-- <field name="invoice_id"/> -->
|
||||
<field name="fees_lines">
|
||||
<separator string="Fees"/>
|
||||
<field name="fees_lines" attrs="{'readonly': [('invoice_method','=', 'none')]}">
|
||||
<form string="Fees" version="7.0">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h2>
|
||||
|
@ -169,15 +172,14 @@
|
|||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Extra Info">
|
||||
<page string="Extra Info" groups="base.group_no_one">
|
||||
<group>
|
||||
<group>
|
||||
<field name="picking_id"/>
|
||||
<field name="invoice_id" context="{'form_view_ref': 'account.invoice_form'}"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="move_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="location_dest_id" attrs="{'required':[('deliver_bool','=', True)]}" groups="stock.group_locations"/>
|
||||
<field name="repaired"/>
|
||||
<field name="invoiced"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
|
|
@ -1,29 +1,27 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mrp_repair_user,MRP Repair user,model_mrp_repair,stock.group_stock_user,1,0,0,0
|
||||
access_mrp_repair_manager,MRP Repair manager,model_mrp_repair,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_repair_line_user,MRP Repair Line user,model_mrp_repair_line,stock.group_stock_user,1,0,0,0
|
||||
access_mrp_repair_line_manager,MRP Repair Line manager,model_mrp_repair_line,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_repair_fee_user,MRP Repair Fee user,model_mrp_repair_fee,stock.group_stock_user,1,1,1,1
|
||||
access_mrp_repair_fee_manager,MRP Repair Fee manager,model_mrp_repair_fee,stock.group_stock_manager,1,0,0,0
|
||||
access_mrp_repair_user,MRP Repair user,model_mrp_repair,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_repair_manager,MRP Repair manager,model_mrp_repair,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_pricelist_user,product.pricelist user,product.model_product_pricelist,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_production_lot_user,stock.production.lot user,stock.model_stock_production_lot,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_production_lot_manager,stock.production.lot manager,stock.model_stock_production_lot,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_repair_line_user,mrp.repair.line user,model_mrp_repair_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_repair_line_manager,mrp.repair.line manager,model_mrp_repair_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_production_lot_revision_manager,stock.production.lot.revision manager,stock.model_stock_production_lot_revision,mrp.group_mrp_manager,1,0,0,0
|
||||
access_stock_production_lot_revision_user,stock.production.lot.revision user,stock.model_stock_production_lot_revision,mrp.group_mrp_user,1,1,1,1
|
||||
access_product_price_type_manager,product.price.type manager,product.model_product_price_type,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_price_type_user,product.price.type,product.model_product_price_type,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_tax_user,account.tax,account.model_account_tax,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_tax_manager,account.tax manager,account.model_account_tax,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_repair_fee_user_mrp,MRP Repair Fee user mrp,model_mrp_repair_fee,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_repair_fee_mgr,MRP Repair Fee mgr,model_mrp_repair_fee,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_invoice_user,account.invoice,account.model_account_invoice,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_invoice_manager,account.invoice manager,account.model_account_invoice,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_invoice_line_user,account.invoice.line,account.model_account_invoice_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_invoice_line_manager,account.invoice.line manager,account.model_account_invoice_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_journal_user,account.journal,account.model_account_journal,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_journal_manager,account.journal manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mrp_repair_user,MRP Repair user,model_mrp_repair,stock.group_stock_user,1,0,0,0
|
||||
access_mrp_repair_manager,MRP Repair manager,model_mrp_repair,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_repair_line_user,MRP Repair Line user,model_mrp_repair_line,stock.group_stock_user,1,0,0,0
|
||||
access_mrp_repair_line_manager,MRP Repair Line manager,model_mrp_repair_line,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_repair_fee_user,MRP Repair Fee user,model_mrp_repair_fee,stock.group_stock_user,1,1,1,1
|
||||
access_mrp_repair_fee_manager,MRP Repair Fee manager,model_mrp_repair_fee,stock.group_stock_manager,1,0,0,0
|
||||
access_mrp_repair_user,MRP Repair user,model_mrp_repair,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_repair_manager,MRP Repair manager,model_mrp_repair,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_pricelist_manager,product.pricelist manager,product.model_product_pricelist,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_pricelist_user,product.pricelist user,product.model_product_pricelist,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_production_lot_user,stock.production.lot user,stock.model_stock_production_lot,mrp.group_mrp_user,1,1,1,1
|
||||
access_stock_production_lot_manager,stock.production.lot manager,stock.model_stock_production_lot,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_repair_line_user,mrp.repair.line user,model_mrp_repair_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_repair_line_manager,mrp.repair.line manager,model_mrp_repair_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_price_type_manager,product.price.type manager,product.model_product_price_type,mrp.group_mrp_manager,1,0,0,0
|
||||
access_product_price_type_user,product.price.type,product.model_product_price_type,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_tax_user,account.tax,account.model_account_tax,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_tax_manager,account.tax manager,account.model_account_tax,mrp.group_mrp_manager,1,0,0,0
|
||||
access_mrp_repair_fee_user_mrp,MRP Repair Fee user mrp,model_mrp_repair_fee,mrp.group_mrp_user,1,1,1,1
|
||||
access_mrp_repair_fee_mgr,MRP Repair Fee mgr,model_mrp_repair_fee,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_invoice_user,account.invoice,account.model_account_invoice,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_invoice_manager,account.invoice manager,account.model_account_invoice,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_invoice_line_user,account.invoice.line,account.model_account_invoice_line,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_invoice_line_manager,account.invoice.line manager,account.model_account_invoice_line,mrp.group_mrp_manager,1,0,0,0
|
||||
access_account_journal_user,account.journal,account.model_account_journal,mrp.group_mrp_user,1,1,1,1
|
||||
access_account_journal_manager,account.journal manager,account.model_account_journal,mrp.group_mrp_manager,1,0,0,0
|
||||
|
|
|
|
@ -8,7 +8,7 @@
|
|||
-
|
||||
!workflow {model: mrp.repair, action: repair_confirm, ref: mrp_repair_rmrp1}
|
||||
-
|
||||
I start the repairing process by clicking on "Start Repair" button for Invoice Method 'No Invoice'.
|
||||
I start the repairing process by clicking on "Start Repair" button for Invoice Method 'No Invoice'.
|
||||
-
|
||||
!workflow {model: mrp.repair, action: repair_ready, ref: mrp_repair_rmrp1}
|
||||
-
|
||||
|
@ -22,9 +22,8 @@
|
|||
!workflow {model: mrp.repair, action: action_repair_end, ref: mrp_repair_rmrp1}
|
||||
-
|
||||
I define Invoice Method 'No Invoice' option in this repair order.
|
||||
So, I check that Invoice should not be created for this repair order.
|
||||
So, I check that Invoice has not been created for this repair order.
|
||||
-
|
||||
!python {model: mrp.repair}: |
|
||||
repair_id = self.browse(cr, uid, [ref('mrp_repair_rmrp1')], context=context)[0]
|
||||
assert not repair_id.invoice_id.id, "Invoice should not be exists for this repair order"
|
||||
|
||||
assert not repair_id.invoice_id.id, "Invoice should not exist for this repair order"
|
|
@ -100,7 +100,6 @@
|
|||
</record>
|
||||
<record id="product_product_odoo1" model="product.product">
|
||||
<field name="default_code">ODOO</field>
|
||||
<field name="supply_method">produce</field>
|
||||
<field name="list_price">35.0</field>
|
||||
<field name="standard_price">10.0</field>
|
||||
<field name="uom_id" ref="product.product_uom_unit"/>
|
||||
|
|
|
@ -61,8 +61,8 @@ class pos_config(osv.osv):
|
|||
'journal_ids' : fields.many2many('account.journal', 'pos_config_journal_rel',
|
||||
'pos_config_id', 'journal_id', 'Available Payment Methods',
|
||||
domain="[('journal_user', '=', True ), ('type', 'in', ['bank', 'cash'])]",),
|
||||
'warehouse_id' : fields.many2one('stock.warehouse', 'Warehouse',
|
||||
required=True),
|
||||
'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type'),
|
||||
'stock_location_id': fields.many2one('stock.location', 'Stock Location', domain=[('usage', '=', 'internal')], required=True),
|
||||
'journal_id' : fields.many2one('account.journal', 'Sale Journal',
|
||||
domain=[('type', '=', 'sale')],
|
||||
help="Accounting journal used to post sales entries."),
|
||||
|
@ -87,7 +87,8 @@ class pos_config(osv.osv):
|
|||
"to customize the reference numbers of your orders."),
|
||||
'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'),
|
||||
'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"),
|
||||
'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True)
|
||||
'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
}
|
||||
|
||||
def _check_cash_control(self, cr, uid, ids, context=None):
|
||||
|
@ -131,24 +132,39 @@ class pos_config(osv.osv):
|
|||
res = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'sale'), ('company_id', '=', company_id)], limit=1, context=context)
|
||||
return res and res[0] or False
|
||||
|
||||
def _default_warehouse(self, cr, uid, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context)
|
||||
res = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', user.company_id.id)], limit=1, context=context)
|
||||
return res and res[0] or False
|
||||
|
||||
def _default_pricelist(self, cr, uid, context=None):
|
||||
res = self.pool.get('product.pricelist').search(cr, uid, [('type', '=', 'sale')], limit=1, context=context)
|
||||
return res and res[0] or False
|
||||
|
||||
def _get_default_location(self, cr, uid, context=None):
|
||||
wh_obj = self.pool.get('stock.warehouse')
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context)
|
||||
res = wh_obj.search(cr, uid, [('company_id', '=', user.company_id.id)], limit=1, context=context)
|
||||
if res and res[0]:
|
||||
return wh_obj.browse(cr, uid, res[0], context=context).lot_stock_id.id
|
||||
return False
|
||||
|
||||
def _get_default_company(self, cr, uid, context=None):
|
||||
company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
|
||||
return company_id
|
||||
|
||||
_defaults = {
|
||||
'state' : POS_CONFIG_STATE[0][0],
|
||||
'warehouse_id': _default_warehouse,
|
||||
'journal_id': _default_sale_journal,
|
||||
'group_by' : True,
|
||||
'pricelist_id': _default_pricelist,
|
||||
'iface_invoicing': True,
|
||||
'stock_location_id': _get_default_location,
|
||||
'company_id': _get_default_company,
|
||||
}
|
||||
|
||||
def onchange_picking_type_id(self, cr, uid, ids, picking_type_id, context=None):
|
||||
p_type_obj = self.pool.get("stock.picking.type")
|
||||
p_type = p_type_obj.browse(cr, uid, picking_type_id, context=context)
|
||||
if p_type.default_location_src_id and p_type.default_location_src_id.usage == 'internal' and p_type.default_location_dest_id and p_type.default_location_dest_id.usage == 'customer':
|
||||
return {'value': {'stock_location_id': p_type.default_location_src_id.id}}
|
||||
return False
|
||||
|
||||
def set_active(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state' : 'active'}, context=context)
|
||||
|
||||
|
@ -334,7 +350,7 @@ class pos_session(osv.osv):
|
|||
# the .xml files as the CoA is not yet installed.
|
||||
jobj = self.pool.get('pos.config')
|
||||
pos_config = jobj.browse(cr, uid, config_id, context=context)
|
||||
context.update({'company_id': pos_config.warehouse_id.company_id.id})
|
||||
context.update({'company_id': pos_config.company_id.id})
|
||||
if not pos_config.journal_id:
|
||||
jid = jobj.default_get(cr, uid, ['journal_id'], context=context)['journal_id']
|
||||
if jid:
|
||||
|
@ -361,7 +377,7 @@ class pos_session(osv.osv):
|
|||
bank_values = {
|
||||
'journal_id' : journal.id,
|
||||
'user_id' : uid,
|
||||
'company_id' : pos_config.warehouse_id.company_id.id
|
||||
'company_id' : pos_config.company_id.id
|
||||
}
|
||||
statement_id = self.pool.get('account.bank.statement').create(cr, uid, bank_values, context=context)
|
||||
bank_statement_ids.append(statement_id)
|
||||
|
@ -513,8 +529,7 @@ class pos_order(osv.osv):
|
|||
_description = "Point of Sale"
|
||||
_order = "id desc"
|
||||
|
||||
def create_from_ui(self, cr, uid, orders, context=None):
|
||||
|
||||
def create_from_ui(self, cr, uid, orders, context=None):
|
||||
# Keep only new orders
|
||||
submitted_references = [o['data']['name'] for o in orders]
|
||||
existing_order_ids = self.search(cr, uid, [('pos_reference', 'in', submitted_references)], context=context)
|
||||
|
@ -643,7 +658,6 @@ class pos_order(osv.osv):
|
|||
_columns = {
|
||||
'name': fields.char('Order Ref', size=64, required=True, readonly=True),
|
||||
'company_id':fields.many2one('res.company', 'Company', required=True, readonly=True),
|
||||
'warehouse_id': fields.related('session_id', 'config_id', 'warehouse_id', relation='stock.warehouse', type='many2one', string='Warehouse', store=True, readonly=True),
|
||||
'date_order': fields.datetime('Order Date', readonly=True, select=True),
|
||||
'user_id': fields.many2one('res.users', 'Salesman', help="Person who uses the the cash register. It can be a reliever, a student or an interim employee."),
|
||||
'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Account'), multi='all'),
|
||||
|
@ -672,6 +686,8 @@ class pos_order(osv.osv):
|
|||
'invoice_id': fields.many2one('account.invoice', 'Invoice'),
|
||||
'account_move': fields.many2one('account.move', 'Journal Entry', readonly=True),
|
||||
'picking_id': fields.many2one('stock.picking', 'Picking', readonly=True),
|
||||
'picking_type_id': fields.related('session_id', 'config_id', 'picking_type_id', string="Picking Type", type='many2one', relation='stock.picking.type'),
|
||||
'location_id': fields.related('session_id', 'config_id', 'stock_location_id', string="Location", type='many2one', store=True, relation='stock.location'),
|
||||
'note': fields.text('Internal Notes'),
|
||||
'nb_print': fields.integer('Number of Print', readonly=True),
|
||||
'pos_reference': fields.char('Receipt Ref', size=64, readonly=True),
|
||||
|
@ -690,6 +706,10 @@ class pos_order(osv.osv):
|
|||
return session_record.config_id.pricelist_id and session_record.config_id.pricelist_id.id or False
|
||||
return False
|
||||
|
||||
def _get_out_picking_type(self, cr, uid, context=None):
|
||||
return self.pool.get('ir.model.data').xmlid_to_res_id(
|
||||
cr, uid, 'point_of_sale.picking_type_posout', context=context)
|
||||
|
||||
_defaults = {
|
||||
'user_id': lambda self, cr, uid, context: uid,
|
||||
'state': 'draft',
|
||||
|
@ -719,7 +739,7 @@ class pos_order(osv.osv):
|
|||
|
||||
def create_picking(self, cr, uid, ids, context=None):
|
||||
"""Create a picking for each order and validate it."""
|
||||
picking_obj = self.pool.get('stock.picking.out')
|
||||
picking_obj = self.pool.get('stock.picking')
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
move_obj = self.pool.get('stock.move')
|
||||
|
||||
|
@ -727,43 +747,56 @@ class pos_order(osv.osv):
|
|||
if not order.state=='draft':
|
||||
continue
|
||||
addr = order.partner_id and partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery']) or {}
|
||||
picking_id = picking_obj.create(cr, uid, {
|
||||
'origin': order.name,
|
||||
'partner_id': addr.get('delivery',False),
|
||||
'type': 'out',
|
||||
'company_id': order.company_id.id,
|
||||
'move_type': 'direct',
|
||||
'note': order.note or "",
|
||||
'invoice_state': 'none',
|
||||
'auto_picking': True,
|
||||
}, context=context)
|
||||
self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context)
|
||||
location_id = order.warehouse_id.lot_stock_id.id
|
||||
picking_type = order.picking_type_id
|
||||
picking_id = False
|
||||
if picking_type:
|
||||
picking_id = picking_obj.create(cr, uid, {
|
||||
'origin': order.name,
|
||||
'partner_id': addr.get('delivery',False),
|
||||
'picking_type_id': picking_type.id,
|
||||
'company_id': order.company_id.id,
|
||||
'move_type': 'direct',
|
||||
'note': order.note or "",
|
||||
'invoice_state': 'none',
|
||||
}, context=context)
|
||||
self.write(cr, uid, [order.id], {'picking_id': picking_id}, context=context)
|
||||
location_id = order.location_id.id
|
||||
if order.partner_id:
|
||||
destination_id = order.partner_id.property_stock_customer.id
|
||||
elif picking_type:
|
||||
if not picking_type.default_location_dest_id:
|
||||
raise osv.except_osv(_('Error!'), _('Missing source or destination location for picking type %s. Please configure those fields and try again.' % (picking_type.name,)))
|
||||
destination_id = picking_type.default_location_dest_id.id
|
||||
else:
|
||||
destination_id = partner_obj.default_get(cr, uid, ['property_stock_customer'], context=context)['property_stock_customer']
|
||||
|
||||
move_list = []
|
||||
for line in order.lines:
|
||||
if line.product_id and line.product_id.type == 'service':
|
||||
continue
|
||||
|
||||
move_obj.create(cr, uid, {
|
||||
move_list.append(move_obj.create(cr, uid, {
|
||||
'name': line.name,
|
||||
'product_uom': line.product_id.uom_id.id,
|
||||
'product_uos': line.product_id.uom_id.id,
|
||||
'picking_id': picking_id,
|
||||
'picking_type_id': picking_type.id,
|
||||
'product_id': line.product_id.id,
|
||||
'product_uos_qty': abs(line.qty),
|
||||
'product_qty': abs(line.qty),
|
||||
'tracking_id': False,
|
||||
'product_uom_qty': abs(line.qty),
|
||||
'state': 'draft',
|
||||
'location_id': location_id if line.qty >= 0 else destination_id,
|
||||
'location_dest_id': destination_id if line.qty >= 0 else location_id,
|
||||
}, context=context)
|
||||
|
||||
picking_obj.signal_button_confirm(cr, uid, [picking_id])
|
||||
picking_obj.force_assign(cr, uid, [picking_id], context)
|
||||
}, context=context))
|
||||
|
||||
if picking_id:
|
||||
picking_obj.action_confirm(cr, uid, [picking_id], context=context)
|
||||
picking_obj.force_assign(cr, uid, [picking_id], context=context)
|
||||
picking_obj.action_done(cr, uid, [picking_id], context=context)
|
||||
elif move_list:
|
||||
move_obj.action_confirm(cr, uid, move_list, context=context)
|
||||
move_obj.force_assign(cr, uid, move_list, context=context)
|
||||
move_obj.action_done(cr, uid, move_list, context=context)
|
||||
return True
|
||||
|
||||
def cancel_order(self, cr, uid, ids, context=None):
|
||||
|
@ -772,7 +805,7 @@ class pos_order(osv.osv):
|
|||
"""
|
||||
stock_picking_obj = self.pool.get('stock.picking')
|
||||
for order in self.browse(cr, uid, ids, context=context):
|
||||
stock_picking_obj.signal_button_cancel(cr, uid, [order.picking_id.id])
|
||||
stock_picking_obj.action_cancel(cr, uid, [order.picking_id.id])
|
||||
if stock_picking_obj.browse(cr, uid, order.picking_id.id, context=context).state <> 'cancel':
|
||||
raise osv.except_osv(_('Error!'), _('Unable to cancel the picking.'))
|
||||
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
<data noupdate="0">
|
||||
<record id="seq_picking_type_posout" model="ir.sequence">
|
||||
<field name="name">Picking POS</field>
|
||||
<field name="prefix">POS</field>
|
||||
<field name="padding">5</field>
|
||||
<field name="company_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="picking_type_posout" model="stock.picking.type">
|
||||
<field name="name">PoS Orders</field>
|
||||
<field name="sequence_id" ref="seq_picking_type_posout"/>
|
||||
<field name="default_location_src_id" ref="stock.stock_location_stock"/>
|
||||
<field name="default_location_dest_id" ref="stock.stock_location_customers"/>
|
||||
<field name="code">outgoing</field>
|
||||
</record>
|
||||
</data>
|
||||
<data noupdate="1">
|
||||
<!-- After installation of the module, open the related menu -->
|
||||
<record id="action_client_pos_menu" model="ir.actions.client">
|
||||
<field name="name">Open POS Menu</field>
|
||||
|
@ -13,6 +29,7 @@
|
|||
</record>
|
||||
<record model="pos.config" id="pos_config_main">
|
||||
<field name="name">Main</field>
|
||||
<field name="picking_type_id" ref="picking_type_posout"/>
|
||||
</record>
|
||||
|
||||
<!-- notify all employees of module installation -->
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
<page string="Extra Info">
|
||||
<group string="General Information">
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="warehouse_id" widget="selection" groups="stock.group_locations"/>
|
||||
<field name="location_id" widget="selection" groups="stock.group_locations"/>
|
||||
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'point_of_sale.group_pos_user']}"/>
|
||||
<field name="pricelist_id" groups="product.group_sale_pricelist" domain="[('type','=','sale')]"/>
|
||||
<field name="picking_id" readonly="1"/>
|
||||
|
@ -475,137 +475,7 @@
|
|||
<field name="domain">[('date_order','like',time.strftime('%Y-%m'))]</field>
|
||||
</record>
|
||||
|
||||
<!-- Sales by margin -->
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_form">
|
||||
<field name="name">report.sales.by.margin.pos.form</field>
|
||||
<field name="model">report.sales.by.margin.pos</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="POS " version="7.0">
|
||||
<group col="4">
|
||||
<field name="user_id"/>
|
||||
<field name="product_name"/>
|
||||
<field name="date_order" widget="date"/>
|
||||
<field name="qty"/>
|
||||
<field name="net_margin_per_qty"/>
|
||||
<field name="total"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_tree">
|
||||
<field name="name">report.sales.by.margin.pos.tree</field>
|
||||
<field name="model">report.sales.by.margin.pos</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="POS">
|
||||
<field name="user_id"/>
|
||||
<field name="product_name"/>
|
||||
<field name="date_order" widget="date"/>
|
||||
<field name="qty"/>
|
||||
<field name="net_margin_per_qty"/>
|
||||
<field name="total"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_calendar">
|
||||
<field name="name">report.sales.by.margin.pos.calendar</field>
|
||||
<field name="model">report.sales.by.margin.pos</field>
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<calendar color="user_id" date_start="date_order" string="Sales by User Margin">
|
||||
<field name="product_name"/>
|
||||
<field name="total" />
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_graph">
|
||||
<field name="name">report.sales.by.margin.pos.graph</field>
|
||||
<field name="model">report.sales.by.margin.pos</field>
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<graph type="bar" string="Sales by User Margin" orientation="horizontal">
|
||||
<field name="product_name" />
|
||||
<field name="total" operator="+"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_report_sales_by_margin_pos_today">
|
||||
<field name="name">Sales by User Daily margin</field>
|
||||
<field name="res_model">report.sales.by.margin.pos</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,calendar,form,graph</field>
|
||||
<field name="domain">[('date_order','=',time.strftime('%Y-%m-%d'))]</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_month_form">
|
||||
<field name="name">report.sales.by.margin.pos.month.form</field>
|
||||
<field name="model">report.sales.by.margin.pos.month</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="POS " version="7.0">
|
||||
<group col="4">
|
||||
<field name="user_id"/>
|
||||
<field name="product_name"/>
|
||||
<field name="date_order" widget="date"/>
|
||||
<field name="qty"/>
|
||||
<field name="net_margin_per_qty"/>
|
||||
<field name="total"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_month_tree">
|
||||
<field name="name">report.sales.by.margin.pos.month.tree</field>
|
||||
<field name="model">report.sales.by.margin.pos.month</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="POS">
|
||||
<field name="user_id"/>
|
||||
<field name="product_name"/>
|
||||
<field name="date_order" widget="date"/>
|
||||
<field name="qty"/>
|
||||
<field name="net_margin_per_qty"/>
|
||||
<field name="total"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_month_calendar">
|
||||
<field name="name">report.sales.by.margin.pos.month.calendar</field>
|
||||
<field name="model">report.sales.by.margin.pos.month</field>
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<calendar color="user_id" date_start="date_order" string="Sales by User Margin">
|
||||
<field name="product_name"/>
|
||||
<field name="total" />
|
||||
</calendar>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_report_sales_by_margin_pos_month_graph">
|
||||
<field name="name">report.sales.by.margin.pos.month.graph</field>
|
||||
<field name="model">report.sales.by.margin.pos.month</field>
|
||||
<field eval="2" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<graph type="bar" string="Sales by User Margin" orientation="horizontal">
|
||||
<field name="product_name" />
|
||||
<field name="total" operator="+"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_report_sales_by_margin_pos_month">
|
||||
<field name="name">Sales by User Monthly margin</field>
|
||||
<field name="res_model">report.sales.by.margin.pos.month</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,calendar,form,graph</field>
|
||||
<field name="domain">[('date_order','like',time.strftime('%Y-%m'))]</field>
|
||||
</record>
|
||||
|
||||
<record id="product_normal_form_view" model="ir.ui.view">
|
||||
<field name="name">product.normal.form.inherit</field>
|
||||
|
@ -721,7 +591,9 @@
|
|||
<field name="name"/>
|
||||
</h1>
|
||||
<group col="4">
|
||||
<field name="warehouse_id" widget="selection" groups="stock.group_locations" />
|
||||
<field name="picking_type_id" widget="selection" groups="stock.group_locations"
|
||||
on_change="onchange_picking_type_id(picking_type_id)"/>
|
||||
<field name="stock_location_id" groups="stock.group_locations"/>
|
||||
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="journal_id" widget="selection"/>
|
||||
|
@ -769,7 +641,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<tree string="Point of Sale Configuration" colors="grey:state == 'inactive'">
|
||||
<field name="name" />
|
||||
<field name="warehouse_id" groups="stock.group_locations"/>
|
||||
<field name="stock_location_id" groups="stock.group_locations"/>
|
||||
<field name="state" />
|
||||
</tree>
|
||||
</field>
|
||||
|
@ -783,7 +655,7 @@
|
|||
<field name="name" />
|
||||
<filter string="Active" domain="[('state', '=', 'active')]" />
|
||||
<filter string="Inactive" domain="[('state', '=', 'inactive')]" />
|
||||
<field name="warehouse_id" groups="stock.group_locations" />
|
||||
<field name="stock_location_id" groups="stock.group_locations" />
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -36,7 +36,7 @@ class pos_order_report(osv.osv):
|
|||
'price_total':fields.float('Total Price', readonly=True),
|
||||
'total_discount':fields.float('Total Discount', readonly=True),
|
||||
'average_price': fields.float('Average Price', readonly=True,group_operator="avg"),
|
||||
'warehouse_id':fields.many2one('stock.warehouse', 'Warehouse', readonly=True),
|
||||
'location_id':fields.many2one('stock.location', 'Location', readonly=True),
|
||||
'company_id':fields.many2one('res.company', 'Company', readonly=True),
|
||||
'nbr':fields.integer('# of Lines', readonly=True),
|
||||
'product_qty':fields.integer('# of Qty', readonly=True),
|
||||
|
@ -61,7 +61,7 @@ class pos_order_report(osv.osv):
|
|||
s.partner_id as partner_id,
|
||||
s.state as state,
|
||||
s.user_id as user_id,
|
||||
s.warehouse_id as warehouse_id,
|
||||
s.location_id as location_id,
|
||||
s.company_id as company_id,
|
||||
s.sale_journal as journal_id,
|
||||
l.product_id as product_id
|
||||
|
@ -71,7 +71,7 @@ class pos_order_report(osv.osv):
|
|||
left join product_uom u on (u.id=pt.uom_id)
|
||||
group by
|
||||
s.date_order, s.partner_id,s.state,
|
||||
s.user_id,s.warehouse_id,s.company_id,s.sale_journal,l.product_id,s.create_date
|
||||
s.user_id,s.location_id,s.company_id,s.sale_journal,l.product_id,s.create_date
|
||||
having
|
||||
sum(l.qty * u.factor) != 0)""")
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
<filter string="Salesperson" icon="terp-personal" name="User" context="{'group_by':'user_id'}"/>
|
||||
<filter string="Customer" icon="terp-personal" context="{'group_by':'partner_id'}"/>
|
||||
<filter string="Product" icon="terp-accessories-archiver" context="{'group_by':'product_id'}"/>
|
||||
<filter string="Stock Location" context="{'group_by': 'location_id'}"/>
|
||||
<filter string="Order Date (day)" icon="terp-go-today" context="{'group_by':'date:day'}" help="Day of order date"/>
|
||||
<filter string="Order Date (month)" icon="terp-go-month" context="{'group_by':'date:month'}" help="Month of order date"/>
|
||||
<filter string="Order Date (year)" icon="terp-go-year" context="{'group_by':'date:year'}" help="Year of order date"/>
|
||||
|
|
|
@ -138,95 +138,5 @@ class report_sales_by_user_pos_month(osv.osv):
|
|||
)
|
||||
""")
|
||||
|
||||
class report_sales_by_margin_pos(osv.osv):
|
||||
_name = "report.sales.by.margin.pos"
|
||||
_description = "Sales by margin"
|
||||
_auto = False
|
||||
_columns = {
|
||||
'product_name':fields.char('Product Name', size=64, readonly=True),
|
||||
'date_order': fields.date('Order Date',required=True, select=True),
|
||||
'user_id': fields.many2one('res.users', 'User', readonly=True, select=True),
|
||||
'qty': fields.float('Qty', readonly=True, select=True),
|
||||
'net_margin_per_qty':fields.float('Net margin per Qty', readonly=True, select=True),
|
||||
'total':fields.float('Margin', readonly=True, select=True),
|
||||
|
||||
}
|
||||
|
||||
def init(self, cr):
|
||||
tools.drop_view_if_exists(cr, 'report_sales_by_margin_pos')
|
||||
cr.execute("""
|
||||
create or replace view report_sales_by_margin_pos as (
|
||||
select
|
||||
min(pol.id) as id,
|
||||
po.user_id as user_id,
|
||||
pt.name as product_name,
|
||||
to_char(date_trunc('day',po.date_order),'YYYY-MM-DD')::text as date_order,
|
||||
sum(pol.qty) as qty,
|
||||
pt.list_price-pt.standard_price as net_margin_per_qty,
|
||||
(pt.list_price-pt.standard_price) *sum(pol.qty) as total
|
||||
from
|
||||
product_template as pt,
|
||||
product_product as pp,
|
||||
pos_order_line as pol,
|
||||
pos_order as po
|
||||
where
|
||||
pol.product_id = pp.product_tmpl_id and
|
||||
pp.product_tmpl_id = pt.id and
|
||||
po.id = pol.order_id
|
||||
|
||||
group by
|
||||
pt.name,
|
||||
pt.list_price,
|
||||
pt.standard_price,
|
||||
po.user_id,
|
||||
to_char(date_trunc('day',po.date_order),'YYYY-MM-DD')::text
|
||||
|
||||
)
|
||||
""")
|
||||
|
||||
class report_sales_by_margin_pos_month(osv.osv):
|
||||
_name = "report.sales.by.margin.pos.month"
|
||||
_description = "Sales by margin monthly"
|
||||
_auto = False
|
||||
_columns = {
|
||||
'product_name':fields.char('Product Name', size=64, readonly=True),
|
||||
'date_order': fields.date('Order Date',required=True, select=True),
|
||||
'user_id': fields.many2one('res.users', 'User', readonly=True, select=True),
|
||||
'qty': fields.float('Qty', readonly=True, select=True),
|
||||
'net_margin_per_qty':fields.float('Net margin per Qty', readonly=True, select=True),
|
||||
'total':fields.float('Margin', readonly=True, select=True),
|
||||
}
|
||||
|
||||
def init(self, cr):
|
||||
tools.drop_view_if_exists(cr, 'report_sales_by_margin_pos_month')
|
||||
cr.execute("""
|
||||
create or replace view report_sales_by_margin_pos_month as (
|
||||
select
|
||||
min(pol.id) as id,
|
||||
po.user_id as user_id,
|
||||
pt.name as product_name,
|
||||
to_char(date_trunc('month',po.date_order),'YYYY-MM-DD')::text as date_order,
|
||||
sum(pol.qty) as qty,
|
||||
pt.list_price-pt.standard_price as net_margin_per_qty,
|
||||
(pt.list_price-pt.standard_price) *sum(pol.qty) as total
|
||||
from
|
||||
product_template as pt,
|
||||
product_product as pp,
|
||||
pos_order_line as pol,
|
||||
pos_order as po
|
||||
where
|
||||
pol.product_id = pp.product_tmpl_id and
|
||||
pp.product_tmpl_id = pt.id and
|
||||
po.id = pol.order_id
|
||||
|
||||
group by
|
||||
pt.name,
|
||||
pt.list_price,
|
||||
pt.standard_price,
|
||||
po.user_id,
|
||||
to_char(date_trunc('month',po.date_order),'YYYY-MM-DD')::text
|
||||
|
||||
)
|
||||
""")
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -13,8 +13,6 @@ access_pos_order_stock_worker,pos.order stock_worker,model_pos_order,stock.group
|
|||
access_stock_move_pos_user,stock.move pos_user,stock.model_stock_move,group_pos_user,1,1,1,1
|
||||
access_report_sales_by_user_pos,report.sales.by.user.pos,model_report_sales_by_user_pos,group_pos_user,1,0,0,0
|
||||
access_report_sales_by_user_pos_month,report.sales.by.user.pos.month,model_report_sales_by_user_pos_month,group_pos_user,1,0,0,0
|
||||
access_report_sales_by_margin_pos,report.sales.by.margin.pos,model_report_sales_by_margin_pos,group_pos_user,1,0,0,0
|
||||
access_report_sales_by_margin_pos_month,report.sales.by.margin.pos.month,model_report_sales_by_margin_pos_month,group_pos_user,1,0,0,0
|
||||
access_report_pos_order,report.pos.order,model_report_pos_order,group_pos_user,1,1,1,1
|
||||
access_account_bank_statement,account.bank.statement,account.model_account_bank_statement,group_pos_user,1,1,1,0
|
||||
access_account_bank_statement_manager,account.bank.statement manager,account.model_account_bank_statement,group_pos_manager,1,1,1,1
|
||||
|
|
|
|
@ -23,7 +23,7 @@
|
|||
<field name="name">Point Of Sale Config</field>
|
||||
<field name="model_id" ref="model_pos_config" />
|
||||
<field name="global" eval="True" />
|
||||
<field name="domain_force">[('warehouse_id.company_id','child_of',[user.company_id.id])]</field>
|
||||
<field name="domain_force">[('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -34,7 +34,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
this.company_logo = null;
|
||||
this.company_logo_base64 = '';
|
||||
this.currency = null;
|
||||
this.shop = null;
|
||||
this.company = null;
|
||||
this.user = null;
|
||||
this.users = [];
|
||||
|
@ -174,7 +173,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
|
||||
return self.fetch(
|
||||
'pos.config',
|
||||
['name','journal_ids','warehouse_id','journal_id','pricelist_id',
|
||||
['name','journal_ids','journal_id','pricelist_id',
|
||||
'iface_self_checkout', 'iface_led', 'iface_cashdrawer',
|
||||
'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan',
|
||||
'iface_vkeyboard','iface_print_via_proxy','iface_scan_via_proxy',
|
||||
|
@ -191,10 +190,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
self.config.iface_scan_via_proxy ||
|
||||
self.config.iface_cashdrawer;
|
||||
|
||||
return self.fetch('stock.warehouse',[],[['id','=', self.config.warehouse_id[0]]]);
|
||||
}).then(function(shops){
|
||||
self.shop = shops[0];
|
||||
|
||||
return self.fetch('product.pricelist',['currency_id'],[['id','=',self.config.pricelist_id[0]]]);
|
||||
}).then(function(pricelists){
|
||||
self.pricelist = pricelists[0];
|
||||
|
@ -954,7 +949,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
var client = this.get('client');
|
||||
var cashier = this.pos.cashier || this.pos.user;
|
||||
var company = this.pos.company;
|
||||
var shop = this.pos.shop;
|
||||
var date = new Date();
|
||||
|
||||
return {
|
||||
|
@ -999,9 +993,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
|
|||
phone: company.phone,
|
||||
logo: this.pos.company_logo_base64,
|
||||
},
|
||||
shop:{
|
||||
name: shop.name,
|
||||
},
|
||||
currency: this.pos.currency,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -781,7 +781,6 @@
|
|||
<t t-esc="widget.pos.company.name"/><br />
|
||||
Phone: <t t-esc="widget.pos.company.phone || ''"/><br />
|
||||
User: <t t-esc="widget.pos.user.name"/><br />
|
||||
Shop: <t t-esc="widget.pos.shop.name"/><br />
|
||||
<br />
|
||||
<t t-if="widget.pos.config.receipt_header">
|
||||
<div style='text-align:center'>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
<div t-field="o.partner_id"
|
||||
t-field-options='{"widget": "contact", "fields": ["address", "name", "phone", "fax"], "no_marker": true}'/>
|
||||
User: <span t-field="o.user_id"/><br/>
|
||||
Warehouse: <span t-field="o.warehouse_id"/><br/>
|
||||
Date: <span t-field="o.date_order"/><br/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_stock_picking,stock.picking,stock.model_stock_picking,base.group_portal,1,0,0,0
|
||||
access_stock_picking.out,stock.picking.out,stock.model_stock_picking_out,base.group_portal,1,0,0,0
|
||||
access_stock_move,stock.move,stock.model_stock_move,base.group_portal,1,0,0,0
|
||||
access_stock_warehouse_orderpoint,stock.warehouse.orderpoint,procurement.model_stock_warehouse_orderpoint,base.group_portal,1,0,0,0
|
||||
access_stock_warehouse_orderpoint,stock.warehouse.orderpoint,stock.model_stock_warehouse_orderpoint,base.group_portal,1,0,0,0
|
||||
|
|
|
|
@ -9,13 +9,5 @@
|
|||
<field name="domain_force">[('message_follower_ids','in',[user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="portal_stock_picking_out_user_rule" model="ir.rule">
|
||||
<field name="name">Portal Personal Out Pickings</field>
|
||||
<field name="model_id" ref="stock.model_stock_picking_out"/>
|
||||
<field name="domain_force">[('message_follower_ids','in',[user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -21,7 +21,4 @@
|
|||
|
||||
import procurement
|
||||
import wizard
|
||||
import schedulers
|
||||
import company
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
'author' : 'OpenERP SA',
|
||||
'website' : 'http://www.openerp.com',
|
||||
'category' : 'Hidden/Dependency',
|
||||
'depends' : ['base', 'product', 'stock'],
|
||||
'depends' : ['base', 'product'],
|
||||
'description': """
|
||||
This is the module for computing Procurements.
|
||||
==============================================
|
||||
|
@ -47,18 +47,13 @@ depending on the product's configuration.
|
|||
'security/ir.model.access.csv',
|
||||
'security/procurement_security.xml',
|
||||
'procurement_data.xml',
|
||||
'wizard/make_procurement_view.xml',
|
||||
'wizard/mrp_procurement_view.xml',
|
||||
'wizard/orderpoint_procurement_view.xml',
|
||||
'wizard/schedulers_all_view.xml',
|
||||
'procurement_view.xml',
|
||||
'procurement_workflow.xml',
|
||||
'company_view.xml',
|
||||
],
|
||||
'demo': ['stock_orderpoint.xml','procurement_demo.xml'],
|
||||
'demo': [],
|
||||
'test': ['test/procurement.yml'],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'images': ['images/compute_schedulers.jpeg','images/config_companies_sched.jpeg', 'images/minimum_stock_rules.jpeg'],
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='account_grp']" position="after">
|
||||
<group name="logistics_grp" string="Logistics">
|
||||
<field name="schedule_range"/>
|
||||
<!-- This group will be filled by other modules like sale_stock, mrp, purchase... -->
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
@ -19,59 +19,80 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from operator import attrgetter
|
||||
import time
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp import workflow
|
||||
import openerp.addons.decimal_precision as dp
|
||||
from openerp.tools.translate import _
|
||||
import openerp
|
||||
|
||||
# Procurement
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Produce, Buy or Find products and place a move
|
||||
# then wizard for picking lists & move
|
||||
#
|
||||
class procurement_group(osv.osv):
|
||||
'''
|
||||
The procurement group class is used to group products together
|
||||
when computing procurements. (tasks, physical products, ...)
|
||||
|
||||
class mrp_property_group(osv.osv):
|
||||
"""
|
||||
Group of mrp properties.
|
||||
"""
|
||||
_name = 'mrp.property.group'
|
||||
_description = 'Property Group'
|
||||
The goal is that when you have one sale order of several products
|
||||
and the products are pulled from the same or several location(s), to keep
|
||||
having the moves grouped into pickings that represent the sale order.
|
||||
|
||||
Used in: sales order (to group delivery order lines like the so), pull/push
|
||||
rules (to pack like the delivery order), on orderpoints (e.g. for wave picking
|
||||
all the similar products together).
|
||||
|
||||
Grouping is made only if the source and the destination is the same.
|
||||
Suppose you have 4 lines on a picking from Output where 2 lines will need
|
||||
to come from Input (crossdock) and 2 lines coming from Stock -> Output As
|
||||
the four procurement orders will have the same group ids from the SO, the
|
||||
move from input will have a stock.picking with 2 grouped lines and the move
|
||||
from stock will have 2 grouped lines also.
|
||||
|
||||
The name is usually the name of the original document (sale order) or a
|
||||
sequence computed if created manually.
|
||||
'''
|
||||
_name = 'procurement.group'
|
||||
_description = 'Procurement Requisition'
|
||||
_order = "id desc"
|
||||
_columns = {
|
||||
'name': fields.char('Property Group', size=64, required=True),
|
||||
'description': fields.text('Description'),
|
||||
}
|
||||
|
||||
class mrp_property(osv.osv):
|
||||
"""
|
||||
Properties of mrp.
|
||||
"""
|
||||
_name = 'mrp.property'
|
||||
_description = 'Property'
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=64, required=True),
|
||||
'composition': fields.selection([('min','min'),('max','max'),('plus','plus')], 'Properties composition', required=True, help="Not used in computations, for information purpose only."),
|
||||
'group_id': fields.many2one('mrp.property.group', 'Property Group', required=True),
|
||||
'description': fields.text('Description'),
|
||||
'name': fields.char('Reference', required=True),
|
||||
'move_type': fields.selection([
|
||||
('direct', 'Partial'), ('one', 'All at once')],
|
||||
'Delivery Method', required=True),
|
||||
'procurement_ids': fields.one2many('procurement.order', 'group_id', 'Procurements'),
|
||||
}
|
||||
_defaults = {
|
||||
'composition': lambda *a: 'min',
|
||||
'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').get(cr, uid, 'procurement.group') or '',
|
||||
'move_type': lambda self, cr, uid, c: 'one'
|
||||
}
|
||||
|
||||
class StockMove(osv.osv):
|
||||
_inherit = 'stock.move'
|
||||
_columns= {
|
||||
'procurements': fields.one2many('procurement.order', 'move_id', 'Procurements'),
|
||||
class procurement_rule(osv.osv):
|
||||
'''
|
||||
A rule describe what a procurement should do; produce, buy, move, ...
|
||||
'''
|
||||
_name = 'procurement.rule'
|
||||
_description = "Procurement Rule"
|
||||
_order = "name"
|
||||
|
||||
def _get_action(self, cr, uid, context=None):
|
||||
return []
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True,
|
||||
help="This field will fill the packing origin and the name of its moves"),
|
||||
'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the rule without removing it."),
|
||||
'group_propagation_option': fields.selection([('none', 'Leave Empty'), ('propagate', 'Propagate'), ('fixed', 'Fixed')], string="Propagation of Procurement Group"),
|
||||
'group_id': fields.many2one('procurement.group', 'Fixed Procurement Group'),
|
||||
'action': fields.selection(selection=lambda s, cr, uid, context=None: s._get_action(cr, uid, context=context),
|
||||
string='Action', required=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'company_id': fields.many2one('res.company', 'Company'),
|
||||
}
|
||||
|
||||
def copy_data(self, cr, uid, id, default=None, context=None):
|
||||
if default is None:
|
||||
default = {}
|
||||
default['procurements'] = []
|
||||
return super(StockMove, self).copy_data(cr, uid, id, default, context=context)
|
||||
_defaults = {
|
||||
'group_propagation_option': 'propagate',
|
||||
'sequence': 20,
|
||||
'active': True,
|
||||
}
|
||||
|
||||
|
||||
class procurement_order(osv.osv):
|
||||
|
@ -80,81 +101,70 @@ class procurement_order(osv.osv):
|
|||
"""
|
||||
_name = "procurement.order"
|
||||
_description = "Procurement"
|
||||
_order = 'priority desc,date_planned'
|
||||
_order = 'priority desc, date_planned, id asc'
|
||||
_inherit = ['mail.thread']
|
||||
_log_create = False
|
||||
_columns = {
|
||||
'name': fields.text('Description', required=True),
|
||||
|
||||
'origin': fields.char('Source Document', size=64,
|
||||
help="Reference of the document that created this Procurement.\n"
|
||||
"This is automatically completed by OpenERP."),
|
||||
'priority': fields.selection([('0','Not urgent'),('1','Normal'),('2','Urgent'),('3','Very Urgent')], 'Priority', required=True, select=True),
|
||||
'date_planned': fields.datetime('Scheduled date', required=True, select=True),
|
||||
'date_close': fields.datetime('Date Closed'),
|
||||
'product_id': fields.many2one('product.product', 'Product', required=True, states={'draft':[('readonly',False)]}, readonly=True),
|
||||
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'draft':[('readonly',False)]}, readonly=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, states={'draft':[('readonly',False)]}, readonly=True),
|
||||
'product_uos_qty': fields.float('UoS Quantity', states={'draft':[('readonly',False)]}, readonly=True),
|
||||
'product_uos': fields.many2one('product.uom', 'Product UoS', states={'draft':[('readonly',False)]}, readonly=True),
|
||||
'move_id': fields.many2one('stock.move', 'Reservation', ondelete='set null'),
|
||||
'close_move': fields.boolean('Close Move at end'),
|
||||
'location_id': fields.many2one('stock.location', 'Location', required=True, states={'draft':[('readonly',False)]}, readonly=True),
|
||||
'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', states={'draft':[('readonly',False)], 'confirmed':[('readonly',False)]},
|
||||
readonly=True, required=True, help="If you encode manually a Procurement, you probably want to use" \
|
||||
" a make to order method."),
|
||||
'note': fields.text('Note'),
|
||||
'message': fields.text('Latest error', help="Exception occurred while computing procurement orders."),
|
||||
'company_id': fields.many2one('res.company', 'Company', required=True),
|
||||
|
||||
# These two fields are used for shceduling
|
||||
'priority': fields.selection([('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')], 'Priority', required=True, select=True, track_visibility='onchange'),
|
||||
'date_planned': fields.datetime('Scheduled Date', required=True, select=True, track_visibility='onchange'),
|
||||
|
||||
'group_id': fields.many2one('procurement.group', 'Procurement Group'),
|
||||
'rule_id': fields.many2one('procurement.rule', 'Rule', track_visibility='onchange', help="Chosen rule for the procurement resolution. Usually chosen by the system but can be manually set by the procurement manager to force an unusual behavior."),
|
||||
|
||||
'product_id': fields.many2one('product.product', 'Product', required=True, states={'confirmed': [('readonly', False)]}, readonly=True),
|
||||
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, states={'confirmed': [('readonly', False)]}, readonly=True),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True, states={'confirmed': [('readonly', False)]}, readonly=True),
|
||||
|
||||
'product_uos_qty': fields.float('UoS Quantity', states={'confirmed': [('readonly', False)]}, readonly=True),
|
||||
'product_uos': fields.many2one('product.uom', 'Product UoS', states={'confirmed': [('readonly', False)]}, readonly=True),
|
||||
|
||||
'state': fields.selection([
|
||||
('draft','Draft'),
|
||||
('cancel','Cancelled'),
|
||||
('confirmed','Confirmed'),
|
||||
('exception','Exception'),
|
||||
('running','Running'),
|
||||
('ready','Ready'),
|
||||
('done','Done'),
|
||||
('waiting','Waiting')], 'Status', required=True, track_visibility='onchange',
|
||||
help='When a procurement is created the status is set to \'Draft\'.\n If the procurement is confirmed, the status is set to \'Confirmed\'.\
|
||||
\nAfter confirming the status is set to \'Running\'.\n If any exception arises in the order then the status is set to \'Exception\'.\n Once the exception is removed the status becomes \'Ready\'.\n It is in \'Waiting\'. status when the procurement is waiting for another one to finish.'),
|
||||
'note': fields.text('Note'),
|
||||
'company_id': fields.many2one('res.company','Company',required=True),
|
||||
('cancel', 'Cancelled'),
|
||||
('confirmed', 'Confirmed'),
|
||||
('exception', 'Exception'),
|
||||
('running', 'Running'),
|
||||
('done', 'Done')
|
||||
], 'Status', required=True, track_visibility='onchange'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'state': 'draft',
|
||||
'state': 'confirmed',
|
||||
'priority': '1',
|
||||
'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'close_move': 0,
|
||||
'procure_method': 'make_to_order',
|
||||
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'procurement.order', context=c)
|
||||
}
|
||||
|
||||
def message_track(self, cr, uid, ids, tracked_fields, initial_values, context=None):
|
||||
""" Overwrite message_track to avoid tracking more than once the confirm-exception loop
|
||||
Add '_first_pass_done_' to the note field only the first time stuck in exception state
|
||||
Will avoid getting furthur confirmed and exception change of state messages
|
||||
|
||||
TODO: this hack is necessary for a stable version but should be avoided for the next release.
|
||||
Instead find a more elegant way to prevent redundant messages or entirely stop tracking states on procurement orders
|
||||
"""
|
||||
for proc in self.browse(cr, uid, ids, context=context):
|
||||
if not proc.note or '_first_pass_done_' not in proc.note or proc.state not in ('confirmed', 'exception'):
|
||||
super(procurement_order, self).message_track(cr, uid, [proc.id], tracked_fields, initial_values, context=context)
|
||||
if proc.state == 'exception':
|
||||
cr.execute("""UPDATE procurement_order set note = TRIM(both E'\n' FROM COALESCE(note, '') || %s) WHERE id = %s""", ('\n\n_first_pass_done_',proc.id))
|
||||
|
||||
return True
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
procurements = self.read(cr, uid, ids, ['state'], context=context)
|
||||
unlink_ids = []
|
||||
for s in procurements:
|
||||
if s['state'] in ['draft','cancel']:
|
||||
if s['state'] == 'cancel':
|
||||
unlink_ids.append(s['id'])
|
||||
else:
|
||||
raise osv.except_osv(_('Invalid Action!'),
|
||||
_('Cannot delete Procurement Order(s) which are in %s state.') % \
|
||||
s['state'])
|
||||
_('Cannot delete Procurement Order(s) which are in %s state.') % s['state'])
|
||||
return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
|
||||
|
||||
def do_view_procurements(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
This function returns an action that display existing procurement orders
|
||||
of same procurement group of given ids.
|
||||
'''
|
||||
act_obj = self.pool.get('ir.actions.act_window')
|
||||
action_id = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'procurement.do_view_procurements', raise_if_not_found=True)
|
||||
result = act_obj.read(cr, uid, [action_id], context=context)[0]
|
||||
group_ids = set([proc.group_id.id for proc in self.browse(cr, uid, ids, context=context) if proc.group_id])
|
||||
result['domain'] = "[('group_id','in',[" + ','.join(map(str, list(group_ids))) + "])]"
|
||||
return result
|
||||
|
||||
def onchange_product_id(self, cr, uid, ids, product_id, context=None):
|
||||
""" Finds UoM and UoS of changed product.
|
||||
@param product_id: Changed id of product.
|
||||
|
@ -169,461 +179,137 @@ class procurement_order(osv.osv):
|
|||
return {'value': v}
|
||||
return {}
|
||||
|
||||
def is_product(self, cr, uid, ids, context=None):
|
||||
""" Checks product type to decide which transition of the workflow to follow.
|
||||
@return: True if all product ids received in argument are of type 'product' or 'consummable'. False if any is of type 'service'
|
||||
"""
|
||||
return all(proc.product_id.type in ('product', 'consu') for proc in self.browse(cr, uid, ids, context=context))
|
||||
def get_cancel_ids(self, cr, uid, ids, context=None):
|
||||
return [proc.id for proc in self.browse(cr, uid, ids, context=context) if proc.state != 'done']
|
||||
|
||||
def check_move_cancel(self, cr, uid, ids, context=None):
|
||||
""" Checks if move is cancelled or not.
|
||||
@return: True or False.
|
||||
"""
|
||||
return all(procurement.move_id.state == 'cancel' for procurement in self.browse(cr, uid, ids, context=context))
|
||||
def cancel(self, cr, uid, ids, context=None):
|
||||
#cancel only the procurements that aren't done already
|
||||
to_cancel_ids = self.get_cancel_ids(cr, uid, ids, context=context)
|
||||
if to_cancel_ids:
|
||||
return self.write(cr, uid, to_cancel_ids, {'state': 'cancel'}, context=context)
|
||||
|
||||
def check_move_done(self, cr, uid, ids, context=None):
|
||||
""" Checks if move is done or not.
|
||||
@return: True or False.
|
||||
"""
|
||||
return all(proc.product_id.type == 'service' or (proc.move_id and proc.move_id.state == 'done') \
|
||||
for proc in self.browse(cr, uid, ids, context=context))
|
||||
def reset_to_confirmed(self, cr, uid, ids, context=None):
|
||||
return self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
|
||||
|
||||
#
|
||||
# This method may be overrided by objects that override procurement.order
|
||||
# for computing their own purpose
|
||||
#
|
||||
def _quantity_compute_get(self, cr, uid, proc, context=None):
|
||||
""" Finds sold quantity of product.
|
||||
@param proc: Current procurement.
|
||||
@return: Quantity or False.
|
||||
"""
|
||||
if proc.product_id.type == 'product' and proc.move_id:
|
||||
if proc.move_id.product_uos:
|
||||
return proc.move_id.product_uos_qty
|
||||
return False
|
||||
def run(self, cr, uid, ids, context=None):
|
||||
for procurement_id in ids:
|
||||
#we intentionnaly do the browse under the for loop to avoid caching all ids which would be ressource greedy
|
||||
#and useless as we'll make a refresh later that will invalidate all the cache (and thus the next iteration
|
||||
#will fetch all the ids again)
|
||||
procurement = self.browse(cr, uid, procurement_id, context=context)
|
||||
if procurement.state not in ("running", "done"):
|
||||
if self._assign(cr, uid, procurement, context=context):
|
||||
procurement.refresh()
|
||||
res = self._run(cr, uid, procurement, context=context or {})
|
||||
if res:
|
||||
self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context)
|
||||
else:
|
||||
self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context)
|
||||
else:
|
||||
self.message_post(cr, uid, [procurement.id], body=_('No rule matching this procurement'), context=context)
|
||||
self.write(cr, uid, [procurement.id], {'state': 'exception'}, context=context)
|
||||
return True
|
||||
|
||||
def _uom_compute_get(self, cr, uid, proc, context=None):
|
||||
""" Finds UoS if product is Stockable Product.
|
||||
@param proc: Current procurement.
|
||||
@return: UoS or False.
|
||||
"""
|
||||
if proc.product_id.type == 'product' and proc.move_id:
|
||||
if proc.move_id.product_uos:
|
||||
return proc.move_id.product_uos.id
|
||||
return False
|
||||
|
||||
#
|
||||
# Return the quantity of product shipped/produced/served, which may be
|
||||
# different from the planned quantity
|
||||
#
|
||||
def quantity_get(self, cr, uid, id, context=None):
|
||||
""" Finds quantity of product used in procurement.
|
||||
@return: Quantity of product.
|
||||
"""
|
||||
proc = self.browse(cr, uid, id, context=context)
|
||||
result = self._quantity_compute_get(cr, uid, proc, context=context)
|
||||
if not result:
|
||||
result = proc.product_qty
|
||||
return result
|
||||
|
||||
def uom_get(self, cr, uid, id, context=None):
|
||||
""" Finds UoM of product used in procurement.
|
||||
@return: UoM of product.
|
||||
"""
|
||||
proc = self.browse(cr, uid, id, context=context)
|
||||
result = self._uom_compute_get(cr, uid, proc, context=context)
|
||||
if not result:
|
||||
result = proc.product_uom.id
|
||||
return result
|
||||
|
||||
def check_waiting(self, cr, uid, ids, context=None):
|
||||
""" Checks state of move.
|
||||
@return: True or False
|
||||
"""
|
||||
def check(self, cr, uid, ids, context=None):
|
||||
done_ids = []
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
if procurement.move_id and procurement.move_id.state == 'auto':
|
||||
result = self._check(cr, uid, procurement, context=context)
|
||||
if result:
|
||||
done_ids.append(procurement.id)
|
||||
if done_ids:
|
||||
self.write(cr, uid, done_ids, {'state': 'done'}, context=context)
|
||||
return done_ids
|
||||
|
||||
#
|
||||
# Method to overwrite in different procurement modules
|
||||
#
|
||||
def _find_suitable_rule(self, cr, uid, procurement, context=None):
|
||||
'''This method returns a procurement.rule that depicts what to do with the given procurement
|
||||
in order to complete its needs. It returns False if no suiting rule is found.
|
||||
:param procurement: browse record
|
||||
:rtype: int or False
|
||||
'''
|
||||
return False
|
||||
|
||||
def _assign(self, cr, uid, procurement, context=None):
|
||||
'''This method check what to do with the given procurement in order to complete its needs.
|
||||
It returns False if no solution is found, otherwise it stores the matching rule (if any) and
|
||||
returns True.
|
||||
:param procurement: browse record
|
||||
:rtype: boolean
|
||||
'''
|
||||
#if the procurement already has a rule assigned, we keep it (it has a higher priority as it may have been chosen manually)
|
||||
if procurement.rule_id:
|
||||
return True
|
||||
elif procurement.product_id.type != 'service':
|
||||
rule_id = self._find_suitable_rule(cr, uid, procurement, context=context)
|
||||
if rule_id:
|
||||
self.write(cr, uid, [procurement.id], {'rule_id': rule_id}, context=context)
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_produce_service(self, cr, uid, procurement, context=None):
|
||||
""" Depicts the capacity of the procurement workflow to deal with production of services.
|
||||
By default, it's False. Overwritten by project_mrp module.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_produce_product(self, cr, uid, procurement, context=None):
|
||||
""" Depicts the capacity of the procurement workflow to deal with production of products.
|
||||
By default, it's False. Overwritten by mrp module.
|
||||
"""
|
||||
return False
|
||||
|
||||
def check_make_to_stock(self, cr, uid, ids, context=None):
|
||||
""" Checks product type.
|
||||
@return: True or False
|
||||
"""
|
||||
ok = True
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
if procurement.product_id.type == 'service':
|
||||
ok = ok and self._check_make_to_stock_service(cr, uid, procurement, context)
|
||||
else:
|
||||
ok = ok and self._check_make_to_stock_product(cr, uid, procurement, context)
|
||||
return ok
|
||||
|
||||
def check_produce(self, cr, uid, ids, context=None):
|
||||
""" Checks product type.
|
||||
@return: True or False
|
||||
"""
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
product = procurement.product_id
|
||||
#TOFIX: if product type is 'service' but supply_method is 'buy'.
|
||||
if product.supply_method <> 'produce':
|
||||
return False
|
||||
if product.type=='service':
|
||||
res = self.check_produce_service(cr, uid, procurement, context)
|
||||
else:
|
||||
res = self.check_produce_product(cr, uid, procurement, context)
|
||||
if not res:
|
||||
return False
|
||||
def _run(self, cr, uid, procurement, context=None):
|
||||
'''This method implements the resolution of the given procurement
|
||||
:param procurement: browse record
|
||||
:returns: True if the resolution of the procurement was a success, False otherwise to set it in exception
|
||||
'''
|
||||
return True
|
||||
|
||||
def check_buy(self, cr, uid, ids):
|
||||
""" Depicts the capacity of the procurement workflow to manage the supply_method == 'buy'.
|
||||
By default, it's False. Overwritten by purchase module.
|
||||
"""
|
||||
def _check(self, cr, uid, procurement, context=None):
|
||||
'''Returns True if the given procurement is fulfilled, False otherwise
|
||||
:param procurement: browse record
|
||||
:rtype: boolean
|
||||
'''
|
||||
return False
|
||||
|
||||
def check_move(self, cr, uid, ids, context=None):
|
||||
""" Check whether the given procurement can be satisfied by an internal move,
|
||||
typically a pulled flow. By default, it's False. Overwritten by the `stock_location` module.
|
||||
"""
|
||||
return False
|
||||
#
|
||||
# Scheduler
|
||||
#
|
||||
def run_scheduler(self, cr, uid, use_new_cursor=False, context=None):
|
||||
'''
|
||||
Call the scheduler to check the procurement order. This is intented to be done for all existing companies at
|
||||
the same time, so we're running all the methods as SUPERUSER to avoid intercompany and access rights issues.
|
||||
|
||||
def check_conditions_confirm2wait(self, cr, uid, ids):
|
||||
""" condition on the transition to go from 'confirm' activity to 'confirm_wait' activity """
|
||||
return not self.test_cancel(cr, uid, ids)
|
||||
|
||||
def test_cancel(self, cr, uid, ids):
|
||||
""" Tests whether state of move is cancelled or not.
|
||||
@return: True or False
|
||||
"""
|
||||
for record in self.browse(cr, uid, ids):
|
||||
if record.move_id and record.move_id.state == 'cancel':
|
||||
return True
|
||||
return False
|
||||
|
||||
#Initialize get_phantom_bom_id method as it is raising an error from yml of mrp_jit
|
||||
#when one install first mrp and after that, mrp_jit. get_phantom_bom_id defined in mrp module
|
||||
#which is not dependent for mrp_jit.
|
||||
def get_phantom_bom_id(self, cr, uid, ids, context=None):
|
||||
return False
|
||||
|
||||
def action_confirm(self, cr, uid, ids, context=None):
|
||||
""" Confirms procurement and writes exception message if any.
|
||||
@return: True
|
||||
"""
|
||||
move_obj = self.pool.get('stock.move')
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
if procurement.product_qty <= 0.00:
|
||||
raise osv.except_osv(_('Data Insufficient!'),
|
||||
_('Please check the quantity in procurement order(s) for the product "%s", it should not be 0 or less!' % procurement.product_id.name))
|
||||
if procurement.product_id.type in ('product', 'consu'):
|
||||
if not procurement.move_id:
|
||||
source = procurement.location_id.id
|
||||
if procurement.procure_method == 'make_to_order':
|
||||
source = procurement.product_id.property_stock_procurement.id
|
||||
id = move_obj.create(cr, uid, {
|
||||
'name': procurement.name,
|
||||
'location_id': source,
|
||||
'location_dest_id': procurement.location_id.id,
|
||||
'product_id': procurement.product_id.id,
|
||||
'product_qty': procurement.product_qty,
|
||||
'product_uom': procurement.product_uom.id,
|
||||
'date_expected': procurement.date_planned,
|
||||
'state': 'draft',
|
||||
'company_id': procurement.company_id.id,
|
||||
'auto_validate': True,
|
||||
})
|
||||
move_obj.action_confirm(cr, uid, [id], context=context)
|
||||
self.write(cr, uid, [procurement.id], {'move_id': id, 'close_move': 1})
|
||||
self.write(cr, uid, ids, {'state': 'confirmed', 'message': ''})
|
||||
return True
|
||||
|
||||
def action_move_assigned(self, cr, uid, ids, context=None):
|
||||
""" Changes procurement state to Running and writes message.
|
||||
@return: True
|
||||
"""
|
||||
message = _('Products reserved from stock.')
|
||||
self.write(cr, uid, ids, {'state': 'running',
|
||||
'message': message}, context=context)
|
||||
self.message_post(cr, uid, ids, body=message, context=context)
|
||||
return True
|
||||
|
||||
def _check_make_to_stock_service(self, cr, uid, procurement, context=None):
|
||||
"""
|
||||
This method may be overrided by objects that override procurement.order
|
||||
for computing their own purpose
|
||||
@return: True"""
|
||||
return True
|
||||
|
||||
def _check_make_to_stock_product(self, cr, uid, procurement, context=None):
|
||||
""" Checks procurement move state.
|
||||
@param procurement: Current procurement.
|
||||
@return: True or move id.
|
||||
"""
|
||||
ok = True
|
||||
if procurement.move_id:
|
||||
message = False
|
||||
id = procurement.move_id.id
|
||||
if not (procurement.move_id.state in ('done','assigned','cancel')):
|
||||
ok = ok and self.pool.get('stock.move').action_assign(cr, uid, [id])
|
||||
order_point_id = self.pool.get('stock.warehouse.orderpoint').search(cr, uid, [('product_id', '=', procurement.product_id.id)], context=context)
|
||||
if not order_point_id and not ok:
|
||||
message = _("Not enough stock and no minimum orderpoint rule defined.")
|
||||
elif not ok:
|
||||
message = _("Not enough stock.")
|
||||
|
||||
if message:
|
||||
message = _("Procurement '%s' is in exception: ") % (procurement.name) + message
|
||||
#temporary context passed in write to prevent an infinite loop
|
||||
ctx_wkf = dict(context or {})
|
||||
ctx_wkf['workflow.trg_write.%s' % self._name] = False
|
||||
self.write(cr, uid, [procurement.id], {'message': message},context=ctx_wkf)
|
||||
return ok
|
||||
|
||||
def step_workflow(self, cr, uid, ids, context=None):
|
||||
""" Don't trigger workflow for the element specified in trigger """
|
||||
wkf_op_key = 'workflow.trg_write.%s' % self._name
|
||||
if context and not context.get(wkf_op_key, True):
|
||||
# make sure we don't have a trigger loop while processing triggers
|
||||
return
|
||||
return super(procurement_order, self).step_workflow(cr, uid, ids, context=context)
|
||||
|
||||
def action_produce_assign_service(self, cr, uid, ids, context=None):
|
||||
""" Changes procurement state to Running.
|
||||
@return: True
|
||||
"""
|
||||
for procurement in self.browse(cr, uid, ids, context=context):
|
||||
self.write(cr, uid, [procurement.id], {'state': 'running'})
|
||||
return True
|
||||
|
||||
def action_produce_assign_product(self, cr, uid, ids, context=None):
|
||||
""" This is action which call from workflow to assign production order to procurements
|
||||
@return: True
|
||||
"""
|
||||
return 0
|
||||
|
||||
|
||||
def action_po_assign(self, cr, uid, ids, context=None):
|
||||
""" This is action which call from workflow to assign purchase order to procurements
|
||||
@return: True
|
||||
"""
|
||||
return 0
|
||||
|
||||
# XXX action_cancel() should accept a context argument
|
||||
def action_cancel(self, cr, uid, ids):
|
||||
"""Cancel Procurements and either cancel or assign the related Stock Moves, depending on the procurement configuration.
|
||||
|
||||
@return: True
|
||||
"""
|
||||
to_assign = []
|
||||
to_cancel = []
|
||||
move_obj = self.pool.get('stock.move')
|
||||
for proc in self.browse(cr, uid, ids):
|
||||
if proc.close_move and proc.move_id:
|
||||
if proc.move_id.state not in ('done', 'cancel'):
|
||||
to_cancel.append(proc.move_id.id)
|
||||
else:
|
||||
if proc.move_id and proc.move_id.state == 'waiting':
|
||||
to_assign.append(proc.move_id.id)
|
||||
if len(to_cancel):
|
||||
move_obj.action_cancel(cr, uid, to_cancel)
|
||||
if len(to_assign):
|
||||
move_obj.write(cr, uid, to_assign, {'state': 'confirmed'})
|
||||
move_obj.action_assign(cr, uid, to_assign)
|
||||
self.write(cr, uid, ids, {'state': 'cancel'})
|
||||
for id in ids:
|
||||
workflow.trg_trigger(uid, 'procurement.order', id, cr)
|
||||
return True
|
||||
|
||||
def action_check_finished(self, cr, uid, ids):
|
||||
return self.check_move_done(cr, uid, ids)
|
||||
|
||||
def action_check(self, cr, uid, ids):
|
||||
""" Checks procurement move state whether assigned or done.
|
||||
@return: True
|
||||
"""
|
||||
ok = False
|
||||
for procurement in self.browse(cr, uid, ids):
|
||||
if procurement.move_id and procurement.move_id.state == 'assigned' or procurement.move_id.state == 'done':
|
||||
self.action_done(cr, uid, [procurement.id])
|
||||
ok = True
|
||||
return ok
|
||||
|
||||
def action_ready(self, cr, uid, ids):
|
||||
""" Changes procurement state to Ready.
|
||||
@return: True
|
||||
"""
|
||||
res = self.write(cr, uid, ids, {'state': 'ready'})
|
||||
return res
|
||||
|
||||
def action_done(self, cr, uid, ids):
|
||||
""" Changes procurement state to Done and writes Closed date.
|
||||
@return: True
|
||||
"""
|
||||
move_obj = self.pool.get('stock.move')
|
||||
for procurement in self.browse(cr, uid, ids):
|
||||
if procurement.move_id:
|
||||
if procurement.close_move and (procurement.move_id.state <> 'done'):
|
||||
move_obj.action_done(cr, uid, [procurement.move_id.id])
|
||||
res = self.write(cr, uid, ids, {'state': 'done', 'date_close': time.strftime('%Y-%m-%d')})
|
||||
for id in ids:
|
||||
workflow.trg_trigger(uid, 'procurement.order', id, cr)
|
||||
return res
|
||||
|
||||
class StockPicking(osv.osv):
|
||||
_inherit = 'stock.picking'
|
||||
def test_finished(self, cr, uid, ids):
|
||||
res = super(StockPicking, self).test_finished(cr, uid, ids)
|
||||
for picking in self.browse(cr, uid, ids):
|
||||
for move in picking.move_lines:
|
||||
if move.state == 'done' and move.procurements:
|
||||
self.pool.get('procurement.order').signal_button_check(cr, uid, map(attrgetter('id'), move.procurements))
|
||||
return res
|
||||
|
||||
class stock_warehouse_orderpoint(osv.osv):
|
||||
"""
|
||||
Defines Minimum stock rules.
|
||||
"""
|
||||
_name = "stock.warehouse.orderpoint"
|
||||
_description = "Minimum Inventory Rule"
|
||||
|
||||
def _get_draft_procurements(self, cr, uid, ids, field_name, arg, context=None):
|
||||
@param self: The object pointer
|
||||
@param cr: The current row, from the database cursor,
|
||||
@param uid: The current user ID for security checks
|
||||
@param ids: List of selected IDs
|
||||
@param use_new_cursor: False or the dbname
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of values
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
result = {}
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
for orderpoint in self.browse(cr, uid, ids, context=context):
|
||||
procurement_ids = procurement_obj.search(cr, uid , [('state', '=', 'draft'), ('product_id', '=', orderpoint.product_id.id), ('location_id', '=', orderpoint.location_id.id)])
|
||||
result[orderpoint.id] = procurement_ids
|
||||
return result
|
||||
try:
|
||||
if use_new_cursor:
|
||||
cr = openerp.registry(use_new_cursor).cursor()
|
||||
|
||||
def _check_product_uom(self, cr, uid, ids, context=None):
|
||||
'''
|
||||
Check if the UoM has the same category as the product standard UoM
|
||||
'''
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
for rule in self.browse(cr, uid, ids, context=context):
|
||||
if rule.product_id.uom_id.category_id.id != rule.product_uom.category_id.id:
|
||||
return False
|
||||
|
||||
return True
|
||||
# Run confirmed procurements
|
||||
while True:
|
||||
ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'confirmed')], context=context)
|
||||
if not ids:
|
||||
break
|
||||
self.run(cr, SUPERUSER_ID, ids, context=context)
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', size=32, required=True),
|
||||
'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the orderpoint without removing it."),
|
||||
'logic': fields.selection([('max','Order to Max'),('price','Best price (not yet active!)')], 'Reordering Mode', required=True),
|
||||
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True, ondelete="cascade"),
|
||||
'location_id': fields.many2one('stock.location', 'Location', required=True, ondelete="cascade"),
|
||||
'product_id': fields.many2one('product.product', 'Product', required=True, ondelete='cascade', domain=[('type','!=','service')]),
|
||||
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
|
||||
'product_min_qty': fields.float('Minimum Quantity', required=True,
|
||||
help="When the virtual stock goes below the Min Quantity specified for this field, OpenERP generates "\
|
||||
"a procurement to bring the forecasted quantity to the Max Quantity."),
|
||||
'product_max_qty': fields.float('Maximum Quantity', required=True,
|
||||
help="When the virtual stock goes below the Min Quantity, OpenERP generates "\
|
||||
"a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity."),
|
||||
'qty_multiple': fields.integer('Qty Multiple', required=True,
|
||||
help="The procurement quantity will be rounded up to this multiple."),
|
||||
'procurement_id': fields.many2one('procurement.order', 'Latest procurement', ondelete="set null"),
|
||||
'company_id': fields.many2one('res.company','Company',required=True),
|
||||
'procurement_draft_ids': fields.function(_get_draft_procurements, type='many2many', relation="procurement.order", \
|
||||
string="Related Procurement Orders",help="Draft procurement of the product and location of that orderpoint"),
|
||||
}
|
||||
_defaults = {
|
||||
'active': lambda *a: 1,
|
||||
'logic': lambda *a: 'max',
|
||||
'qty_multiple': lambda *a: 1,
|
||||
'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.orderpoint') or '',
|
||||
'product_uom': lambda sel, cr, uid, context: context.get('product_uom', False),
|
||||
'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=c)
|
||||
}
|
||||
_sql_constraints = [
|
||||
('qty_multiple_check', 'CHECK( qty_multiple > 0 )', 'Qty Multiple must be greater than zero.'),
|
||||
]
|
||||
_constraints = [
|
||||
(_check_product_uom, 'You have to select a product unit of measure in the same category than the default unit of measure of the product', ['product_id', 'product_uom']),
|
||||
]
|
||||
# Check if running procurements are done
|
||||
offset = 0
|
||||
while True:
|
||||
ids = self.search(cr, SUPERUSER_ID, [('state', '=', 'running')], offset=offset, context=context)
|
||||
if not ids:
|
||||
break
|
||||
done = self.check(cr, SUPERUSER_ID, ids, context=context)
|
||||
offset += len(ids) - len(done)
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
warehouse_obj = self.pool.get('stock.warehouse')
|
||||
res = super(stock_warehouse_orderpoint, self).default_get(cr, uid, fields, context)
|
||||
# default 'warehouse_id' and 'location_id'
|
||||
if 'warehouse_id' not in res:
|
||||
warehouse_ids = res.get('company_id') and warehouse_obj.search(cr, uid, [('company_id', '=', res['company_id'])], limit=1, context=context) or []
|
||||
res['warehouse_id'] = warehouse_ids and warehouse_ids[0] or False
|
||||
if 'location_id' not in res:
|
||||
res['location_id'] = res.get('warehouse_id') and warehouse_obj.browse(cr, uid, res['warehouse_id'], context).lot_stock_id.id or False
|
||||
return res
|
||||
finally:
|
||||
if use_new_cursor:
|
||||
try:
|
||||
cr.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
|
||||
""" Finds location id for changed warehouse.
|
||||
@param warehouse_id: Changed id of warehouse.
|
||||
@return: Dictionary of values.
|
||||
"""
|
||||
if warehouse_id:
|
||||
w = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
|
||||
v = {'location_id': w.lot_stock_id.id}
|
||||
return {'value': v}
|
||||
return {}
|
||||
|
||||
def onchange_product_id(self, cr, uid, ids, product_id, context=None):
|
||||
""" Finds UoM for changed product.
|
||||
@param product_id: Changed id of product.
|
||||
@return: Dictionary of values.
|
||||
"""
|
||||
if product_id:
|
||||
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
|
||||
d = {'product_uom': [('category_id', '=', prod.uom_id.category_id.id)]}
|
||||
v = {'product_uom': prod.uom_id.id}
|
||||
return {'value': v, 'domain': d}
|
||||
return {'domain': {'product_uom': []}}
|
||||
|
||||
def copy(self, cr, uid, id, default=None, context=None):
|
||||
if not default:
|
||||
default = {}
|
||||
default.update({
|
||||
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
|
||||
})
|
||||
return super(stock_warehouse_orderpoint, self).copy(cr, uid, id, default, context=context)
|
||||
|
||||
class product_template(osv.osv):
|
||||
_inherit="product.template"
|
||||
|
||||
_columns = {
|
||||
'type': fields.selection([('product','Stockable Product'),('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."),
|
||||
'procure_method': fields.selection([('make_to_stock','Make to Stock'),('make_to_order','Make to Order')], 'Procurement Method', required=True, help="Make to Stock: When needed, the product is taken from the stock or we wait for replenishment. \nMake to Order: When needed, the product is purchased or produced."),
|
||||
'supply_method': fields.selection([('produce','Manufacture'),('buy','Buy')], 'Supply Method', required=True, help="Manufacture: When procuring the product, a manufacturing order or a task will be generated, depending on the product type. \nBuy: When procuring the product, a purchase order will be generated."),
|
||||
}
|
||||
_defaults = {
|
||||
'procure_method': 'make_to_stock',
|
||||
'supply_method': 'buy',
|
||||
}
|
||||
|
||||
class product_product(osv.osv):
|
||||
_inherit="product.product"
|
||||
def _orderpoint_count(self, cr, uid, ids, field_name, arg, context=None):
|
||||
OrderPoints = self.pool('stock.warehouse.orderpoint')
|
||||
return {
|
||||
product_id: OrderPoints.search_count(cr, uid, [('product_id', '=', product_id)], context=context)
|
||||
for product_id in ids
|
||||
}
|
||||
_columns = {
|
||||
'orderpoint_count': fields.function(_orderpoint_count, string='# Orderpoints', type='integer'),
|
||||
}
|
||||
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -12,33 +12,22 @@
|
|||
<field eval="False" name="doall"/>
|
||||
<field eval="'procurement.order'" name="model"/>
|
||||
<field eval="'run_scheduler'" name="function"/>
|
||||
<field eval="'(False,True)'" name="args"/>
|
||||
<field eval="'(True,)'" name="args"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="ir_cron_scheduler_action_fast" model="ir.cron">
|
||||
<field name="name">Run fast mrp scheduler (without exception)</field>
|
||||
<field eval="False" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">4</field>
|
||||
<field name="interval_type">hours</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
<field eval="'procurement.order'" name="model"/>
|
||||
<field eval="'run_scheduler'" name="function"/>
|
||||
<field eval="'(False,True,True)'" name="args"/>
|
||||
|
||||
<record id="sequence_proc_group_type" model="ir.sequence.type">
|
||||
<field name="name">Procurement Group</field>
|
||||
<field name="code">procurement.group</field>
|
||||
</record>
|
||||
|
||||
<record id="sequence_mrp_op_type" model="ir.sequence.type">
|
||||
<field name="name">Stock orderpoint</field>
|
||||
<field name="code">stock.orderpoint</field>
|
||||
</record>
|
||||
|
||||
<record id="sequence_mrp_op" model="ir.sequence">
|
||||
<field name="name">Stock orderpoint</field>
|
||||
<field name="code">stock.orderpoint</field>
|
||||
<field name="prefix">OP/</field>
|
||||
<field name="padding">5</field>
|
||||
<record id="sequence_proc_group" model="ir.sequence">
|
||||
<field name="name">Procurement Group</field>
|
||||
<field name="code">procurement.group</field>
|
||||
<field name="prefix">PG/</field>
|
||||
<field name="padding">6</field>
|
||||
<field name="number_next">1</field>
|
||||
<field name="number_increment">1</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
||||
|
|
|
@ -16,82 +16,64 @@
|
|||
<field name="product_id"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="product_uom" string="Unit of Measure"/>
|
||||
<field name="procure_method"/>
|
||||
<field name="state"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<field name="message"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="procurement_tree_view_board" model="ir.ui.view">
|
||||
<field name="name">procurement.order.tree.board</field>
|
||||
<field name="model">procurement.order</field>
|
||||
<field eval="20" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Procurement Lines" colors="red:date_planned<current_date and state == 'exception';black:state=='running';darkgreen:state=='confirmed';gray:state in ['done','cancel'];blue:state == 'ready'">
|
||||
<field name="date_planned" widget="date"/>
|
||||
<field name="origin"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_qty"/>
|
||||
<field name="product_uom" string="Unit of Measure"/>
|
||||
<field name="state" invisible = "1"/>
|
||||
<field name="message"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="procurement_form_view" model="ir.ui.view">
|
||||
<field name="name">procurement.order.form</field>
|
||||
<field name="model">procurement.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Procurement" version="7.0">
|
||||
<header>
|
||||
<button name="button_confirm" states="draft" string="Confirm" class="oe_highlight"/>
|
||||
<button name="button_check" states="confirmed" string="Run Procurement" class="oe_highlight"/>
|
||||
<button name="button_restart" states="exception" string="Retry" class="oe_highlight"/>
|
||||
<button name="button_cancel" states="draft,exception,waiting" string="Cancel Procurement"/>
|
||||
<field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,confirmed" />
|
||||
<button name="run" states="confirmed,exception" string="Run Procurement" class="oe_highlight" type="object"/>
|
||||
<button name="check" states="running" string="Check Procurement" class="oe_highlight" type="object"/>
|
||||
<button name="cancel" states="exception,confirmed,running" string="Cancel Procurement" type="object"/>
|
||||
<button name="reset_to_confirmed" states="cancel" string="Reconfirm Procurement" type="object"/>
|
||||
<field name="state" readonly="1" widget="statusbar" statusbar_visible="draft,confirmed,running,done" />
|
||||
</header>
|
||||
<sheet>
|
||||
<label for="product_id" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id)"/>
|
||||
</h1>
|
||||
<label for="product_qty" class="oe_edit_only"/>
|
||||
<h2>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline"/>
|
||||
<field name="product_uom" class="oe_inline" groups="product.group_uom"/>
|
||||
</div>
|
||||
</h2>
|
||||
<field name="name" placeholder="External note..."/>
|
||||
<div class="oe_right oe_button_box" name="button_box">
|
||||
<button name="do_view_procurements" string="Group's Procurements" type="object"/>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="date_planned"/>
|
||||
<field name="procure_method"/>
|
||||
<field name="priority"/>
|
||||
<field name="product_id" on_change="onchange_product_id(product_id)"/>
|
||||
<label for="product_qty"/>
|
||||
<div>
|
||||
<div>
|
||||
<field name="product_qty" class="oe_inline"/>
|
||||
<field name="product_uom" class="oe_inline" groups="product.group_uom"/>
|
||||
</div>
|
||||
</div>
|
||||
<label for="product_uos_qty" groups="product.group_uos"/>
|
||||
<div groups="product.group_uos">
|
||||
<field name="product_uos_qty" class="oe_inline"/>
|
||||
<field name="product_uos" class="oe_inline"/>
|
||||
</div>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<field name="origin" class="oe_inline" placeholder="e.g. SO005"/>
|
||||
<field name="message"/>
|
||||
<group name="scheduling" string="Scheduling">
|
||||
<field name="date_planned"/>
|
||||
<field name="priority"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Notes">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<field name="name" placeholder="External note..."/>
|
||||
</page>
|
||||
<page string="Extra Information">
|
||||
<group>
|
||||
<label for="product_uos_qty" groups="product.group_uos"/>
|
||||
<div groups="product.group_uos">
|
||||
<field name="product_uos_qty" class="oe_inline"/>
|
||||
<field name="product_uos" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="location_id" domain="[('usage','=','internal')]"/>
|
||||
<group>
|
||||
<field name="origin" placeholder="e.g. SO005"/>
|
||||
<field name="group_id" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="rule_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="move_id"/>
|
||||
<field name="date_close"/>
|
||||
<field name="close_move"/>
|
||||
</group>
|
||||
<field name="note" placeholder="Internal note..."/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
|
@ -102,6 +84,33 @@
|
|||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="do_view_procurements" model="ir.actions.act_window">
|
||||
<field name="name">Group's Procurements</field>
|
||||
<field name="res_model">procurement.order</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="domain">[('group_id','=',active_id)]</field>
|
||||
</record>
|
||||
|
||||
<record id="procurement_group_form_view" model="ir.ui.view">
|
||||
<field name="name">procurement.group.form</field>
|
||||
<field name="model">procurement.group</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Procurement group" version="7.0">
|
||||
<sheet>
|
||||
<div class="oe_right oe_button_box" name="button_box">
|
||||
<button name="%(do_view_procurements)d" string="Procurements" type="action"/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="move_type"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_procurement_filter" model="ir.ui.view">
|
||||
<field name="name">procurement.order.select</field>
|
||||
<field name="model">procurement.order</field>
|
||||
|
@ -111,22 +120,18 @@
|
|||
<field name="date_planned"/>
|
||||
<filter icon="terp-emblem-important" string="Exceptions" name="exceptions" domain="[('state','=','exception')]" help="Procurement Exceptions"/>
|
||||
<separator/>
|
||||
<filter icon="terp-emblem-important" string="To Fix" name="perm_exceptions" domain="[('state','=','exception'),('message', '!=', '')]" help="Permanent Procurement Exceptions"/>
|
||||
<filter icon="terp-emblem-important" string="Temporary" name="temp_exceptions" domain="[('state','=','exception'),('message', '=', '')]" help="Temporary Procurement Exceptions"/>
|
||||
<separator/>
|
||||
<filter icon="terp-gnome-cpu-frequency-applet+" string="Late" domain="['&', ('date_planned','<', current_date), ('state', 'in', ('draft', 'confirmed'))]" help="Procurement started late" />
|
||||
<filter icon="terp-gnome-cpu-frequency-applet+" string="Late" domain="['&', ('date_planned','<', current_date), ('state', '=', 'confirmed')]" help="Procurement started late" />
|
||||
<field name="product_id" />
|
||||
<field name="state" />
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Product" icon="terp-accessories-archiver" domain="[]" context="{'group_by':'product_id'}"/>
|
||||
<filter string="Reason" icon="terp-gtk-jump-to-rtl" domain="[]" context="{'group_by':'name'}"/>
|
||||
<filter string="Scheduled Month" icon="terp-go-month" domain="[]" context="{'group_by':'date_planned'}"/>
|
||||
<filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="procurement_action" model="ir.actions.act_window">
|
||||
<field name="name">Procurement Orders</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
|
@ -137,32 +142,44 @@
|
|||
<field name="context">{'search_default_current':1}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a procurement order.
|
||||
</p><p>
|
||||
A procurement order is used to record a need for a specific
|
||||
product at a specific location. Procurement orders are usually
|
||||
created automatically from sales orders, pull logistic rules or
|
||||
minimum stock rules.
|
||||
</p><p>
|
||||
Click to create a procurement order.
|
||||
</p>
|
||||
<p>
|
||||
A <b>procurement order</b> is used to record a need for a specific
|
||||
product at a specific location. Procurement orders are usually
|
||||
created automatically from <i>sales orders, pull logistic rules or
|
||||
minimum stock rules.</i>
|
||||
</p>
|
||||
<p>
|
||||
When the procurement order is confirmed, it automatically
|
||||
creates the necessary operations to fullfil the need: purchase
|
||||
order proposition, manufacturing order, etc.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="procurement_exceptions" model="ir.actions.act_window">
|
||||
<field name="name">Procurement Exceptions</field>
|
||||
<field name="name">Procurements</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">procurement.order</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="context">{'search_default_perm_exceptions':1}</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="search_view_id" ref="view_procurement_filter"/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
Procurement Orders represent the need for a certain quantity of products, at a given time, in a given location. Sales Orders are one typical source of Procurement Orders (but these are distinct documents). Depending on the procurement parameters and the product configuration, the procurement engine will attempt to satisfy the need by reserving products from stock, ordering products from a supplier, or passing a manufacturing order, etc. A Procurement Exception occurs when the system cannot find a way to fulfill a procurement. Some exceptions will resolve themselves automatically, but others require manual intervention (those are identified by a specific error message).
|
||||
</p>
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a Procurement.
|
||||
</p>
|
||||
<p>
|
||||
<b>Procurement Orders</b> represent the need for a certain quantity of products, at a given time, in a given location.
|
||||
</p>
|
||||
<p>
|
||||
<b>Sales Orders</b> are one typical source of Procurement Orders (but these are distinct documents).
|
||||
<br/>Depending on the procurement parameters and the product configuration, the procurement engine will attempt to satisfy the need by reserving products from stock, ordering products from a supplier, or passing a manufacturing order, etc...
|
||||
</p>
|
||||
<p>
|
||||
A <b>Procurement Exception</b> occurs when the system cannot find a way to fulfill a procurement. Some exceptions will resolve themselves automatically, but others require manual intervention (those are identified by a specific error message in the chatter).
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
@ -175,224 +192,45 @@
|
|||
<field name="domain">[('state','=','exception')]</field>
|
||||
</record>
|
||||
|
||||
<!-- Order Point -->
|
||||
<record id="view_warehouse_orderpoint_tree" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.orderpoint.tree</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<!-- Procurement Rules -->
|
||||
<record model="ir.ui.view" id="view_procurement_rule_tree">
|
||||
<field name="name">procurement.rule.tree</field>
|
||||
<field name="model">procurement.rule</field>
|
||||
<field eval="10" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Reordering Rules">
|
||||
<tree string="Pull Rules">
|
||||
<field name="name"/>
|
||||
<field name="warehouse_id" groups="stock.group_locations"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom" groups="product.group_uom"/>
|
||||
<field name="product_min_qty"/>
|
||||
<field name="product_max_qty"/>
|
||||
<field name="action"/>
|
||||
<field name='company_id' groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="warehouse_orderpoint_search">
|
||||
<field name="name">stock.warehouse.orderpoint.search</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<record model="ir.ui.view" id="view_procurement_rule_form">
|
||||
<field name="name">procurement.rule.form</field>
|
||||
<field name="model">procurement.rule</field>
|
||||
<field eval="10" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Reordering Rules Search">
|
||||
<field name="name" string="Reordering Rules"/>
|
||||
<field name="warehouse_id"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="product_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Warehouse" icon="terp-go-home" domain="[]" context="{'group_by':'warehouse_id'}"/>
|
||||
<filter string="Location" icon="terp-go-home" domain="[]" context="{'group_by':'location_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_warehouse_orderpoint_form" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.orderpoint.form</field>
|
||||
<field name="model">stock.warehouse.orderpoint</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Reordering Rules" version="7.0">
|
||||
<form string="Pull Rule" version="7.0">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1><field name="name"/></h1>
|
||||
</div>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" />
|
||||
<field name="product_id" on_change="onchange_product_id(product_id)"/>
|
||||
<group string="General Information">
|
||||
<field name="action"/>
|
||||
<field name="sequence"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="warehouse_id" on_change="onchange_warehouse_id(warehouse_id)" options="{'no_create': True}" groups="stock.group_locations"/>
|
||||
<field name="product_uom" groups="product.group_uom"/>
|
||||
<field name="location_id" groups="stock.group_locations"/>
|
||||
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
|
||||
<group name="propagation_group" string="Propagation Options" groups="base.group_no_one">
|
||||
<field name="group_propagation_option"/>
|
||||
<field name="group_id" attrs="{'invisible': [('group_propagation_option', '!=', 'fixed')], 'required': [('group_propagation_option', '=', 'fixed')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Rules">
|
||||
<field name="product_min_qty" />
|
||||
<field name="product_max_qty" />
|
||||
<field name="qty_multiple" string="Quantity Multiple"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<field name="procurement_id" readonly="1"/>
|
||||
<field name="active" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="Procurement Orders to Process">
|
||||
<field name="procurement_draft_ids" nolabel="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_orderpoint_form" model="ir.actions.act_window">
|
||||
<field name="name">Reordering Rules</field>
|
||||
<field name="res_model">stock.warehouse.orderpoint</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_warehouse_orderpoint_tree"/>
|
||||
<field name="search_view_id" ref="warehouse_orderpoint_search" />
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to add a reordering rule.
|
||||
</p><p>You can define your minimum stock rules, so that OpenERP will automatically create draft manufacturing orders or request for quotations according to the stock level. Once the virtual stock of a product (= stock on hand minus all confirmed orders and reservations) is below the minimum quantity, OpenERP will generate a procurement request to increase the stock up to the maximum quantity.</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window
|
||||
context="{'search_default_warehouse_id': active_id, 'default_warehouse_id': active_id}"
|
||||
id="act_stock_warehouse_2_stock_warehouse_orderpoint"
|
||||
name="Reordering Rules"
|
||||
res_model="stock.warehouse.orderpoint"
|
||||
src_model="stock.warehouse"
|
||||
groups="stock.group_stock_user"/>
|
||||
|
||||
<act_window
|
||||
context="{'product_uom': locals().has_key('uom_id') and uom_id, 'default_procurement_id': active_id}"
|
||||
id="act_procurement_2_stock_warehouse_orderpoint"
|
||||
name="Reordering Rules"
|
||||
res_model="stock.warehouse.orderpoint"
|
||||
src_model="procurement.order"
|
||||
groups="stock.group_stock_user"/>
|
||||
|
||||
<!-- Procurements are located in Warehouse menu hierarchy, MRP users should come to Stock application to use it. -->
|
||||
<menuitem id="menu_stock_sched" name="Schedulers" parent="stock.menu_stock_root" sequence="4" groups="stock.group_stock_manager"/>
|
||||
<menuitem action="action_compute_schedulers" id="menu_stock_proc_schedulers" parent="menu_stock_sched" sequence="20" groups="stock.group_stock_manager"/>
|
||||
<menuitem action="procurement_exceptions" id="menu_stock_procurement_action" parent="menu_stock_sched" sequence="50" groups="stock.group_stock_manager"/>
|
||||
<menuitem id="menu_stock_procurement" name="Automatic Procurements" parent="stock.menu_stock_configuration" sequence="5"/>
|
||||
<menuitem action="action_orderpoint_form" id="menu_stock_order_points" parent="stock.menu_stock_configuration" sequence="10"/>
|
||||
|
||||
|
||||
<record model="ir.actions.act_window" id="product_open_orderpoint">
|
||||
<field name="context">{'default_product_id': active_id, 'search_default_product_id': active_id}</field>
|
||||
<field name="name">Orderpoints</field>
|
||||
<field name="res_model">stock.warehouse.orderpoint</field>
|
||||
</record>
|
||||
|
||||
<record id="product_template_search_view_procurment" model="ir.ui.view">
|
||||
<field name="name">product.template.search.procurement</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_search_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="consumable" position="before">
|
||||
<filter string="Products" icon="terp-accessories-archiver" domain="[('type','=','product')]" help="Stockable products"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_template_form_view_procurement">
|
||||
<field name="name">product.template.procurement</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='cost_method']" position="before">
|
||||
<field name="procure_method"/>
|
||||
<field name="supply_method"/>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='general']" position="after" >
|
||||
<group name="procurement_help" class="oe_grey" col="1" groups="base.group_user">
|
||||
<p attrs="{'invisible': ['|', '|', ('type', '!=', 'service'), ('procure_method', '!=', 'make_to_stock')]}">
|
||||
When you sell this service, nothing special will be triggered
|
||||
to deliver the customer, as you set the procurement method as
|
||||
'Make to Stock'.
|
||||
</p>
|
||||
<p attrs="{'invisible': ['|', '|', ('type', '!=', 'product'), ('procure_method', '!=', 'make_to_stock')]}">
|
||||
When you sell this product, OpenERP will <b>use the available
|
||||
inventory</b> for the delivery order.
|
||||
<br/><br/>
|
||||
If there are not enough quantities available, the delivery order
|
||||
will wait for new products. To fulfill the inventory, you should
|
||||
create others rules like orderpoints.
|
||||
</p>
|
||||
<p attrs="{'invisible': ['|', '|', ('type', '!=', 'consu'), ('procure_method', '!=', 'make_to_stock')]}">
|
||||
When you sell this product, a delivery order will be created.
|
||||
OpenERP will consider that the <b>required quantities are always
|
||||
available</b> as it's a consumable (as a result of this, the quantity
|
||||
on hand may become negative).
|
||||
</p>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="product_search_form_view_procurment" model="ir.ui.view">
|
||||
<field name="name">product.search.procurment.form</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_search_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<filter name="consumable" position="before">
|
||||
<filter string="Products" icon="terp-accessories-archiver" domain="[('type','=','product')]" help="Stockable products"/>
|
||||
</filter>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_form_view_procurement_button">
|
||||
<field name="name">product.product.procurement</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="stock.view_normal_procurement_locations_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='incoming_qty']" position="after">
|
||||
<button string="⇒ Request Procurement" name="%(act_make_procurement)d" type="action" class="oe_link"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='buttons']" position="inside">
|
||||
<button class="oe_inline oe_stat_button" name="%(product_open_orderpoint)d" type="action"
|
||||
attrs="{'invisible':[('type', '=', 'service')]}" icon="fa-pinterest">
|
||||
<field string="Order Points" name="orderpoint_count" widget="statinfo" />
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='cost_method']" position="before">
|
||||
<field name="procure_method" groups="base.group_user"
|
||||
attrs="{'readonly': [('is_only_child', '=', False)]}"/>
|
||||
<field name="supply_method" groups="base.group_user"
|
||||
attrs="{'readonly': [('is_only_child', '=', False)]}"/>
|
||||
</xpath>
|
||||
<xpath expr="//group[@name='general']" position="after" >
|
||||
<group name="procurement_help" class="oe_grey" col="1" groups="base.group_user">
|
||||
<p attrs="{'invisible': ['|','|',('type','<>','service'),('procure_method','<>','make_to_stock')]}">
|
||||
When you sell this service, nothing special will be triggered
|
||||
to deliver the customer, as you set the procurement method as
|
||||
'Make to Stock'.
|
||||
</p>
|
||||
<p attrs="{'invisible': ['|','|',('type','<>','product'),('procure_method','<>','make_to_stock')]}">
|
||||
When you sell this product, OpenERP will <b>use the available
|
||||
inventory</b> for the delivery order.
|
||||
<br/><br/>
|
||||
If there are not enough quantities available, the delivery order
|
||||
will wait for new products. To fulfill the inventory, you should
|
||||
create others rules like orderpoints.
|
||||
</p>
|
||||
<p attrs="{'invisible': ['|','|',('type','<>','consu'),('procure_method','<>','make_to_stock')]}">
|
||||
When you sell this product, a delivery order will be created.
|
||||
OpenERP will consider that the <b>required quantities are always
|
||||
available</b> as it's a consumable (as a result of this, the quantity
|
||||
on hand may become negative).
|
||||
</p>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="wkf_procurement" model="workflow">
|
||||
<field name="name">procurement.order.basic</field>
|
||||
<field name="osv">procurement.order</field>
|
||||
<field name="on_create">True</field>
|
||||
</record>
|
||||
|
||||
<record id="act_draft" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="flow_start">True</field>
|
||||
<field name="name">draft</field>
|
||||
</record>
|
||||
<record id="act_cancel" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">cancel</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_cancel()</field>
|
||||
<field name="flow_stop">True</field>
|
||||
</record>
|
||||
<record id="act_confirm" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">confirm</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_confirm()</field>
|
||||
</record>
|
||||
<record id="act_confirm_wait" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">confirm_wait</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'state':'exception'})</field>
|
||||
</record>
|
||||
<record id="act_confirm_mts" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">confirm_mts</field>
|
||||
</record>
|
||||
<record id="act_confirm_mto" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">confirm_mto</field>
|
||||
</record>
|
||||
<record id="act_make_to_stock" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">make_to_stock</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_move_assigned()</field>
|
||||
</record>
|
||||
|
||||
<record id="act_make_done" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">ready</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_ready()</field>
|
||||
</record>
|
||||
<record id="act_wait_done" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="name">wait_done</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">write({'state':'waiting'})</field>
|
||||
</record>
|
||||
|
||||
<record id="act_done" model="workflow.activity">
|
||||
<field name="wkf_id" ref="wkf_procurement"/>
|
||||
<field name="flow_stop">True</field>
|
||||
<field name="name">done</field>
|
||||
<field name="kind">function</field>
|
||||
<field name="action">action_done()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_draft_confirm" model="workflow.transition">
|
||||
<field name="act_from" ref="act_draft"/>
|
||||
<field name="act_to" ref="act_confirm"/>
|
||||
<field name="signal">button_confirm</field>
|
||||
</record>
|
||||
<record id="trans_confirm_cancel2" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm"/>
|
||||
<field name="act_to" ref="act_wait_done"/>
|
||||
<field name="signal">button_wait_done</field>
|
||||
<field name="condition">True</field>
|
||||
</record>
|
||||
<record id="trans_confirm_wait_done" model="workflow.transition">
|
||||
<field name="act_from" ref="act_wait_done"/>
|
||||
<field name="act_to" ref="act_done"/>
|
||||
<field name="condition">check_move_done()</field>
|
||||
<field name="trigger_model">stock.move</field>
|
||||
<field name="trigger_expr_id">[move_id.id]</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_confirm_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm"/>
|
||||
<field name="act_to" ref="act_cancel"/>
|
||||
<field name="signal">button_check</field>
|
||||
<field name="condition">test_cancel()</field>
|
||||
</record>
|
||||
<record id="trans_confirm_confirm_wait" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm"/>
|
||||
<field name="act_to" ref="act_confirm_wait"/>
|
||||
<field name="signal">button_check</field>
|
||||
<field name="condition">check_conditions_confirm2wait()</field>
|
||||
</record>
|
||||
<record id="trans_confirm_wait_confirm_mto" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_wait"/>
|
||||
<field name="act_to" ref="act_confirm_mto"/>
|
||||
<field name="condition">procure_method=='make_to_order'</field>
|
||||
</record>
|
||||
<record id="trans_confirm_wait_confirm_mts" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_wait"/>
|
||||
<field name="act_to" ref="act_confirm_mts"/>
|
||||
<field name="condition">procure_method=='make_to_stock'</field>
|
||||
</record>
|
||||
<record id="trans_confirm_mts_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_mts"/>
|
||||
<field name="act_to" ref="act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
<record id="trans_confirm_waiting_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="act_wait_done"/>
|
||||
<field name="act_to" ref="act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
<record id="trans_confirm_mts_confirm" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_mts"/>
|
||||
<field name="act_to" ref="act_confirm"/>
|
||||
<field name="signal">button_restart</field>
|
||||
</record>
|
||||
<record id="trans_confirm_mto_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_mto"/>
|
||||
<field name="act_to" ref="act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
<record id="trans_confirm_mto_confirm" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_mto"/>
|
||||
<field name="act_to" ref="act_confirm"/>
|
||||
<field name="signal">button_restart</field>
|
||||
</record>
|
||||
<record id="trans_draft_cancel" model="workflow.transition">
|
||||
<field name="act_from" ref="act_draft"/>
|
||||
<field name="act_to" ref="act_cancel"/>
|
||||
<field name="signal">button_cancel</field>
|
||||
</record>
|
||||
<record id="trans_confirm_mts_make_to_stock" model="workflow.transition">
|
||||
<field name="act_from" ref="act_confirm_mts"/>
|
||||
<field name="act_to" ref="act_make_to_stock"/>
|
||||
<field name="condition">check_make_to_stock()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_confirm_mto_make_done" model="workflow.transition">
|
||||
<!-- This transition is there to unblock products that would be in MTO with a supply method that would be
|
||||
produce or buy, and without MRP or Purchase modules installed. These modules overwrite the check_produce()
|
||||
and check_buy() methods -so that it invalidates their part of this 'bypass transition'-, and define
|
||||
their own workflow paths.
|
||||
The stock_location module also introduces a check_move() alternative, for pulled flows that are
|
||||
satisfied with an internal product move. This yields a threefold test for the bypass transition.
|
||||
-->
|
||||
<field name="act_from" ref="act_confirm_mto"/>
|
||||
<field name="act_to" ref="act_make_done"/>
|
||||
<field name="condition">not check_produce() and not check_buy() and not check_move()</field>
|
||||
</record>
|
||||
|
||||
<record id="trans_make_to_stock_make_done" model="workflow.transition">
|
||||
<field name="act_from" ref="act_make_to_stock"/> <!-- TOFIX: If product is service product and procure method is 'make_to_stock', procurement is closed without generated service -->
|
||||
<field name="act_to" ref="act_make_done"/>
|
||||
<field name="condition">True</field>
|
||||
<field name="trigger_model" eval="False"/>
|
||||
<field name="trigger_expr_id" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="trans_make_done_done" model="workflow.transition">
|
||||
<field name="act_from" ref="act_make_done"/>
|
||||
<field name="act_to" ref="act_done"/>
|
||||
<field name="condition">action_check_finished()</field>
|
||||
<field name="trigger_model">stock.move</field>
|
||||
<field name="trigger_expr_id">move_id and [move_id.id] or []</field>
|
||||
</record>
|
||||
<record id="trans_make_done_confirm" model="workflow.transition">
|
||||
<field name="act_from" ref="act_make_done"/>
|
||||
<field name="act_to" ref="act_cancel"/>
|
||||
<field name="condition">check_move_cancel()</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -1,257 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
import openerp
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp import tools
|
||||
|
||||
class procurement_order(osv.osv):
|
||||
_inherit = 'procurement.order'
|
||||
|
||||
def run_scheduler(self, cr, uid, automatic=False, use_new_cursor=False, skip_exception=False, context=None):
|
||||
''' Runs through scheduler.
|
||||
@param use_new_cursor: False or the dbname
|
||||
'''
|
||||
if use_new_cursor:
|
||||
use_new_cursor = cr.dbname
|
||||
self._procure_confirm(cr, uid, use_new_cursor=use_new_cursor, skip_exception=skip_exception, context=context)
|
||||
self._procure_orderpoint_confirm(cr, uid, automatic=automatic,\
|
||||
use_new_cursor=use_new_cursor, context=context)
|
||||
|
||||
def _procure_confirm(self, cr, uid, ids=None, use_new_cursor=False, skip_exception=False, context=None):
|
||||
'''
|
||||
Call the scheduler to check the procurement order
|
||||
|
||||
@param self: The object pointer
|
||||
@param cr: The current row, from the database cursor,
|
||||
@param uid: The current user ID for security checks
|
||||
@param ids: List of selected IDs
|
||||
@param use_new_cursor: False or the dbname
|
||||
@param skip_exception: boolean
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of values
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
try:
|
||||
if use_new_cursor:
|
||||
cr = openerp.registry(use_new_cursor).cursor()
|
||||
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
if not skip_exception:
|
||||
if not ids:
|
||||
ids = procurement_obj.search(cr, uid, [('state', '=', 'exception')], order="date_planned")
|
||||
self.signal_button_restart(cr, uid, ids)
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
|
||||
maxdate = (datetime.today() + relativedelta(days=company.schedule_range)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
offset = 0
|
||||
while True:
|
||||
ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_order')], offset=offset, limit=500, order='priority, date_planned', context=context)
|
||||
for proc in procurement_obj.browse(cr, uid, ids, context=context):
|
||||
if maxdate >= proc.date_planned:
|
||||
self.signal_button_check(cr, uid, [proc.id])
|
||||
else:
|
||||
offset += 1
|
||||
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
if not ids:
|
||||
break
|
||||
offset = 0
|
||||
ids = []
|
||||
while True:
|
||||
report_ids = []
|
||||
ids = procurement_obj.search(cr, uid, [('state', '=', 'confirmed'), ('procure_method', '=', 'make_to_stock')], offset=offset)
|
||||
for proc in procurement_obj.browse(cr, uid, ids):
|
||||
if maxdate >= proc.date_planned:
|
||||
self.signal_button_check(cr, uid, [proc.id])
|
||||
report_ids.append(proc.id)
|
||||
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
offset += len(ids)
|
||||
if not ids: break
|
||||
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
finally:
|
||||
if use_new_cursor:
|
||||
try:
|
||||
cr.close()
|
||||
except Exception:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def _prepare_automatic_op_procurement(self, cr, uid, product, warehouse, location_id, context=None):
|
||||
return {'name': _('Automatic OP: %s') % (product.name,),
|
||||
'origin': _('SCHEDULER'),
|
||||
'date_planned': datetime.today().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'product_id': product.id,
|
||||
'product_qty': -product.virtual_available,
|
||||
'product_uom': product.uom_id.id,
|
||||
'location_id': location_id,
|
||||
'company_id': warehouse.company_id.id,
|
||||
'procure_method': 'make_to_order',}
|
||||
|
||||
def create_automatic_op(self, cr, uid, context=None):
|
||||
"""
|
||||
Create procurement of virtual stock < 0
|
||||
|
||||
@param self: The object pointer
|
||||
@param cr: The current row, from the database cursor,
|
||||
@param uid: The current user ID for security checks
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: Dictionary of values
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
product_obj = self.pool.get('product.product')
|
||||
proc_obj = self.pool.get('procurement.order')
|
||||
warehouse_obj = self.pool.get('stock.warehouse')
|
||||
|
||||
warehouse_ids = warehouse_obj.search(cr, uid, [], context=context)
|
||||
products_ids = product_obj.search(cr, uid, [], order='id', context=context)
|
||||
|
||||
for warehouse in warehouse_obj.browse(cr, uid, warehouse_ids, context=context):
|
||||
context['warehouse'] = warehouse
|
||||
# Here we check products availability.
|
||||
# We use the method 'read' for performance reasons, because using the method 'browse' may crash the server.
|
||||
for product_read in product_obj.read(cr, uid, products_ids, ['virtual_available'], context=context):
|
||||
if product_read['virtual_available'] >= 0.0:
|
||||
continue
|
||||
|
||||
product = product_obj.browse(cr, uid, [product_read['id']], context=context)[0]
|
||||
if product.supply_method == 'buy':
|
||||
location_id = warehouse.lot_input_id.id
|
||||
elif product.supply_method == 'produce':
|
||||
location_id = warehouse.lot_stock_id.id
|
||||
else:
|
||||
continue
|
||||
proc_id = proc_obj.create(cr, uid,
|
||||
self._prepare_automatic_op_procurement(cr, uid, product, warehouse, location_id, context=context),
|
||||
context=context)
|
||||
self.signal_button_confirm(cr, uid, [proc_id])
|
||||
self.signal_button_check(cr, uid, [proc_id])
|
||||
return True
|
||||
|
||||
def _get_orderpoint_date_planned(self, cr, uid, orderpoint, start_date, context=None):
|
||||
date_planned = start_date + \
|
||||
relativedelta(days=orderpoint.product_id.seller_delay or 0.0)
|
||||
return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
def _prepare_orderpoint_procurement(self, cr, uid, orderpoint, product_qty, context=None):
|
||||
return {'name': orderpoint.name,
|
||||
'date_planned': self._get_orderpoint_date_planned(cr, uid, orderpoint, datetime.today(), context=context),
|
||||
'product_id': orderpoint.product_id.id,
|
||||
'product_qty': product_qty,
|
||||
'company_id': orderpoint.company_id.id,
|
||||
'product_uom': orderpoint.product_uom.id,
|
||||
'location_id': orderpoint.location_id.id,
|
||||
'procure_method': 'make_to_order',
|
||||
'origin': orderpoint.name}
|
||||
|
||||
def _product_virtual_get(self, cr, uid, order_point):
|
||||
location_obj = self.pool.get('stock.location')
|
||||
return location_obj._product_virtual_get(cr, uid,
|
||||
order_point.location_id.id, [order_point.product_id.id],
|
||||
{'uom': order_point.product_uom.id})[order_point.product_id.id]
|
||||
|
||||
def _procure_orderpoint_confirm(self, cr, uid, automatic=False,\
|
||||
use_new_cursor=False, context=None, user_id=False):
|
||||
'''
|
||||
Create procurement based on Orderpoint
|
||||
use_new_cursor: False or the dbname
|
||||
|
||||
@param self: The object pointer
|
||||
@param cr: The current row, from the database cursor,
|
||||
@param user_id: The current user ID for security checks
|
||||
@param context: A standard dictionary for contextual values
|
||||
@param param: False or the dbname
|
||||
@return: Dictionary of values
|
||||
"""
|
||||
'''
|
||||
if context is None:
|
||||
context = {}
|
||||
if use_new_cursor:
|
||||
cr = openerp.registry(use_new_cursor).cursor()
|
||||
orderpoint_obj = self.pool.get('stock.warehouse.orderpoint')
|
||||
|
||||
procurement_obj = self.pool.get('procurement.order')
|
||||
offset = 0
|
||||
ids = [1]
|
||||
if automatic:
|
||||
self.create_automatic_op(cr, uid, context=context)
|
||||
while ids:
|
||||
ids = orderpoint_obj.search(cr, uid, [], offset=offset, limit=100)
|
||||
for op in orderpoint_obj.browse(cr, uid, ids, context=context):
|
||||
prods = self._product_virtual_get(cr, uid, op)
|
||||
if prods is None:
|
||||
continue
|
||||
if prods < op.product_min_qty:
|
||||
qty = max(op.product_min_qty, op.product_max_qty)-prods
|
||||
|
||||
reste = qty % op.qty_multiple
|
||||
if reste > 0:
|
||||
qty += op.qty_multiple - reste
|
||||
|
||||
if qty <= 0:
|
||||
continue
|
||||
if op.product_id.type not in ('consu'):
|
||||
if op.procurement_draft_ids:
|
||||
# Check draft procurement related to this order point
|
||||
pro_ids = [x.id for x in op.procurement_draft_ids]
|
||||
procure_datas = procurement_obj.read(
|
||||
cr, uid, pro_ids, ['id', 'product_qty'], context=context)
|
||||
to_generate = qty
|
||||
for proc_data in procure_datas:
|
||||
if to_generate >= proc_data['product_qty']:
|
||||
self.signal_button_confirm(cr, uid, [proc_data['id']])
|
||||
procurement_obj.write(cr, uid, [proc_data['id']], {'origin': op.name}, context=context)
|
||||
to_generate -= proc_data['product_qty']
|
||||
if not to_generate:
|
||||
break
|
||||
qty = to_generate
|
||||
|
||||
if qty:
|
||||
proc_id = procurement_obj.create(cr, uid,
|
||||
self._prepare_orderpoint_procurement(cr, uid, op, qty, context=context),
|
||||
context=context)
|
||||
self.signal_button_confirm(cr, uid, [proc_id])
|
||||
self.signal_button_check(cr, uid, [proc_id])
|
||||
orderpoint_obj.write(cr, uid, [op.id],
|
||||
{'procurement_id': proc_id}, context=context)
|
||||
offset += len(ids)
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
if use_new_cursor:
|
||||
cr.commit()
|
||||
cr.close()
|
||||
return {}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -1,9 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_procurement,procurement.order,model_procurement_order,base.group_user,1,0,0,0
|
||||
access_procurement_stock_user,procurement.order stock.user,model_procurement_order,stock.group_stock_user,1,1,1,1
|
||||
access_stock_warehouse_orderpoint,stock.warehouse.orderpoint,model_stock_warehouse_orderpoint,stock.group_stock_user,1,0,0,0
|
||||
access_stock_warehouse_orderpoint_system,stock.warehouse.orderpoint system,model_stock_warehouse_orderpoint,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_property_group,mrp.property.group,model_mrp_property_group,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_property,mrp.property,model_mrp_property,stock.group_stock_manager,1,1,1,1
|
||||
access_mrp_property_group,mrp.property.group,model_mrp_property_group,base.group_user,1,0,0,0
|
||||
access_mrp_property,mrp.property,model_mrp_property,base.group_user,1,0,0,0
|
||||
access_procurement,procurement.order,model_procurement_order,base.group_user,1,1,1,0
|
||||
access_procurement_group,procurement.group,model_procurement_group,base.group_user,1,1,1,0
|
||||
access_procurement_rule,procurement.rule,model_procurement_rule,base.group_user,1,1,1,0
|
||||
|
|
|
|
@ -9,12 +9,12 @@
|
|||
<field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.rule" id="stock_warehouse_orderpoint_rule">
|
||||
<field name="name">stock_warehouse.orderpoint multi-company</field>
|
||||
<field name="model_id" search="[('model','=','stock.warehouse.orderpoint')]" model="ir.model"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','child_of',[user.company_id.id]),('company_id','=',False)]</field>
|
||||
</record>
|
||||
<record model="ir.rule" id="product_pulled_flow_comp_rule">
|
||||
<field name="name">product_pulled_flow multi-company</field>
|
||||
<field name="model_id" ref="model_procurement_rule"/>
|
||||
<field name="global" eval="True"/>
|
||||
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,89 +1,22 @@
|
|||
-
|
||||
For test the procurement process, First I have to apply a minimum stock rule on product
|
||||
I create a procurement
|
||||
-
|
||||
I create minimum stock rule for product.
|
||||
-
|
||||
!record {model: stock.warehouse.orderpoint, id: stock_warehouse_orderpoint_op0}:
|
||||
!record {model: procurement.order, id: procurement_order0}:
|
||||
company_id: base.main_company
|
||||
location_id: stock.stock_location_stock
|
||||
logic: max
|
||||
name: OP/00008
|
||||
name: Procurement Test
|
||||
product_id: product.product_product_32
|
||||
product_max_qty: 15.0
|
||||
product_min_qty: 5.0
|
||||
product_qty: 15.0
|
||||
product_uom: product.product_uom_unit
|
||||
qty_multiple: 1
|
||||
warehouse_id: stock.warehouse0
|
||||
-
|
||||
Check product quantity and update it, if needed for apply a minimum stock rule.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
product = self.browse(cr, uid, ref('product.product_product_32'))
|
||||
if product.virtual_available < 5.0:
|
||||
change_qty = self.pool.get('stock.change.product.qty')
|
||||
id = change_qty.create(cr, uid, {'location_id' : ref('stock.stock_location_stock'), 'new_quantity': 4, 'product_id': product.id})
|
||||
change_qty.change_product_qty(cr, uid, [id], {'active_model':'product.product', 'active_id': product.id, 'active_ids':[product.id]})
|
||||
assert product.qty_available == 4,"Product quantity is not updated."
|
||||
assert product.virtual_available < 5.0,'Virtual stock have more quantities.'
|
||||
product_uos_qty: 15.0
|
||||
product_uos: product.product_uom_unit
|
||||
-
|
||||
I run the scheduler.
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
self.run_scheduler(cr, uid)
|
||||
-
|
||||
I check that procurement order is based on minimum stock rule.
|
||||
I check that procurement order is in exception, as at first there isn't any suitable rule
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
proc_ids = self.search(cr, uid, [('product_id','=', ref('product.product_product_32'))])
|
||||
assert proc_ids, 'No Procurement created.'
|
||||
proc_order = self.browse(cr, uid, proc_ids)[0]
|
||||
assert proc_order.product_qty == 11.0,"Procurement product quantity is not corresponded."
|
||||
-
|
||||
I check product quantity.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
product = self.browse(cr, uid, ref('product.product_product_32'))
|
||||
assert product.virtual_available == 15.0,"After run the scheduler product's virtual stock is not updated."
|
||||
-
|
||||
For test the Procurement Request wizard, Again I have to update product quantity.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
mk_procure = self.pool.get('make.procurement')
|
||||
procur_order = self.pool.get('procurement.order')
|
||||
product = self.browse(cr, uid, ref('product.product_product_32'))
|
||||
|
||||
context.update({'active_model': 'product.product','active_id':ref('product.product_product_32')})
|
||||
values = {'warehouse_id': ref('base.main_company'), 'uom_id': ref('product.product_uom_unit'), 'qty': 5}
|
||||
id = mk_procure.create(cr, uid, values, context)
|
||||
procurement = mk_procure.make_procurement(cr, uid, [id], context)
|
||||
assert product.virtual_available == 20.0, 'Virtual stock should be updated'
|
||||
|
||||
proc_id = procurement.get('res_id')
|
||||
for procurement in procur_order.browse(cr, uid, [proc_id]):
|
||||
if procurement.state == 'confirmed':
|
||||
assert procurement.state == 'confirmed',"Procurement state should be 'Confirmed'."
|
||||
assert procurement.product_id.id == ref('product.product_product_32'),"Product is not correspond."
|
||||
assert procurement.product_qty == 5,"Product Quantity is not correspond."
|
||||
assert procurement.product_uom.id == ref('product.product_uom_unit'),"Product's UOM is not correspond."
|
||||
context.update({'proc': proc_id})
|
||||
-
|
||||
I run the scheduler.
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
self.run_scheduler(cr, uid)
|
||||
-
|
||||
I check the current state of procurement.
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
proc_id = context.get('proc')
|
||||
proc = self.browse(cr, uid, [proc_id])[0]
|
||||
assert proc.state == 'ready' or 'exception',"Procurement should be in Ready or Exception state"
|
||||
#-
|
||||
# I compute minimum stock.
|
||||
# [Note. Commented out because it spawns a thread that may query the db after tests have been reverted.]
|
||||
#-
|
||||
# !python {model: procurement.orderpoint.compute}: |
|
||||
# proc_id = context.get('proc')
|
||||
# context.update({'active_model': 'procurement.order', 'active_id': proc_id})
|
||||
# id = self.create(cr, uid, {'automatic': True}, context)
|
||||
# self.procure_calculation(cr, uid, [id], context)
|
||||
proc_order = self.browse(cr, uid, ref('procurement_order0'), context=context)
|
||||
assert proc_order.state == 'exception'
|
||||
|
|
|
@ -19,9 +19,6 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import orderpoint_procurement
|
||||
import mrp_procurement
|
||||
import schedulers_all
|
||||
import make_procurement_product
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import threading
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
class procurement_compute(osv.osv_memory):
|
||||
_name = 'procurement.order.compute'
|
||||
_description = 'Compute Procurement'
|
||||
|
||||
def _procure_calculation_procure(self, cr, uid, ids, context=None):
|
||||
try:
|
||||
proc_obj = self.pool.get('procurement.order')
|
||||
proc_obj._procure_confirm(cr, uid, use_new_cursor=cr.dbname, context=context)
|
||||
finally:
|
||||
pass
|
||||
return {}
|
||||
|
||||
def procure_calculation(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
@param self: The object pointer.
|
||||
@param cr: A database cursor
|
||||
@param uid: ID of the user currently logged in
|
||||
@param ids: List of IDs selected
|
||||
@param context: A standard dictionary
|
||||
"""
|
||||
threaded_calculation = threading.Thread(target=self._procure_calculation_procure, args=(cr, uid, ids, context))
|
||||
threaded_calculation.start()
|
||||
return {}
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Compute Procurement -->
|
||||
|
||||
<record id="view_compute_procurment_wizard" model="ir.ui.view">
|
||||
<field name="name">Compute Procurements</field>
|
||||
<field name="model">procurement.order.compute</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Parameters" version="7.0">
|
||||
<group>
|
||||
<label string="This wizard will schedule procurements."/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="procure_calculation" string="Compute Procurements"
|
||||
type="object" class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -21,20 +21,12 @@
|
|||
|
||||
import threading
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.osv import osv
|
||||
|
||||
class procurement_compute_all(osv.osv_memory):
|
||||
_name = 'procurement.order.compute.all'
|
||||
_description = 'Compute all schedulers'
|
||||
|
||||
_columns = {
|
||||
'automatic': fields.boolean('Automatic orderpoint',help='Triggers an automatic procurement for all products that have a virtual stock under 0. You should probably not use this option, we suggest using a MTO configuration on products.'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'automatic': lambda *a: False,
|
||||
}
|
||||
|
||||
def _procure_calculation_all(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
@param self: The object pointer.
|
||||
|
@ -46,9 +38,7 @@ class procurement_compute_all(osv.osv_memory):
|
|||
proc_obj = self.pool.get('procurement.order')
|
||||
#As this function is in a new thread, i need to open a new cursor, because the old one may be closed
|
||||
new_cr = self.pool.cursor()
|
||||
for proc in self.browse(new_cr, uid, ids, context=context):
|
||||
proc_obj.run_scheduler(new_cr, uid, automatic=proc.automatic, use_new_cursor=new_cr.dbname,\
|
||||
context=context)
|
||||
proc_obj.run_scheduler(new_cr, uid, use_new_cursor=new_cr.dbname, context=context)
|
||||
#close the new cursor
|
||||
new_cr.close()
|
||||
return {}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<field name="model">procurement.order.compute.all</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Scheduler Parameters" version="7.0">
|
||||
<group>
|
||||
<field name="automatic"/>
|
||||
</group>
|
||||
<p>
|
||||
Compute all procurements in the background.
|
||||
</p>
|
||||
<footer>
|
||||
<button name="procure_calculation" string="Run Schedulers" type="object" class="oe_highlight" />
|
||||
or
|
||||
|
|
|
@ -19,5 +19,6 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
import procurement_jit
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -23,7 +23,7 @@
|
|||
{
|
||||
'name': 'Just In Time Scheduling',
|
||||
'version': '1.0',
|
||||
'category': 'Manufacturing',
|
||||
'category': 'Base',
|
||||
'description': """
|
||||
This module allows Just In Time computation of procurement orders.
|
||||
==================================================================
|
||||
|
@ -40,9 +40,9 @@ In that case, you can not use priorities any more on the different picking.
|
|||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['procurement'],
|
||||
'data': ['mrp_jit.xml'],
|
||||
'data': [],
|
||||
'demo': [],
|
||||
'test': ['test/mrp_jit.yml'],
|
||||
'test': ['test/procurement_jit.yml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue