Merge lp:~openerp-dev/openobject-addons/trunk-mo-disassemble-jpr into lp:openobject-addons

Proposed by Jitendra Prajapati(OpenERP)
Status: Needs review
Proposed branch: lp:~openerp-dev/openobject-addons/trunk-mo-disassemble-jpr
Merge into: lp:openobject-addons
Diff against target: 800 lines (+387/-41)
15 files modified
mrp/__openerp__.py (+2/-1)
mrp/mrp.py (+70/-12)
mrp/mrp_demo.xml (+1/-0)
mrp/mrp_view.xml (+46/-18)
mrp/procurement.py (+1/-0)
mrp/static/src/js/mrp.js (+38/-0)
mrp/test/disassemble_process.yml (+96/-0)
mrp/wizard/__init__.py (+1/-0)
mrp/wizard/change_production_qty.py (+1/-1)
mrp/wizard/disassemble_qty.py (+63/-0)
mrp/wizard/disassemble_qty_view.xml (+36/-0)
mrp/wizard/mrp_product_produce.py (+14/-2)
mrp/wizard/mrp_product_produce_view.xml (+12/-4)
mrp_byproduct/mrp_byproduct.py (+4/-2)
mrp_operations/mrp_operations.py (+2/-1)
To merge this branch: bzr merge lp:~openerp-dev/openobject-addons/trunk-mo-disassemble-jpr
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+219795@code.launchpad.net

Description of the change

Hello,

Task: [MRP] allow disassembling through manufacturing orders

In the Manufacturing Order, allow to use negative quantities in the Product Quantity field so that the selected product is dismantled and individual components are put back into inventory, with all appropriate stock moves.

More details on pad:
http://pad.openerp.com/p/r.Vquhp9c9xsuPRWaX

Thanks

To post a comment you must log in.
9477. By Jitendra Prajapati(OpenERP)

[IMP]update module description.
create mo with positve and negative qty only.
Try to duplicate MO coping origin but not work for disassembling.
Change the License years as per current License policies.

Unmerged revisions

9477. By Jitendra Prajapati(OpenERP)

[IMP]update module description.
create mo with positve and negative qty only.
Try to duplicate MO coping origin but not work for disassembling.
Change the License years as per current License policies.

9476. By Jitendra Prajapati(OpenERP)

[FIX/IMP]except disassamble coming from procurement.

9475. By Jitendra Prajapati(OpenERP)

[IMP]update wizard label,when disassamble time.
change the source and destination location of mrp_production when product_qty field < 0

9474. By Jitendra Prajapati(OpenERP)

[IMP]improve code

9473. By Jitendra Prajapati(OpenERP)

[ADD]add a new yml file and create disassemble process test case

9472. By Jitendra Prajapati(OpenERP)

[IMP]apply widget on product_qty field. change the label in mrp form view, when product disassemble time.

9471. By Jitendra Prajapati(OpenERP)

[MERGE]sync with lp:openobject-addons

9470. By RGA(OpenERP)

[IMP] add widget update_label that change label of page and separator on change value of quantity field

9469. By Jitendra Prajapati(OpenERP)

[FIX]runbot test

9468. By Jitendra Prajapati(OpenERP)

