[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:
Quentin (OpenERP) 2014-05-08 16:50:15 +02:00
commit 5b6b96e334
521 changed files with 19377 additions and 40060 deletions

View File

@ -57,7 +57,7 @@
<field eval="0" name="days2"/>
<field eval="account_payment_term_net" name="payment_id"/>
</record>
<!--
Account Journal Sequences
-->

View File

@ -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

View File

@ -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.
-

View File

@ -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.
-

View File

@ -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>

View File

@ -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>

View File

@ -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'),
}

View File

@ -21,7 +21,6 @@
import delivery
import partner
import report
import sale
import stock

View File

@ -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'],

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
})

View File

@ -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:

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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']):

View File

@ -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>

View File

@ -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>

View File

@ -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',
],

36
addons/mrp/doc/mrp.rst Normal file
View File

@ -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.

View File

@ -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),

View File

@ -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>

View File

@ -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','&lt;&gt;','make_to_order'),('supply_method','&lt;&gt;','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>

View File

@ -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>

View File

@ -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:

View File

@ -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:

View File

@ -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
)""")

View File

@ -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>

View File

@ -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 '

View File

@ -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"/>

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_analytic_line_user account.analytic.line account.model_account_analytic_line group_mrp_user 1 1 1 0
3 access_mrp_workcenter mrp.workcenter model_mrp_workcenter mrp.group_mrp_user 1 0 0 0
4 access_mrp_routing mrp.routing model_mrp_routing mrp.group_mrp_user 1 0 0 0
5 access_mrp_routing_workcenter mrp.routing.workcenter model_mrp_routing_workcenter mrp.group_mrp_user 1 0 0 0
6 access_mrp_bom mrp.bom model_mrp_bom group_mrp_user 1 0 0 0
7 access_mrp_production mrp.production user model_mrp_production mrp.group_mrp_user 1 1 1 1
8 access_mrp_production_salesman mrp.production salesman model_mrp_production base.group_sale_salesman 1 1 1 0
9 access_mrp_production_product_line_salesman mrp.production.product.line salesman model_mrp_production_product_line base.group_sale_salesman 1 0 1 0
10 access_mrp_production_workcenter_line_salesman mrp.production.workcenter.line salesman model_mrp_production_workcenter_line base.group_sale_salesman 1 0 1 0
11 access_mrp_property_group access_mrp_production_product_line mrp.property.group mrp.production.product.line procurement.model_mrp_property_group model_mrp_production_product_line mrp.group_mrp_manager mrp.group_mrp_user 1 1 1 1
12 access_mrp_property access_procurement mrp.property procurement.order procurement.model_mrp_property model_procurement_order mrp.group_mrp_manager mrp.group_mrp_user 1 1 1 1
13 access_mrp_production_product_line access_mrp_workcenter_manager mrp.production.product.line mrp.workcenter.manager model_mrp_production_product_line model_mrp_workcenter mrp.group_mrp_user mrp.group_mrp_manager 1 1 1 1
14 access_procurement access_mrp_routing_manager procurement.order mrp.routing.manager model_procurement_order model_mrp_routing mrp.group_mrp_user mrp.group_mrp_manager 1 1 1 1
15 access_mrp_workcenter_manager access_mrp_routing_workcenter_manager mrp.workcenter.manager mrp.routing.workcenter.manager model_mrp_workcenter model_mrp_routing_workcenter mrp.group_mrp_manager 1 1 1 1
16 access_mrp_routing_manager access_mrp_bom_manager mrp.routing.manager mrp.bom.manager model_mrp_routing model_mrp_bom mrp.group_mrp_manager 1 1 1 1
17 access_mrp_routing_workcenter_manager access_stock_location_mrp_worker mrp.routing.workcenter.manager stock.location mrp_worker model_mrp_routing_workcenter stock.model_stock_location mrp.group_mrp_manager mrp.group_mrp_user 1 1 0 1 0 1 0
18 access_mrp_bom_manager access_stock_move_mrp_worker mrp.bom.manager stock.move mrp_worker model_mrp_bom stock.model_stock_move mrp.group_mrp_manager mrp.group_mrp_user 1 1 1 1 0
19 access_stock_location_mrp_worker access_stock_picking_mrp_worker stock.location mrp_worker stock.picking mrp_worker stock.model_stock_location stock.model_stock_picking mrp.group_mrp_user 1 0 1 0 1 0 1
20 access_stock_move_mrp_worker access_stock_warehouse stock.move mrp_worker stock.warehouse mrp_worker stock.model_stock_move stock.model_stock_warehouse mrp.group_mrp_user 1 1 0 1 0 0
21 access_stock_picking_mrp_worker access_account_analytic_journal_mrp_worker stock.picking mrp_worker account.analytic.journal mrp_worker stock.model_stock_picking account.model_account_analytic_journal mrp.group_mrp_user 1 1 0 1 0 1 0
22 access_stock_warehouse access_account_account stock.warehouse mrp_worker account.account mrp_worker stock.model_stock_warehouse account.model_account_account mrp.group_mrp_user 1 0 0 0
23 access_account_analytic_journal_mrp_worker access_hr_timesheet_group_mrp_worker account.analytic.journal mrp_worker resource.calendar mrp_manager account.model_account_analytic_journal resource.model_resource_calendar mrp.group_mrp_user mrp.group_mrp_manager 1 0 1 0 1 0 1
24 access_account_account access_procurement_user account.account mrp_worker procurement.order.user account.model_account_account model_procurement_order mrp.group_mrp_user base.group_user 1 0 1 0 1 0 1
25 access_purchase_order_mrp_worker access_mrp_production_stock_worker purchase.order mrp_worker mrp.production stock_worker purchase.model_purchase_order model_mrp_production mrp.group_mrp_user stock.group_stock_user 1 0 0 0
26 access_purchase_order_line_mrp_worker access_report_workcenter_load purchase.order.line mrp_worker report.workcenter.load purchase.model_purchase_order_line model_report_workcenter_load mrp.group_mrp_user mrp.group_mrp_manager 1 0 1 0 1 0 1
27 access_hr_timesheet_group_mrp_worker access_report_mrp_inout resource.calendar mrp_manager report.mrp.inout resource.model_resource_calendar model_report_mrp_inout mrp.group_mrp_manager 1 1 1 1
28 access_procurement_user access_ir_property_manager procurement.order.user ir.property manager model_procurement_order base.model_ir_property base.group_user mrp.group_mrp_manager 1 1 1 1
29 access_mrp_production_stock_worker access_account_sequence_fiscalyear mrp.production stock_worker account.sequence.fiscalyear model_mrp_production account.model_account_sequence_fiscalyear stock.group_stock_user mrp.group_mrp_user 1 0 1 0 1 0 1
30 access_report_workcenter_load access_product_product_user report.workcenter.load product.product user model_report_workcenter_load product.model_product_product mrp.group_mrp_manager mrp.group_mrp_user 1 1 0 1 0 1 0
31 access_report_mrp_inout access_product_template_user report.mrp.inout product.template user model_report_mrp_inout product.model_product_template mrp.group_mrp_manager mrp.group_mrp_user 1 1 0 1 0 1 0
32 access_ir_property_manager access_product_uom_user ir.property manager product.uom user base.model_ir_property product.model_product_uom mrp.group_mrp_manager mrp.group_mrp_user 1 1 0 1 0 1 0
33 access_account_sequence_fiscalyear access_product_supplierinfo_user account.sequence.fiscalyear product.supplierinfo user account.model_account_sequence_fiscalyear product.model_product_supplierinfo mrp.group_mrp_user 1 1 1 1
34 access_product_product_user access_res_partner product.product user res.partner product.model_product_product base.model_res_partner mrp.group_mrp_user 1 0 0 0
35 access_product_template_user access_workcenter_user product.template user mrp.production.workcenter.line.user product.model_product_template model_mrp_production_workcenter_line mrp.group_mrp_user 1 0 1 0 1 0 1
36 access_product_uom_user access_resource_calendar_leaves_user product.uom user mrp.resource.calendar.leaves.user product.model_product_uom resource.model_resource_calendar_leaves mrp.group_mrp_user 1 0 1 0 1 0 1
37 access_product_supplierinfo_user access_resource_calendar_leaves_manager product.supplierinfo user mrp.resource.calendar.leaves.manager product.model_product_supplierinfo resource.model_resource_calendar_leaves mrp.group_mrp_user mrp.group_mrp_manager 1 1 0 1 0 1 0
38 access_stock_tracking access_resource_calendar_attendance_mrp_user stock.tracking mrp.resource.calendar.attendance.mrp.user stock.model_stock_tracking resource.model_resource_calendar_attendance mrp.group_mrp_user 1 1 1 0 1
39 access_res_partner access_resource_calendar_attendance_manager res.partner mrp.resource.calendar.attendance.manager base.model_res_partner resource.model_resource_calendar_attendance mrp.group_mrp_user mrp.group_mrp_manager 1 0 1 0 1 0 1
40 access_workcenter_user access_product_puom_categ mrp.production.workcenter.line.user product.uom.categ model_mrp_production_workcenter_line product.model_product_uom_categ mrp.group_mrp_user 1 1 0 1 0 1 0
41 access_resource_calendar_leaves_user access_resource_resource mrp.resource.calendar.leaves.user resource.resource resource.model_resource_calendar_leaves resource.model_resource_resource mrp.group_mrp_user 1 1 0 1 0 1 0
42 access_resource_calendar_leaves_manager access_board_board_user mrp.resource.calendar.leaves.manager mrp.board.board resource.model_resource_calendar_leaves board.model_board_board mrp.group_mrp_manager mrp.group_mrp_user 1 0 0 0
43 access_resource_calendar_attendance_mrp_user access_account_sequence_fiscalyear_manager mrp.resource.calendar.attendance.mrp.user account.sequence.fiscalyear resource.model_resource_calendar_attendance account.model_account_sequence_fiscalyear mrp.group_mrp_user mrp.group_mrp_manager 1 1 0 1 0 1 0
44 access_resource_calendar_attendance_manager access_product_supplierinfo_manager mrp.resource.calendar.attendance.manager product.supplierinfo user resource.model_resource_calendar_attendance product.model_product_supplierinfo mrp.group_mrp_manager 1 1 0 1 0 1 0
45 access_product_puom_categ access_mrp_production_manager product.uom.categ mrp.production manager product.model_product_uom_categ model_mrp_production mrp.group_mrp_user mrp.group_mrp_manager 1 0 0 0
46 access_resource_resource access_procurement_manager resource.resource procurement.order resource.model_resource_resource model_procurement_order mrp.group_mrp_user mrp.group_mrp_manager 1 0 0 0
47 access_account_sequence_fiscalyear_manager access_workcenter_manager account.sequence.fiscalyear mrp.production.workcenter.line account.model_account_sequence_fiscalyear model_mrp_production_workcenter_line mrp.group_mrp_manager 1 0 0 0
48 access_product_supplierinfo_manager access_stock_move_mrp_manager product.supplierinfo user stock.move mrp_manager product.model_product_supplierinfo stock.model_stock_move mrp.group_mrp_manager 1 0 0 0
49 access_stock_tracking_manager access_mrp_production_product_line_manager stock.tracking mrp.production.product.line manager stock.model_stock_tracking model_mrp_production_product_line mrp.group_mrp_manager 1 0 0 0
50 access_mrp_production_manager access_account_sequence_fiscalyear_system mrp.production manager account.sequence.fiscalyear.system model_mrp_production account.model_account_sequence_fiscalyear mrp.group_mrp_manager 1 0 0 0
51 access_procurement_manager access_stock_production_lot_user procurement.order stock.production.lot model_procurement_order stock.model_stock_production_lot mrp.group_mrp_manager mrp.group_mrp_user 1 0 1 0 1 0 1
52 access_workcenter_manager access_stock_warehouse_orderpoint_user mrp.production.workcenter.line stock.warehouse.orderpoint model_mrp_production_workcenter_line stock.model_stock_warehouse_orderpoint mrp.group_mrp_manager mrp.group_mrp_user 1 0 0 0
53 access_stock_move_mrp_manager access_stock_picking_mrp_manager stock.move mrp_manager stock.picking mrp_manager stock.model_stock_move stock.model_stock_picking mrp.group_mrp_manager 1 0 0 0
54 access_mrp_production_product_line_manager access_report_mrp_inout_user mrp.production.product.line manager report.mrp.inout user model_mrp_production_product_line model_report_mrp_inout mrp.group_mrp_manager mrp.group_mrp_user 1 0 0 0
55 access_account_sequence_fiscalyear_system access_report_workcenter_load_user account.sequence.fiscalyear.system report.workcenter.load.user account.model_account_sequence_fiscalyear model_report_workcenter_load mrp.group_mrp_manager mrp.group_mrp_user 1 0 0 0
56 access_stock_production_lot_user access_mrp_bom_salesman stock.production.lot mrp.bom stock.model_stock_production_lot model_mrp_bom mrp.group_mrp_user base.group_sale_salesman 1 1 0 1 0 1 0
57 access_stock_warehouse_orderpoint_user access_mrp_bom_stockuser stock.warehouse.orderpoint mrp.bom procurement.model_stock_warehouse_orderpoint model_mrp_bom mrp.group_mrp_user stock.group_stock_user 1 0 0 0
58 access_stock_picking_mrp_manager access_product_uom_categ_mrp_manager stock.picking mrp_manager product.uom.categ mrp_manager stock.model_stock_picking product.model_product_uom_categ mrp.group_mrp_manager 1 0 1 0 1 0 1
59 access_report_mrp_inout_user access_product_uom_mrp_manager report.mrp.inout user product.uom mrp_manager model_report_mrp_inout product.model_product_uom mrp.group_mrp_user mrp.group_mrp_manager 1 0 1 0 1 0 1
60 access_report_workcenter_load_user access_product_ul_mrp_manager report.workcenter.load.user product.ul mrp_manager model_report_workcenter_load product.model_product_ul mrp.group_mrp_user mrp.group_mrp_manager 1 0 1 0 1 0 1
61 access_mrp_bom_salesman access_product_category_mrp_manager mrp.bom product.category mrp_manager model_mrp_bom product.model_product_category base.group_sale_salesman mrp.group_mrp_manager 1 0 1 0 1 0 1
62 access_mrp_bom_stockuser access_product_template_mrp_manager mrp.bom product.template mrp_manager model_mrp_bom product.model_product_template stock.group_stock_user mrp.group_mrp_manager 1 0 1 0 1 0 1
63 access_product_uom_categ_mrp_manager access_product_product_mrp_manager product.uom.categ mrp_manager product.product mrp_manager product.model_product_uom_categ product.model_product_product mrp.group_mrp_manager 1 1 1 1
64 access_product_uom_mrp_manager access_product_packaging_mrp_manager product.uom mrp_manager product.packaging mrp_manager product.model_product_uom product.model_product_packaging mrp.group_mrp_manager 1 1 1 1
65 access_product_ul_mrp_manager access_pricelist_partnerinfo_mrp_manager product.ul mrp_manager pricelist.partnerinfo mrp_manager product.model_product_ul product.model_pricelist_partnerinfo mrp.group_mrp_manager 1 1 1 1
66 access_product_category_mrp_manager access_product_price_type_mrp_manager product.category mrp_manager product.price.type mrp_manager product.model_product_category product.model_product_price_type mrp.group_mrp_manager 1 1 1 1
67 access_product_template_mrp_manager access_product_pricelist_type_mrp_manager product.template mrp_manager product.pricelist.type mrp_manager product.model_product_template product.model_product_pricelist_type mrp.group_mrp_manager 1 1 1 1
68 access_product_product_mrp_manager access_product_pricelist_mrp_manager product.product mrp_manager product.pricelist mrp_manager product.model_product_product product.model_product_pricelist mrp.group_mrp_manager 1 1 1 1
69 access_product_packaging_mrp_manager access_ir_property_group_product_mrp_manager product.packaging mrp_manager ir_property group_product_mrp_manager product.model_product_packaging base.model_ir_property mrp.group_mrp_manager 1 1 1 1
70 access_pricelist_partnerinfo_mrp_manager access_product_group_res_partner_mrp_manager pricelist.partnerinfo mrp_manager res_partner group_mrp_manager product.model_pricelist_partnerinfo base.model_res_partner mrp.group_mrp_manager 1 1 1 1 0
71 access_product_price_type_mrp_manager access_product_pricelist_version_mrp_manager product.price.type mrp_manager product.pricelist.version mrp_manager product.model_product_price_type product.model_product_pricelist_version mrp.group_mrp_manager 1 1 1 1
72 access_product_pricelist_type_mrp_manager access_product_pricelist_item_mrp_manager product.pricelist.type mrp_manager product.pricelist.item mrp_manager product.model_product_pricelist_type product.model_product_pricelist_item mrp.group_mrp_manager 1 1 1 1
73 access_product_pricelist_mrp_manager access_resource_calendar_manufacturinguser product.pricelist mrp_manager resource.calendar manufacturing.user product.model_product_pricelist resource.model_resource_calendar mrp.group_mrp_manager mrp.group_mrp_user 1 1 0 1 0 1 0
74 access_ir_property_group_product_mrp_manager access_account_journal_mrp_manager ir_property group_product_mrp_manager account.journal mrp manager base.model_ir_property account.model_account_journal mrp.group_mrp_manager 1 1 0 1 0 1 0
75 access_product_group_res_partner_mrp_manager access_mrp_property_group res_partner group_mrp_manager mrp.property.group base.model_res_partner model_mrp_property_group mrp.group_mrp_manager stock.group_stock_manager 1 1 1 0 1
76 access_product_pricelist_version_mrp_manager access_mrp_property product.pricelist.version mrp_manager mrp.property product.model_product_pricelist_version model_mrp_property mrp.group_mrp_manager stock.group_stock_manager 1 1 1 1
77 access_product_pricelist_item_mrp_manager access_mrp_property_group product.pricelist.item mrp_manager mrp.property.group product.model_product_pricelist_item model_mrp_property_group mrp.group_mrp_manager base.group_user 1 1 0 1 0 1 0
78 access_resource_calendar_manufacturinguser access_mrp_property resource.calendar manufacturing.user mrp.property resource.model_resource_calendar model_mrp_property mrp.group_mrp_user base.group_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

View File

@ -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

View File

@ -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."

View File

@ -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.
-

View File

@ -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')

View File

@ -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])],

View File

@ -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:

View File

@ -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 {}

View File

@ -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 {}

View File

@ -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

View File

@ -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'}

View File

@ -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>

View File

@ -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:

View File

@ -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.

View File

@ -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
)

View File

@ -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.
-

View File

@ -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,
}

View File

@ -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

View File

@ -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','&lt;&gt;','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>

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mrp_repair_user MRP Repair user model_mrp_repair stock.group_stock_user 1 0 0 0
3 access_mrp_repair_manager MRP Repair manager model_mrp_repair stock.group_stock_manager 1 1 1 1
4 access_mrp_repair_line_user MRP Repair Line user model_mrp_repair_line stock.group_stock_user 1 0 0 0
5 access_mrp_repair_line_manager MRP Repair Line manager model_mrp_repair_line stock.group_stock_manager 1 1 1 1
6 access_mrp_repair_fee_user MRP Repair Fee user model_mrp_repair_fee stock.group_stock_user 1 1 1 1
7 access_mrp_repair_fee_manager MRP Repair Fee manager model_mrp_repair_fee stock.group_stock_manager 1 0 0 0
8 access_mrp_repair_user MRP Repair user model_mrp_repair mrp.group_mrp_user 1 1 1 1
9 access_mrp_repair_manager MRP Repair manager model_mrp_repair mrp.group_mrp_manager 1 0 0 0
10 access_product_pricelist_manager product.pricelist manager product.model_product_pricelist mrp.group_mrp_manager 1 0 0 0
11 access_product_pricelist_user product.pricelist user product.model_product_pricelist mrp.group_mrp_user 1 1 1 1
12 access_stock_production_lot_user stock.production.lot user stock.model_stock_production_lot mrp.group_mrp_user 1 1 1 1
13 access_stock_production_lot_manager stock.production.lot manager stock.model_stock_production_lot mrp.group_mrp_manager 1 0 0 0
14 access_mrp_repair_line_user mrp.repair.line user model_mrp_repair_line mrp.group_mrp_user 1 1 1 1
15 access_mrp_repair_line_manager mrp.repair.line manager model_mrp_repair_line mrp.group_mrp_manager 1 0 0 0
16 access_stock_production_lot_revision_manager access_product_price_type_manager stock.production.lot.revision manager product.price.type manager stock.model_stock_production_lot_revision product.model_product_price_type mrp.group_mrp_manager 1 0 0 0
17 access_stock_production_lot_revision_user access_product_price_type_user stock.production.lot.revision user product.price.type stock.model_stock_production_lot_revision product.model_product_price_type mrp.group_mrp_user 1 1 1 1
18 access_product_price_type_manager access_account_tax_user product.price.type manager account.tax product.model_product_price_type account.model_account_tax mrp.group_mrp_manager mrp.group_mrp_user 1 0 1 0 1 0 1
19 access_product_price_type_user access_account_tax_manager product.price.type account.tax manager product.model_product_price_type account.model_account_tax mrp.group_mrp_user mrp.group_mrp_manager 1 1 0 1 0 1 0
20 access_account_tax_user access_mrp_repair_fee_user_mrp account.tax MRP Repair Fee user mrp account.model_account_tax model_mrp_repair_fee mrp.group_mrp_user 1 1 1 1
21 access_account_tax_manager access_mrp_repair_fee_mgr account.tax manager MRP Repair Fee mgr account.model_account_tax model_mrp_repair_fee mrp.group_mrp_manager 1 0 0 0
22 access_mrp_repair_fee_user_mrp access_account_invoice_user MRP Repair Fee user mrp account.invoice model_mrp_repair_fee account.model_account_invoice mrp.group_mrp_user 1 1 1 1
23 access_mrp_repair_fee_mgr access_account_invoice_manager MRP Repair Fee mgr account.invoice manager model_mrp_repair_fee account.model_account_invoice mrp.group_mrp_manager 1 0 0 0
24 access_account_invoice_user access_account_invoice_line_user account.invoice account.invoice.line account.model_account_invoice account.model_account_invoice_line mrp.group_mrp_user 1 1 1 1
25 access_account_invoice_manager access_account_invoice_line_manager account.invoice manager account.invoice.line manager account.model_account_invoice account.model_account_invoice_line mrp.group_mrp_manager 1 0 0 0
26 access_account_invoice_line_user access_account_journal_user account.invoice.line account.journal account.model_account_invoice_line account.model_account_journal mrp.group_mrp_user 1 1 1 1
27 access_account_invoice_line_manager access_account_journal_manager account.invoice.line manager account.journal manager account.model_account_invoice_line account.model_account_journal 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

View File

@ -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"

View File

@ -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"/>

View File

@ -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)

View File

@ -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 -->

View File

@ -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>

View File

@ -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)""")

View File

@ -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"/>

View File

@ -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:

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
13 access_stock_move_pos_user stock.move pos_user stock.model_stock_move group_pos_user 1 1 1 1
14 access_report_sales_by_user_pos report.sales.by.user.pos model_report_sales_by_user_pos group_pos_user 1 0 0 0
15 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
16 access_report_pos_order report.pos.order model_report_pos_order group_pos_user 1 1 1 1
17 access_account_bank_statement account.bank.statement account.model_account_bank_statement group_pos_user 1 1 1 0
18 access_account_bank_statement_manager account.bank.statement manager account.model_account_bank_statement group_pos_manager 1 1 1 1

View File

@ -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>

View File

@ -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,
};
},

View File

@ -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'>

View File

@ -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>

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 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
3 access_stock_move stock.move stock.model_stock_move base.group_portal 1 0 0 0
4 access_stock_warehouse_orderpoint stock.warehouse.orderpoint procurement.model_stock_warehouse_orderpoint stock.model_stock_warehouse_orderpoint base.group_portal 1 0 0 0

View File

@ -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>

View File

@ -21,7 +21,4 @@
import procurement
import wizard
import schedulers
import company
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -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:

View File

@ -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>

View File

@ -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:

View File

@ -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>

View File

@ -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&lt;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="['&amp;', ('date_planned','&lt;', current_date), ('state', 'in', ('draft', 'confirmed'))]" help="Procurement started late" />
<filter icon="terp-gnome-cpu-frequency-applet+" string="Late" domain="['&amp;', ('date_planned','&lt;', 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','&lt;&gt;','service'),('procure_method','&lt;&gt;','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','&lt;&gt;','product'),('procure_method','&lt;&gt;','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','&lt;&gt;','consu'),('procure_method','&lt;&gt;','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>

View File

@ -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>

View File

@ -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:

View File

@ -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

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_procurement procurement.order model_procurement_order base.group_user 1 0 1 0 1 0
3 access_procurement_stock_user access_procurement_group procurement.order stock.user procurement.group model_procurement_order model_procurement_group stock.group_stock_user base.group_user 1 1 1 1 0
4 access_stock_warehouse_orderpoint access_procurement_rule stock.warehouse.orderpoint procurement.rule model_stock_warehouse_orderpoint model_procurement_rule stock.group_stock_user base.group_user 1 0 1 0 1 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

View File

@ -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>

View File

@ -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'

View File

@ -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:

View File

@ -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:

View File

@ -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>

View File

@ -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 {}

View File

@ -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

View File

@ -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:

View File

@ -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