[IMP] mrp: filter 'Manufacturing'/'Disassemble' in search view.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mrp/__openerp__.py'
2--- mrp/__openerp__.py 2014-05-07 17:12:05 +0000
3+++ mrp/__openerp__.py 2014-05-23 07:38:59 +0000
4@@ -34,7 +34,7 @@
5 Manage the Manufacturing process in OpenERP
6 ===========================================
7
8-The manufacturing module allows you to cover planning, ordering, stocks and the manufacturing or assembly of products from raw materials and components. It handles the consumption and production of products according to a bill of materials and the necessary operations on machinery, tools or human resources according to routings.
9+The manufacturing module allows you to cover planning, ordering, stocks and the manufacturing or assembly and disassembling of products from raw materials and components. It handles the consumption and production of products according to a bill of materials and the necessary operations on machinery, tools or human resources according to routings.
10
11 It supports complete integration and planification of stockable goods, consumables or services. Services are completely integrated with the rest of the software. For instance, you can set up a sub-contracting service in a bill of materials to automatically purchase on order the assembly of your production.
12
13@@ -63,6 +63,7 @@
14 'wizard/mrp_price_view.xml',
15 'wizard/mrp_workcenter_load_view.xml',
16 'wizard/stock_move_view.xml',
17+ 'wizard/disassemble_qty_view.xml',
18 'mrp_view.xml',
19 'mrp_report.xml',
20 'company_view.xml',
21
22=== modified file 'mrp/mrp.py'
23--- mrp/mrp.py 2014-05-08 11:59:17 +0000
24+++ mrp/mrp.py 2014-05-23 07:38:59 +0000
25@@ -96,6 +96,12 @@
26 value = {'costs_hour': cost.standard_price}
27 return {'value': value}
28
29+class stock_move(osv.osv):
30+ _inherit = 'stock.move'
31+ _columns = {
32+ 'disassemble': fields.boolean('Disassemble'),
33+ }
34+
35 class mrp_routing(osv.osv):
36 """
37 For specifying the routings of Work Centers.
38@@ -426,14 +432,19 @@
39 """ Return product quantity percentage """
40 result = dict.fromkeys(ids, 100)
41 for mrp_production in self.browse(cr, uid, ids, context=context):
42- if mrp_production.product_qty:
43+ if abs(mrp_production.product_qty):
44 done = 0.0
45 for move in mrp_production.move_created_ids2:
46 if not move.scrapped and move.product_id == mrp_production.product_id:
47 done += move.product_qty
48- result[mrp_production.id] = done / mrp_production.product_qty * 100
49+ result[mrp_production.id] = done / abs(mrp_production.product_qty) * 100
50 return result
51
52+ def create(self, cr, uid, values, context=None):
53+ if values['product_qty'] < 0:
54+ self._description = _('Disassemble Order')
55+ return super(mrp_production, self).create(cr, uid, values, context=context)
56+
57 def _moves_assigned(self, cr, uid, ids, name, arg, context=None):
58 """ Test whether all the consume lines are assigned """
59 res = {}
60@@ -505,6 +516,9 @@
61 When the production is over, the status is set to 'Done'."),
62 'hour_total': fields.function(_production_calc, type='float', string='Total Hours', multi='workorder', store=True),
63 'cycle_total': fields.function(_production_calc, type='float', string='Total Cycles', multi='workorder', store=True),
64+ 'disassemble': fields.boolean('Disassemble'),
65+ 'disassemble_doc': fields.char('Disassemble Document(s)', size=64, readonly=True, help="Reference of disassembled document(s) for this Manufacturing Order."),
66+ 'qty_to_disassemble': fields.float('Remaining Quantity to Disassemble', help="Available product quantity to disassemble", digits_compute=dp.get_precision('Product Unit of Measure')),
67 'user_id': fields.many2one('res.users', 'Responsible'),
68 'company_id': fields.many2one('res.company', 'Company', required=True),
69 'ready_production': fields.function(_moves_assigned, type='boolean', store={'stock.move': (_mrp_from_move, ['state'], 10)}),
70@@ -519,7 +533,9 @@
71 'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'mrp.production') or '/',
72 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'mrp.production', context=c),
73 'location_src_id': _src_id_default,
74- 'location_dest_id': _dest_id_default
75+ 'location_dest_id': _dest_id_default,
76+ 'disassemble': False,
77+ 'disassemble_doc': False
78 }
79
80 _sql_constraints = [
81@@ -530,12 +546,12 @@
82
83 def _check_qty(self, cr, uid, ids, context=None):
84 for order in self.browse(cr, uid, ids, context=context):
85- if order.product_qty <= 0:
86+ if order.product_qty == 0:
87 return False
88 return True
89
90 _constraints = [
91- (_check_qty, 'Order quantity cannot be negative or zero!', ['product_qty']),
92+ (_check_qty, 'Order quantity cannot be zero!', ['product_qty']),
93 ]
94
95 def unlink(self, cr, uid, ids, context=None):
96@@ -547,6 +563,12 @@
97 def copy(self, cr, uid, id, default=None, context=None):
98 if default is None:
99 default = {}
100+ mo = self.browse(cr, uid, id, context=context)
101+ origin = False
102+ if default.get('disassemble'):
103+ origin = mo.origin + '-' + mo.name if not mo.disassemble and mo.origin else mo.name
104+ if not mo.disassemble:
105+ origin = mo.origin
106 default.update({
107 'name': self.pool.get('ir.sequence').get(cr, uid, 'mrp.production'),
108 'move_lines': [],
109@@ -555,6 +577,7 @@
110 'move_created_ids2': [],
111 'product_lines': [],
112 'move_prod_id': False,
113+ 'origin': origin,
114 })
115 return super(mrp_production, self).copy(cr, uid, id, default, context)
116
117@@ -570,6 +593,11 @@
118 return {'value': {'location_dest_id': src}}
119 return {}
120
121+ def onchange_product_qty(self, cr, uid, ids, quantity, context=None):
122+ if quantity < 0:
123+ return {'value': {'disassemble': True, 'qty_to_disassemble': 0.0}}
124+ return {'value': {'disassemble': False, 'qty_to_disassemble': quantity}}
125+
126 def product_id_change(self, cr, uid, ids, product_id, product_qty=0, context=None):
127 """ Finds UoM of changed product.
128 @param product_id: Id of changed product.
129@@ -645,7 +673,7 @@
130 raise osv.except_osv(_('Error!'), _("Cannot find a bill of material for this product."))
131
132 # get components and workcenter_lines from BoM structure
133- factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id)
134+ factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, abs(production.product_qty), bom_point.product_uom.id)
135 res = bom_obj._bom_explode(cr, uid, bom_point, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id)
136 results = res[0] # product_lines
137 results2 = res[1] # workcenter_lines
138@@ -667,6 +695,34 @@
139 """
140 return len(self._action_compute_lines(cr, uid, ids, properties=properties, context=context))
141
142+ def action_disassemble(self, cr, uid, id, qty, context=None):
143+ """ Disassemble the production order.
144+ """
145+ mo = self.browse(cr, uid, id, context=context)
146+ values = {
147+ 'disassemble': True,
148+ 'origin': mo.name,
149+ 'routing_id': False,
150+ 'product_qty': qty,
151+ 'qty_to_disassemble': 0,
152+ }
153+ mo_id = self.copy(cr, uid, mo.id, values, context=context)
154+ if mo_id:
155+ source_doc = self.read(cr, uid, mo_id, ['name'], context=context)
156+ mo.write({'disassemble_doc': mo.disassemble_doc and mo.disassemble_doc + ", " + source_doc['name'] or source_doc['name']}, context=context)
157+ view_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'mrp', 'mrp_production_form_view')
158+ view_id = view_ref and view_ref[1] or False,
159+ return {
160+ 'type': 'ir.actions.act_window',
161+ 'name': _('Disassemble Manufacturing Order'),
162+ 'res_model': 'mrp.production',
163+ 'res_id': mo_id,
164+ 'view_type': 'form',
165+ 'view_mode': 'form',
166+ 'view_id': False,
167+ 'target': 'current',
168+ }
169+
170 def action_cancel(self, cr, uid, ids, context=None):
171 """ Cancels the production order and related stock moves.
172 @return: True
173@@ -788,7 +844,7 @@
174 dicts[scheduled.product_id.id] = {}
175
176 # total qty of consumed product we need after this consumption
177- total_consume = ((product_qty + produced_qty) * scheduled.product_qty / production.product_qty)
178+ total_consume = ((product_qty + produced_qty) * scheduled.product_qty / abs(production.product_qty))
179 qty = total_consume - consumed_qty
180
181 # Search for quants related to this related move
182@@ -861,7 +917,7 @@
183 for produce_product in production.move_created_ids:
184 produced_qty = produced_products.get(produce_product.product_id.id, 0)
185 subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context)
186- rest_qty = (subproduct_factor * production.product_qty) - produced_qty
187+ rest_qty = (subproduct_factor * abs(production.product_qty)) - produced_qty
188 if float_compare(rest_qty, (subproduct_factor * production_qty), precision_rounding=produce_product.product_id.uom_id.rounding) < 0:
189 prod_name = produce_product.product_id.name_get()[0][1]
190 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))
191@@ -977,15 +1033,16 @@
192 'date': production.date_planned,
193 'product_id': production.product_id.id,
194 'product_uom': production.product_uom.id,
195- 'product_uom_qty': production.product_qty,
196+ 'product_uom_qty': abs(production.product_qty),
197 'product_uos_qty': production.product_uos and production.product_uos_qty or False,
198 'product_uos': production.product_uos and production.product_uos.id or False,
199- 'location_id': source_location_id,
200- 'location_dest_id': destination_location_id,
201+ 'location_id': production.disassemble and destination_location_id or source_location_id,
202+ 'location_dest_id': production.disassemble and source_location_id or destination_location_id,
203 'move_dest_id': production.move_prod_id.id,
204 'company_id': production.company_id.id,
205 'production_id': production.id,
206 'origin': production.name,
207+ 'state': production.disassemble and 'assigned' or 'waiting', # for products to produce
208 }
209 move_id = stock_move.create(cr, uid, data, context=context)
210 #a phantom bom cannot be used in mrp order so it's ok to assume the list returned by action_confirm
211@@ -1020,7 +1077,7 @@
212 'name': production.name,
213 'date': production.date_planned,
214 'product_id': product.id,
215- 'product_uom_qty': qty,
216+ 'product_uom_qty': abs(qty),
217 'product_uom': uom_id,
218 'product_uos_qty': uos_id and uos_qty or False,
219 'product_uos': uos_id or False,
220@@ -1032,6 +1089,7 @@
221 #this saves us a browse in create()
222 'price_unit': product.standard_price,
223 'origin': production.name,
224+ 'disassemble': production.disassemble,
225 })
226 return move_id
227
228
229=== modified file 'mrp/mrp_demo.xml'
230--- mrp/mrp_demo.xml 2014-01-20 14:42:43 +0000
231+++ mrp/mrp_demo.xml 2014-05-23 07:38:59 +0000
232@@ -661,6 +661,7 @@
233 <record id="mrp_production_2" model="mrp.production">
234 <field name="product_id" ref="product.product_product_27"/>
235 <field name="product_uom" ref="product.product_uom_unit"/>
236+ <field name="product_qty">5</field>
237 <field name="location_src_id" ref="stock.stock_location_stock"/>
238 <field name="location_dest_id" ref="stock.stock_location_output"/>
239 <field name="bom_id" ref="mrp.mrp_bom_7"/>
240
241=== modified file 'mrp/mrp_view.xml'
242--- mrp/mrp_view.xml 2014-05-07 18:29:17 +0000
243+++ mrp/mrp_view.xml 2014-05-23 07:38:59 +0000
244@@ -665,38 +665,54 @@
245 </field>
246 </record>
247
248+ <template id="mrp_backend" name="mrp assets" inherit_id="web.assets_backend">
249+ <xpath expr="." position="inside">
250+ <script type="text/javascript" src="/mrp/static/src/js/mrp.js"></script>
251+ </xpath>
252+ </template>
253+
254 <record id="mrp_production_form_view" model="ir.ui.view">
255 <field name="name">mrp.production.form</field>
256 <field name="model">mrp.production</field>
257 <field name="arch" type="xml">
258 <form string="Manufacturing Orders" version="7.0">
259 <header>
260- <button name="button_confirm" states="draft" string="Confirm Production" class="oe_highlight"/>
261- <button name="%(act_mrp_product_produce)d" states="ready,in_production" string="Produce" type="action" class="oe_highlight"/>
262- <button name="action_assign" states="confirmed,picking_except" string="Check Availability" type="object" class="oe_highlight"/>
263- <button name="force_production" states="confirmed" string="Force Reservation" type="object"/>
264+ <button name="button_confirm" string="Confirm Production" class="oe_highlight" attrs="{'invisible':['|',('disassemble', '=', True),('state','!=','draft')]}"/>
265+ <button name="button_confirm" string="Confirm Dismantling" class="oe_highlight" attrs="{'invisible':['|',('disassemble', '=', False),('state','!=','draft')]}"/>
266+ <button name="%(act_mrp_product_produce)d" string="Produce" type="action" class="oe_highlight" attrs="{'invisible':['|',('disassemble', '=', True),('state','not in',('ready', 'in_production'))]}"/>
267+ <button name="%(act_mrp_disassemble_product)d" string="Disassemble" type="action" class="oe_highlight" attrs="{'invisible':['|',('disassemble', '=', False),('state','not in',('ready', 'in_production'))]}"/>
268+ <button name="action_assign" string="Check Availability" type="object" class="oe_highlight" attrs="{'invisible':['|',('disassemble', '=', True),('state','not in',('confirmed','picking_except'))]}"/>
269+ <button name="force_production" states="confirmed" string="Force Reservation" type="object" class="oe_highlight"/>
270 <button name="button_produce" states="ready" string="Mark as Started"/>
271- <button name="button_cancel" states="draft,ready,in_production" string="Cancel Production"/>
272- <button name="action_cancel" type="object" states="confirmed" string="Cancel Production"/>
273+ <button name="button_recreate" states="picking_except" string="Recreate Picking"/>
274+ <button name="button_cancel" string="Cancel Production" attrs="{'invisible':['|',('disassemble', '=', True),('state','not in',('draft','ready','in_production','picking_except'))]}"/>
275+ <button name="button_cancel" string="Cancel Dismantling" attrs="{'invisible':['|',('disassemble', '=', False),('state','not in',('draft','ready','in_production','picking_except'))]}"/>
276+ <button name="action_cancel" type="object" string="Cancel Production" attrs="{'invisible':['|',('disassemble', '=', True),('state','!=','confirmed')]}"/>
277+ <button name="action_cancel" type="object" string="Cancel Dismantling" attrs="{'invisible':['|',('disassemble', '=', False),('state','!=','confirmed')]}"/>
278+ <button name="%(action_change_disassemble_qty)d" type="action" string="Disassemble" attrs="{'invisible':['|','|', ('state','!=','done'), ('qty_to_disassemble','=',0)]}"/>
279 <field name="state" widget="statusbar" statusbar_visible="draft,ready,in_production,done" statusbar_colors='{"confirmed":"blue"}'/>
280 </header>
281 <sheet>
282 <div class="oe_title">
283- <h1>Manufacturing Order <field name="name" class="oe_inline"/></h1>
284+ <h1>
285+ <label string="Disassemble" attrs="{'invisible':[('disassemble','=',False)]}" class="oe_inline"/>Manufacturing Order
286+ <field name="name" class="oe_inline"/>
287+ </h1>
288 </div>
289 <group>
290 <group>
291 <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"}'/>
292+ <field name="qty_to_disassemble" invisible="1"/>
293 <label for="product_qty"/>
294 <div>
295- <field name="product_qty" class="oe_inline" on_change="product_id_change(product_id, product_qty)"/>
296+ <field name="product_qty" widget="mrp_product_qty" class="oe_inline" on_change="onchange_product_qty(product_qty)"/>
297 <field name="product_uom" groups="product.group_uom" class="oe_inline"/>
298 <button type="action"
299 name="%(mrp.action_change_production_qty)d"
300 string="Update" states="confirmed" class="oe_edit_only oe_link"/>
301 </div>
302- <label for="product_uos_qty" groups="product.group_uos"/>
303- <div groups="product.group_uos">
304+ <label for="product_uos_qty" attrs="{'invisible':[('disassemble', '=', True)]}" groups="product.group_uos"/>
305+ <div attrs="{'invisible':[('disassemble', '=', True)]}" groups="product.group_uos">
306 <field name="product_uos_qty" class="oe_inline"/>
307 <label string="-" attrs="{'invisible':[('product_uos','=',False)]}" class="oe_inline"/>
308 <field name="product_uos" class="oe_inline"/>
309@@ -715,15 +731,16 @@
310 </group>
311 </group>
312 <notebook>
313- <page string="Consumed Products">
314+ <page string="Consumed Products" name="consumed_products">
315 <group>
316- <group string="Products to Consume">
317+ <group string="Products to Consume" name="group_to_consume">
318 <field name="move_lines" nolabel="1" options="{'reload_on_button': true}">
319 <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">
320 <field name="product_id"/>
321 <field name="product_qty" string="Quantity"/>
322 <field name="product_uom" string="Unit of Measure" groups="product.group_uom"/>
323 <field name="state" invisible="1"/>
324+ <field name="disassemble" invisible="1"/>
325 <button name="%(mrp.move_consume)d"
326 string="Consume Products" type="action"
327 icon="gtk-go-forward" context="{'consume': True}"
328@@ -732,11 +749,11 @@
329 <button name="%(stock.move_scrap)d"
330 string="Scrap Products" type="action"
331 icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
332- states="draft,waiting,confirmed,assigned"/>
333+ attrs="{'invisible':['|',('disassemble', '=', True),('state', '=', 'cancel')]}"/>
334 </tree>
335 </field>
336 </group>
337- <group string="Consumed Products">
338+ <group string="Consumed Products" name="group_consumed">
339 <field name="move_lines2" nolabel="1" options="{'reload_on_button': true}">
340 <tree colors="red:scrapped==True;blue:state == 'draft';black:state in ('confirmed','ready','in_production');gray:state == 'cancel' " string="Consumed Products" editable="bottom">
341 <field name="product_id" readonly="1"/>
342@@ -745,12 +762,17 @@
343 <field name="product_uom" readonly="1" string="Unit of Measure" groups="product.group_uom"/>
344 <field name="state" invisible="1"/>
345 <field name="scrapped" invisible="1"/>
346+ <field name="disassemble" invisible="1"/>
347+ <button name="%(stock.move_scrap)d"
348+ string="Scrap Products" type="action"
349+ icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
350+ attrs="{'invisible':['|',('disassemble', '=', False),('state', '=', 'cancel')]}"/>
351 </tree>
352 </field>
353 </group>
354 </group>
355 </page>
356- <page string="Finished Products">
357+ <page string="Finished Products" attrs="{'invisible':[('disassemble', '=', True)]}">
358 <group>
359 <group string="Products to Produce">
360 <field name="move_created_ids" nolabel="1" options="{'reload_on_button': true}">
361@@ -804,7 +826,7 @@
362 </tree>
363 </field>
364 </page>
365- <page string="Scheduled Products">
366+ <page string="Scheduled Products" attrs="{'invisible':[('disassemble', '=', True)]}">
367 <button name="action_compute" states="draft"
368 string="Compute Data" type="object"
369 icon="terp-stock_format-scientific"/>
370@@ -816,10 +838,12 @@
371 <field name="priority"/>
372 <field name="date_start" invisible="1"/>
373 <field name="date_finished" invisible="1"/>
374+ <field name="disassemble" invisible="1"/>
375+ <field name="disassemble_doc" attrs="{'invisible':[('disassemble_doc', '=', False)]}"/>
376 </group>
377 <group>
378- <field name="company_id" groups="base.group_multi_company" widget="selection" />
379- <field name="move_prod_id" groups="stock.group_locations"/>
380+ <field name="company_id" groups="base.group_multi_company" widget="selection"/>
381+ <field name="move_prod_id" groups="stock.group_locations" attrs="{'invisible':[('disassemble', '=', True)]}"/>
382 </group>
383 </group>
384 </page>
385@@ -845,6 +869,10 @@
386 help="Manufacturing Orders which are ready to start production."/>
387 <filter icon="terp-check" string="In Production" name="inprogress" domain="[('state','=','in_production')]"
388 help="Manufacturing Orders which are currently in production."/>
389+ <filter string="Manufacturing" name="manufacturing" domain="[('disassemble', '=', False)]"
390+ help="Manufacturing Orders."/>
391+ <filter string="Disassemble" name="disassemble" domain="[('product_qty', '&lt;', 0)]"
392+ help="Disassemble Orders."/>
393 <separator/>
394 <filter icon="terp-gnome-cpu-frequency-applet+" string="Late" domain="['&amp;', ('date_planned','&lt;', current_date), ('state', 'in', ('draft', 'confirmed', 'ready'))]"
395 help="Production started late"/>
396
397=== modified file 'mrp/procurement.py'
398--- mrp/procurement.py 2014-05-07 17:14:31 +0000
399+++ mrp/procurement.py 2014-05-23 07:38:59 +0000
400@@ -95,6 +95,7 @@
401 'origin': procurement.origin,
402 'product_id': procurement.product_id.id,
403 'product_qty': procurement.product_qty,
404+ 'qty_to_disassemble': procurement.product_qty,
405 'product_uom': procurement.product_uom.id,
406 'product_uos_qty': procurement.product_uos and procurement.product_uos_qty or False,
407 'product_uos': procurement.product_uos and procurement.product_uos.id or False,
408
409=== added directory 'mrp/static/src/js'
410=== added file 'mrp/static/src/js/mrp.js'
411--- mrp/static/src/js/mrp.js 1970-01-01 00:00:00 +0000
412+++ mrp/static/src/js/mrp.js 2014-05-23 07:38:59 +0000
413@@ -0,0 +1,38 @@
414+openerp.mrp = function(instance) {
415+var _t = instance.web._t;
416+
417+instance.web.form.mrp_product_qty = instance.web.form.FieldFloat.extend({
418+ init: function(field_manager, node) {
419+ var self = this;
420+ this._super(field_manager, node);
421+ this.on("change:value", this, function() {
422+ self.update_label(this.get('value'));
423+ });
424+ var $page = this.view.$el.find("a[name='consumed_products']");
425+ var $label_product = this.view.$el.find("div[name='group_to_consume']");
426+ var $label_consumed = this.view.$el.find("div[name='group_consumed']");
427+ this.label_list = [$page, $label_product, $label_consumed];
428+ this.label_value = [[_t("Consumed Products"),_t("Disassembled Products")],
429+ [_t("Products to Consume"),_t("Products to Disassemble")],
430+ [_t("Consumed Products"),_t("Disassembled Products")]];
431+ },
432+ update_label:function(value){
433+ var self = this
434+ _.map(self.label_list, function(ele, i){
435+ value >= 0 ? ele.text(self.label_value[i][0]) : ele.text(self.label_value[i][1])
436+ });
437+ },
438+ parse_value: function(val, def) {
439+ if (this.widget) this.widget='float'
440+ return instance.web.parse_value(val, this, def);
441+ },
442+ format_value: function(val, def) {
443+ if (this.widget) this.widget='float'
444+ return instance.web.format_value(val, this, def);
445+ },
446+
447+});
448+instance.web.form.widgets = instance.web.form.widgets.extend({
449+'mrp_product_qty': 'instance.web.form.mrp_product_qty',
450+});
451+}
452\ No newline at end of file
453
454=== added file 'mrp/test/disassemble_process.yml'
455--- mrp/test/disassemble_process.yml 1970-01-01 00:00:00 +0000
456+++ mrp/test/disassemble_process.yml 2014-05-23 07:38:59 +0000
457@@ -0,0 +1,96 @@
458+-
459+ MRP user can doing all process related to Production Order, so let's check data with giving the access rights of user.
460+-
461+ !context
462+ uid: 'res_users_mrp_user'
463+-
464+ In order to test Disassemble feature in OpenERP we will create a Production order with negative quantity for PC Assemble SC349
465+-
466+ !record {model: mrp.production, id: mrp_production_test2}:
467+ product_id: product.product_product_4
468+ product_qty: -5.0
469+ location_src_id: stock.stock_location_14
470+ location_dest_id: stock.stock_location_output
471+ bom_id: mrp_bom_24
472+ routing_id: mrp.mrp_routing_1
473+-
474+ I compute the production order.
475+-
476+ !python {model: mrp.production}: |
477+ order = self.browse(cr, uid, ref("mrp_production_test2"), context=context)
478+ order.action_compute(context=context)
479+-
480+ I check production lines after compute.
481+-
482+ !python {model: mrp.production}: |
483+ order = self.browse(cr, uid, ref("mrp_production_test2"), context=context)
484+ assert len(order.product_lines) == 5, "Production lines are not generated proper."
485+-
486+ Now I check workcenter lines.
487+-
488+ !python {model: mrp.production}: |
489+ from openerp.tools import float_compare
490+ def assert_equals(value1, value2, msg, float_compare=float_compare):
491+ assert float_compare(value1, value2, precision_digits=2) == 0, msg
492+ order = self.browse(cr, uid, ref("mrp_production_test2"), context=context)
493+ assert len(order.workcenter_lines), "Workcenter lines are not generated proper."
494+-
495+ I confirm the Production Order.
496+-
497+ !workflow {model: mrp.production, action: button_confirm, ref: mrp_production_test2}
498+-
499+ I check details of Produce Move of Production Order to trace Final Product.
500+-
501+ !python {model: mrp.production}: |
502+ order = self.browse(cr, uid, ref("mrp_production_test2"))
503+ assert order.state == 'ready', "Production order should be ready."
504+ assert order.move_created_ids, "Trace Record is not created for Final Product."
505+ move = order.move_created_ids[0]
506+ source_location_id = order.product_id.property_stock_production.id
507+ assert move.date == order.date_planned, "Planned date is not correspond."
508+ assert move.product_id.id == order.product_id.id, "Product is not correspond."
509+ assert move.product_uom.id == order.product_uom.id, "UOM is not correspond."
510+ assert move.product_qty == abs(order.product_qty), "Qty is not correspond."
511+ assert move.product_uos_qty == order.product_uos and order.product_uos_qty or abs(order.product_qty), "UOS qty is not correspond."
512+ if order.product_uos:
513+ assert move.product_uos.id == order.product_uos.id, "UOS is not correspond."
514+ assert move.location_id.id == order.location_dest_id.id, "Source Location is not correspond."
515+ assert move.location_dest_id.id == source_location_id, "Destination Location is not correspond."
516+ routing_loc = None
517+ if order.bom_id.routing_id and order.bom_id.routing_id.location_id:
518+ routing_loc = order.bom_id.routing_id.location_id.id
519+ date_planned = order.date_planned
520+ for move_line in order.move_lines:
521+ for order_line in order.product_lines:
522+ if move_line.product_id.type not in ('product', 'consu'):
523+ continue
524+ if move_line.product_id.id == order_line.product_id.id:
525+ assert move_line.date == date_planned, "Planned date is not correspond in 'To consume line'."
526+ assert move_line.product_qty == order_line.product_qty, "Qty is not correspond in 'To consume line'."
527+ assert move_line.product_uom.id == order_line.product_uom.id, "UOM is not correspond in 'To consume line'."
528+ 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'."
529+ if order_line.product_uos:
530+ assert move_line.product_uos.id == order_line.product_uos.id, "UOS is not correspond in 'To consume line'."
531+ assert move_line.location_id.id == source_location_id, "Source location is not correspond in 'To consume line'."
532+ assert move_line.location_dest_id.id == routing_loc or order.location_src_id.id, "Destination Location is not correspond in 'To consume line'."
533+-
534+ I check that production order in ready state.
535+-
536+ !python {model: mrp.production}: |
537+ order = self.browse(cr, uid, ref("mrp_production_test2"))
538+ assert order.state == 'ready', 'Production order should be in Ready State.'
539+
540+-
541+ I check that production order in production state after start production.
542+-
543+ !python {model: mrp.production}: |
544+ order = self.browse(cr, uid, ref("mrp_production_test2"))
545+ context.update({'active_id': ref('mrp_production_test2')})
546+ assert self.action_produce(cr, uid, order.id , 5.0, 'consume_produce'), 'Can not do action produce.'
547+
548+-
549+ I check that production order in Done state.
550+-
551+ !python {model: mrp.production}: |
552+ order = self.browse(cr, uid, ref("mrp_production_test2"))
553+ assert order.state == 'done', 'Production order should be in Done State.'
554
555=== modified file 'mrp/wizard/__init__.py'
556--- mrp/wizard/__init__.py 2014-01-28 15:18:34 +0000
557+++ mrp/wizard/__init__.py 2014-05-23 07:38:59 +0000
558@@ -24,6 +24,7 @@
559 import mrp_workcenter_load
560 import change_production_qty
561 import stock_move
562+import disassemble_qty
563 #import mrp_change_standard_price
564
565 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
566
567=== modified file 'mrp/wizard/change_production_qty.py'
568--- mrp/wizard/change_production_qty.py 2013-11-29 16:56:44 +0000
569+++ mrp/wizard/change_production_qty.py 2014-05-23 07:38:59 +0000
570@@ -71,7 +71,7 @@
571 move_obj = self.pool.get('stock.move')
572 for wiz_qty in self.browse(cr, uid, ids, context=context):
573 prod = prod_obj.browse(cr, uid, record_id, context=context)
574- prod_obj.write(cr, uid, [prod.id], {'product_qty': wiz_qty.product_qty})
575+ prod_obj.write(cr, uid, [prod.id], {'product_qty': wiz_qty.product_qty, 'qty_to_disassemble': wiz_qty.product_qty, 'disassemble': wiz_qty.product_qty < 0 and True or False}, context=context)
576 prod_obj.action_compute(cr, uid, [prod.id])
577
578 for move in prod.move_lines:
579
580=== added file 'mrp/wizard/disassemble_qty.py'
581--- mrp/wizard/disassemble_qty.py 1970-01-01 00:00:00 +0000
582+++ mrp/wizard/disassemble_qty.py 2014-05-23 07:38:59 +0000
583@@ -0,0 +1,63 @@
584+# -*- coding: utf-8 -*-
585+##############################################################################
586+#
587+# OpenERP, Open Source Management Solution
588+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
589+#
590+# This program is free software: you can redistribute it and/or modify
591+# it under the terms of the GNU Affero General Public License as
592+# published by the Free Software Foundation, either version 3 of the
593+# License, or (at your option) any later version.
594+#
595+# This program is distributed in the hope that it will be useful,
596+# but WITHOUT ANY WARRANTY; without even the implied warranty of
597+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
598+# GNU Affero General Public License for more details.
599+#
600+# You should have received a copy of the GNU Affero General Public License
601+# along with this program. If not, see <http://www.gnu.org/licenses/>.
602+#
603+##############################################################################
604+
605+from openerp.osv import fields, osv
606+from openerp.tools.translate import _
607+import openerp.addons.decimal_precision as dp
608+
609+class change_disassemble_qty(osv.osv_memory):
610+ _name = 'change.disassemble.qty'
611+ _description = 'Change Quantity for Disassemble Products'
612+
613+ _columns = {
614+ 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
615+ }
616+
617+ def default_get(self, cr, uid, fields, context=None):
618+ """ To get default values for the object.
619+ @param self: The object pointer.
620+ @param cr: A database cursor
621+ @param uid: ID of the user currently logged in
622+ @param fields: List of fields for which we want default values
623+ @param context: A standard dictionary
624+ @return: A dictionary which of fields with values.
625+ """
626+ if context is None:
627+ context = {}
628+ res = super(change_disassemble_qty, self).default_get(cr, uid, fields, context=context)
629+ prod = self.pool.get('mrp.production').browse(cr, uid, context.get('active_id'), context=context)
630+ if 'product_qty' in fields:
631+ res.update({'product_qty': prod.qty_to_disassemble * -1 })
632+ return res
633+
634+ def change_disassemble_qty(self, cr, uid, ids, context=None):
635+ mrp_production_obj = self.pool.get('mrp.production')
636+ mrp_id = context.get('active_id', False)
637+ qty = self.browse(cr, uid, ids[0], context=context).product_qty
638+ if qty >= 0:
639+ raise osv.except_osv(_('Warning!'), _('Quantity must be negative to disassemble.'))
640+ mrp_record = mrp_production_obj.browse(cr, uid, mrp_id, context=context)
641+ mrp_record.write({'qty_to_disassemble': mrp_record.qty_to_disassemble - abs(qty)}, context=context)
642+ if mrp_record.qty_to_disassemble < abs(qty) :
643+ raise osv.except_osv(_('Warning!'), _('You are going to disassemble total %s quantities of "%s".\nBut you can only disassemble up to total %s quantities.') % (abs(qty), mrp_record.product_id.name, mrp_record.qty_to_disassemble))
644+ return mrp_production_obj.action_disassemble(cr, uid, mrp_id, qty, context=context)
645+
646+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
647\ No newline at end of file
648
649=== added file 'mrp/wizard/disassemble_qty_view.xml'
650--- mrp/wizard/disassemble_qty_view.xml 1970-01-01 00:00:00 +0000
651+++ mrp/wizard/disassemble_qty_view.xml 2014-05-23 07:38:59 +0000
652@@ -0,0 +1,36 @@
653+<?xml version="1.0" encoding="utf-8"?>
654+<openerp>
655+ <data>
656+
657+ <!-- Change Product Quantity for Disassemble Order-->
658+ <record id="view_change_disassemble_qty_wizard" model="ir.ui.view">
659+ <field name="name">Change Disassemble Order Qty</field>
660+ <field name="model">change.disassemble.qty</field>
661+ <field name="arch" type="xml">
662+ <form string="Change Disassemble Order Quantity in Negative" version="7.0">
663+ <newline/>
664+ <group col="7">
665+ <field name="product_qty" />
666+ <label string="Use negative quantity for disassemble order" colspan="5" class="oe_left oe_grey" style="margin-top: 5px;"/>
667+ </group>
668+ <footer>
669+ <button name="change_disassemble_qty" string="Create"
670+ colspan="1" type="object" class="oe_highlight" />
671+ or
672+ <button string="Cancel" class="oe_link" special="cancel" />
673+ </footer>
674+ </form>
675+ </field>
676+ </record>
677+
678+ <record id="action_change_disassemble_qty" model="ir.actions.act_window">
679+ <field name="name">Select Quantity to Disassemble</field>
680+ <field name="type">ir.actions.act_window</field>
681+ <field name="res_model">change.disassemble.qty</field>
682+ <field name="view_type">form</field>
683+ <field name="view_mode">form</field>
684+ <field name="target">new</field>
685+ </record>
686+
687+ </data>
688+</openerp>
689\ No newline at end of file
690
691=== modified file 'mrp/wizard/mrp_product_produce.py'
692--- mrp/wizard/mrp_product_produce.py 2014-05-07 16:59:51 +0000
693+++ mrp/wizard/mrp_product_produce.py 2014-05-23 07:38:59 +0000
694@@ -21,7 +21,8 @@
695
696 from openerp.osv import fields, osv
697 import openerp.addons.decimal_precision as dp
698-
699+from lxml import etree
700+from openerp.tools.translate import _
701
702 class mrp_product_produce_line(osv.osv_memory):
703 _name="mrp.product.produce.line"
704@@ -71,6 +72,17 @@
705 new_consume_lines.append([0, False, consume])
706 return {'value': {'consume_lines': new_consume_lines}}
707
708+ def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
709+ if context is None:
710+ context = {}
711+ res = super(mrp_product_produce,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
712+ doc = etree.XML(res['arch'])
713+ mrp_production = self.pool.get('mrp.production').browse(cr, uid, context.get('active_id'), context=context)
714+ if view_type == 'form' and mrp_production.disassemble == True:
715+ for node in doc.xpath("//group[@name='produce']"):
716+ node.set('string',_('Product to Disassemble'))
717+ res['arch'] = etree.tostring(doc)
718+ return res
719
720 def _get_product_qty(self, cr, uid, context=None):
721 """ To obtain product quantity
722@@ -89,7 +101,7 @@
723 if move.product_id == prod.product_id:
724 if not move.scrapped:
725 done += move.product_qty
726- return (prod.product_qty - done) or prod.product_qty
727+ return (abs(prod.product_qty) - done) or abs(prod.product_qty)
728
729 def _get_product_id(self, cr, uid, context=None):
730 """ To obtain product id
731
732=== modified file 'mrp/wizard/mrp_product_produce_view.xml'
733--- mrp/wizard/mrp_product_produce_view.xml 2014-02-05 10:43:24 +0000
734+++ mrp/wizard/mrp_product_produce_view.xml 2014-05-23 07:38:59 +0000
735@@ -10,7 +10,7 @@
736 <field name="model">mrp.product.produce</field>
737 <field name="arch" type="xml">
738 <form string="Produce" version="7.0">
739- <group string="Produce">
740+ <group name="produce" string="Produce">
741 <field name="mode"/>
742 <field name="product_qty" colspan="2" on_change="on_change_qty(product_qty, consume_lines, context)"/>
743 <field name="product_id" invisible="1"/>
744@@ -47,8 +47,16 @@
745 <field name="view_type">form</field>
746 <field name="view_mode">form</field>
747 <field name="target">new</field>
748- </record>
749-
750-
751+ </record>
752+
753+ <record id="act_mrp_disassemble_product" model="ir.actions.act_window">
754+ <field name="name">Disassemble</field>
755+ <field name="type">ir.actions.act_window</field>
756+ <field name="res_model">mrp.product.produce</field>
757+ <field name="view_type">form</field>
758+ <field name="view_mode">form</field>
759+ <field name="target">new</field>
760+ </record>
761+
762 </data>
763 </openerp>
764
765=== modified file 'mrp_byproduct/mrp_byproduct.py'
766--- mrp_byproduct/mrp_byproduct.py 2014-04-22 12:16:25 +0000
767+++ mrp_byproduct/mrp_byproduct.py 2014-05-23 07:38:59 +0000
768@@ -102,6 +102,8 @@
769 qty1 *= product_uom_factor / (production.bom_id.product_qty or 1.0)
770 if production.product_uos_qty:
771 qty2 *= product_uos_factor / (production.bom_id.product_uos_qty or 1.0)
772+ if production.disassemble:
773+ continue
774 data = {
775 'name': 'PROD:'+production.name,
776 'date': production.date_planned,
777@@ -110,8 +112,8 @@
778 'product_uom': sub_product.product_uom.id,
779 'product_uos_qty': qty2,
780 'product_uos': production.product_uos and production.product_uos.id or False,
781- 'location_id': source,
782- 'location_dest_id': production.location_dest_id.id,
783+ 'location_id': source if not production.disassemble else production.location_dest_id.id,
784+ 'location_dest_id': production.location_dest_id.id if not production.disassemble else source,
785 'move_dest_id': production.move_prod_id.id,
786 'state': 'waiting',
787 'production_id': production.id
788
789=== modified file 'mrp_operations/mrp_operations.py'
790--- mrp_operations/mrp_operations.py 2014-04-23 09:48:07 +0000
791+++ mrp_operations/mrp_operations.py 2014-05-23 07:38:59 +0000
792@@ -139,7 +139,8 @@
793 if flag:
794 for production in prod_obj_pool.browse(cr, uid, [prod_obj.id], context= None):
795 if production.move_lines or production.move_created_ids:
796- prod_obj_pool.action_produce(cr,uid, production.id, production.product_qty, 'consume_produce', context = None)
797+ product_qty = production.disassemble and abs(production.product_qty) or production.product_qty
798+ prod_obj_pool.action_produce(cr,uid, production.id, product_qty, 'consume_produce', context = None)
799 prod_obj_pool.signal_button_produce_done(cr, uid, [oper_obj.production_id.id])
800 return
801

Subscribers

People subscribed via source and target branches

to all changes: