Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(269)

Delta Between Two Patch Sets: production.py

Issue 4306055: New production module (Closed)
Left Patch Set: Update to tip and add workflow Created 13 years ago
Right Patch Set: To be sure Created 12 years, 11 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « product.xml ('k') | production.xml » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 #This file is part of Tryton. The COPYRIGHT file at the top level of 1 #This file is part of Tryton. The COPYRIGHT file at the top level of
2 #this repository contains the full copyright notices and license terms. 2 #this repository contains the full copyright notices and license terms.
3 from decimal import Decimal
4
3 from trytond.model import ModelView, ModelSQL, Workflow, fields 5 from trytond.model import ModelView, ModelSQL, Workflow, fields
4 from trytond.wizard import Wizard, StateTransition, StateView, Button 6 from trytond.wizard import Wizard, StateTransition, StateView, Button
5 from trytond.pyson import Eval, Bool, If, Id 7 from trytond.pyson import Eval, Bool, If, Id
6 from trytond.pool import Pool 8 from trytond.pool import Pool
7 from trytond.transaction import Transaction 9 from trytond.transaction import Transaction
8 10
9 BOM_CHANGES = ['bom', 'product', 'quantity', 'uom', 'warehouse', 'location', 11 BOM_CHANGES = ['bom', 'product', 'quantity', 'uom', 'warehouse', 'location',
10 'company', 'inputs', 'outputs'] 12 'company', 'inputs', 'outputs']
11 13
12 14
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
89 on_change_with=['uom']), 'get_unit_digits') 91 on_change_with=['uom']), 'get_unit_digits')
90 quantity = fields.Float('Quantity', 92 quantity = fields.Float('Quantity',
91 digits=(16, Eval('unit_digits', 2)), 93 digits=(16, Eval('unit_digits', 2)),
92 states={ 94 states={
93 'readonly': ~Eval('state').in_(['request', 'draft']), 95 'readonly': ~Eval('state').in_(['request', 'draft']),
94 'required': Bool(Eval('bom')), 96 'required': Bool(Eval('bom')),
95 'invisible': ~Eval('bom'), 97 'invisible': ~Eval('bom'),
96 }, 98 },
97 on_change=BOM_CHANGES, 99 on_change=BOM_CHANGES,
98 depends=['unit_digits']) 100 depends=['unit_digits'])
101 cost = fields.Function(fields.Numeric('Cost', digits=(16, 4),
102 readonly=True, on_change_with=['inputs']), 'get_cost')
99 inputs = fields.One2Many('stock.move', 'production_input', 'Inputs', 103 inputs = fields.One2Many('stock.move', 'production_input', 'Inputs',
100 domain=[ 104 domain=[
101 ('from_location', 'child_of', [Eval('warehouse')], 'parent'), 105 ('from_location', 'child_of', [Eval('warehouse')], 'parent'),
102 ('to_location', '=', Eval('location')), 106 ('to_location', '=', Eval('location')),
103 ], 107 ],
104 states={ 108 states={
105 'readonly': ~Eval('state').in_(['request', 'draft', 'waiting']), 109 'readonly': (~Eval('state').in_(['request', 'draft', 'waiting'])
110 | ~Eval('location')),
106 }, 111 },
107 depends=['warehouse', 'location']) 112 depends=['warehouse', 'location'])
108 outputs = fields.One2Many('stock.move', 'production_output', 'Outputs', 113 outputs = fields.One2Many('stock.move', 'production_output', 'Outputs',
109 domain=[ 114 domain=[
110 ('from_location', '=', Eval('location')), 115 ('from_location', '=', Eval('location')),
111 ('to_location', 'child_of', [Eval('warehouse')], 'parent'), 116 ('to_location', 'child_of', [Eval('warehouse')], 'parent'),
112 ], 117 ],
113 states={ 118 states={
114 'readonly': Eval('state').in_(['done', 'cancel']), 119 'readonly': (Eval('state').in_(['done', 'cancel'])
120 | ~Eval('location')),
115 }, 121 },
116 depends=['warehouse', 'location']) 122 depends=['warehouse', 'location'])
117 state = fields.Selection([ 123 state = fields.Selection([
118 ('request', 'Request'), 124 ('request', 'Request'),
119 ('draft', 'Draft'), 125 ('draft', 'Draft'),
120 ('waiting', 'Waiting'), 126 ('waiting', 'Waiting'),
121 ('assigned', 'Assigned'), 127 ('assigned', 'Assigned'),
122 ('running', 'Running'), 128 ('running', 'Running'),
123 ('done', 'Done'), 129 ('done', 'Done'),
124 ('cancel', 'Canceled'), 130 ('cancel', 'Canceled'),
125 ], 'State', readonly=True) 131 ], 'State', readonly=True)
126 132
127 def __init__(self): 133 def __init__(self):
128 super(Production, self).__init__() 134 super(Production, self).__init__()
135 self._constraints += [
136 ('check_cost', 'missing_cost'),
137 ]
138 self._error_messages.update({
139 'missing_cost': 'It misses some cost on the outputs!',
140 })
129 self._transitions |= set(( 141 self._transitions |= set((
130 ('request', 'draft'), 142 ('request', 'draft'),
131 ('draft', 'waiting'), 143 ('draft', 'waiting'),
132 ('waiting', 'assigned'), 144 ('waiting', 'assigned'),
133 ('assigned', 'running'), 145 ('assigned', 'running'),
134 ('running', 'done'), 146 ('running', 'done'),
135 ('running', 'waiting'), 147 ('running', 'waiting'),
136 ('assigned', 'waiting'), 148 ('assigned', 'waiting'),
137 ('waiting', 'waiting'), 149 ('waiting', 'waiting'),
138 ('waiting', 'draft'), 150 ('waiting', 'draft'),
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
176 }) 188 })
177 189
178 def default_state(self): 190 def default_state(self):
179 return 'draft' 191 return 'draft'
180 192
181 def default_warehouse(self): 193 def default_warehouse(self):
182 location_obj = Pool().get('stock.location') 194 location_obj = Pool().get('stock.location')
183 location_ids = location_obj.search(self.warehouse.domain) 195 location_ids = location_obj.search(self.warehouse.domain)
184 if len(location_ids) == 1: 196 if len(location_ids) == 1:
185 return location_ids[0] 197 return location_ids[0]
186 return False
187 198
188 def default_location(self): 199 def default_location(self):
189 location_obj = Pool().get('stock.location') 200 location_obj = Pool().get('stock.location')
190 warehouse_id = self.default_warehouse() 201 warehouse_id = self.default_warehouse()
191 if warehouse_id: 202 if warehouse_id:
192 warehouse = location_obj.browse(warehouse_id) 203 warehouse = location_obj.browse(warehouse_id)
193 return warehouse.production_location.id 204 return warehouse.production_location.id
194 return False
195 205
196 def default_company(self): 206 def default_company(self):
197 return Transaction().context.get('company') or False 207 return Transaction().context.get('company')
198 208
199 def _move_values(self, from_location, to_location, company, bom_io, 209 def _move_values(self, from_location, to_location, company, product, uom,
200 quantity): 210 quantity):
201 values = { 211 values = {
202 'product': bom_io.product.id, 212 'product': product.id,
203 'product.rec_name': bom_io.product.rec_name, 213 'uom': uom.id,
204 'uom': bom_io.uom.id,
205 'uom.rec_name': bom_io.uom.rec_name,
206 'quantity': quantity, 214 'quantity': quantity,
207 'from_location': None, 215 'from_location': None,
208 'to_location': None, 216 'to_location': None,
209 'company': None, 217 'company': None,
210 'state': 'draft', 218 'state': 'draft',
211 } 219 }
220 values['currency'] = company.currency.id if company else None
212 if from_location: 221 if from_location:
213 values['from_location'] = from_location.id 222 values['from_location'] = from_location.id
223 if to_location:
224 values['to_location'] = to_location.id
225 if company:
226 values['company'] = company.id
227 return values
228
229 def _explode_move_values(self, from_location, to_location, company,
230 bom_io, quantity):
231 pool = Pool()
232 move_obj = pool.get('stock.move')
233
234 values = self._move_values(from_location, to_location, company,
235 bom_io.product, bom_io.uom, quantity)
236 values['product.rec_name'] = bom_io.product.rec_name
237 values['uom.rec_name'] = bom_io.uom.rec_name
238 values['unit_price_required'] = \
239 move_obj.on_change_with_unit_price_required({
240 'from_location': (from_location.id if from_location else
241 None),
242 'to_location': to_location.id if to_location else None,
243 })
244 if from_location:
214 values['from_location.rec_name'] = from_location.rec_name 245 values['from_location.rec_name'] = from_location.rec_name
215 if to_location: 246 if to_location:
216 values['to_location'] = to_location.id
217 values['to_location.rec_name'] = to_location.rec_name 247 values['to_location.rec_name'] = to_location.rec_name
218 if company: 248 if company:
219 values['company'] = company.id
220 values['company.rec_name'] = company.rec_name 249 values['company.rec_name'] = company.rec_name
221 return values 250 return values
222 251
223 def explode_bom(self, values): 252 def explode_bom(self, values):
224 pool = Pool() 253 pool = Pool()
225 bom_obj = pool.get('production.bom') 254 bom_obj = pool.get('production.bom')
226 product_obj = pool.get('product.product') 255 product_obj = pool.get('product.product')
227 uom_obj = pool.get('product.uom') 256 uom_obj = pool.get('product.uom')
228 input_obj = pool.get('production.bom.input') 257 input_obj = pool.get('production.bom.input')
229 output_obj = pool.get('production.bom.output') 258 output_obj = pool.get('production.bom.output')
230 location_obj = pool.get('stock.location') 259 location_obj = pool.get('stock.location')
231 company_obj = pool.get('company.company') 260 company_obj = pool.get('company.company')
232 261
233 if not (values.get('bom') 262 if not (values.get('bom')
234 and values.get('product') 263 and values.get('product')
235 and values.get('uom')): 264 and values.get('uom')):
236 return {} 265 return {}
237 inputs = { 266 inputs = {
238 'remove': [r['id'] for r in values.get('inputs') or []], 267 'remove': [r['id'] for r in values.get('inputs') or []],
239 'add': [], 268 'add': [],
240 } 269 }
241 outputs = { 270 outputs = {
242 'remove': [r['id'] for r in values.get('outputs') or []], 271 'remove': [r['id'] for r in values.get('outputs') or []],
243 'add': [], 272 'add': [],
244 } 273 }
245 changes = { 274 changes = {
246 'inputs': inputs, 275 'inputs': inputs,
247 'outputs': outputs, 276 'outputs': outputs,
277 'cost': Decimal(0),
248 } 278 }
249 279
250 bom = bom_obj.browse(values['bom']) 280 bom = bom_obj.browse(values['bom'])
251 product = product_obj.browse(values['product']) 281 product = product_obj.browse(values['product'])
252 quantity = values.get('quantity') or 0 282 quantity = values.get('quantity') or 0
253 uom = uom_obj.browse(values['uom']) 283 uom = uom_obj.browse(values['uom'])
254 if values.get('warehouse'): 284 if values.get('warehouse'):
255 warehouse = location_obj.browse(values['warehouse']) 285 warehouse = location_obj.browse(values['warehouse'])
256 storage_location = warehouse.storage_location 286 storage_location = warehouse.storage_location
257 else: 287 else:
258 storage_location = None 288 storage_location = None
259 if values.get('location'): 289 if values.get('location'):
260 location = location_obj.browse(values['location']) 290 location = location_obj.browse(values['location'])
261 else: 291 else:
262 location = None 292 location = None
263 if values.get('company'): 293 if values.get('company'):
264 company = company_obj.browse(values['company']) 294 company = company_obj.browse(values['company'])
265 else: 295 else:
266 company = None 296 company = None
267 297
268 factor = bom_obj.compute_factor(bom, product, quantity, uom) 298 factor = bom_obj.compute_factor(bom, product, quantity, uom)
269 for input_ in bom.inputs: 299 for input_ in bom.inputs:
270 quantity = input_obj.compute_quantity(input_, factor) 300 quantity = input_obj.compute_quantity(input_, factor)
271 values = self._move_values(storage_location, location, company, 301 values = self._explode_move_values(storage_location, location,
272 input_, quantity) 302 company, input_, quantity)
273 if values: 303 if values:
274 inputs['add'].append(values) 304 inputs['add'].append(values)
305 quantity = uom_obj.compute_qty(input_.uom, quantity,
306 input_.product.default_uom)
307 changes['cost'] += (Decimal(str(quantity)) *
308 input_.product.cost_price)
275 309
276 for output in bom.outputs: 310 for output in bom.outputs:
277 quantity = output_obj.compute_quantity(output, factor) 311 quantity = output_obj.compute_quantity(output, factor)
278 values = self._move_values(location, storage_location, company, 312 values = self._explode_move_values(location, storage_location,
279 output, quantity) 313 company, output, quantity)
280 if values: 314 if values:
315 values['unit_price'] = Decimal(0)
316 if output.product.id == values.get('product') and quantity:
317 values['unit_price'] = (changes['cost'] /
318 Decimal(str(quantity)))
281 outputs['add'].append(values) 319 outputs['add'].append(values)
282 return changes 320 return changes
283 321
284 def on_change_warehouse(self, values): 322 def on_change_warehouse(self, values):
285 location_obj = Pool().get('stock.location') 323 location_obj = Pool().get('stock.location')
286 changes = { 324 changes = {
287 'location': False, 325 'location': None,
288 } 326 }
289 if values.get('warehouse'): 327 if values.get('warehouse'):
290 warehouse = location_obj.browse(values['warehouse']) 328 warehouse = location_obj.browse(values['warehouse'])
291 changes['location'] = warehouse.production_location.id 329 changes['location'] = warehouse.production_location.id
292 return changes 330 return changes
293 331
294 def on_change_product(self, values): 332 def on_change_product(self, values):
295 product_obj = Pool().get('product.product') 333 product_obj = Pool().get('product.product')
296 334
297 result = {} 335 result = {}
298 if values.get('product'): 336 if values.get('product'):
299 product = product_obj.browse(values['product']) 337 product = product_obj.browse(values['product'])
300 uom_ids = [x.id for x in product.default_uom.category.uoms] 338 uom_ids = [x.id for x in product.default_uom.category.uoms]
301 if (not values.get('uom') 339 if (not values.get('uom')
302 or values.get('uom') not in uom_ids): 340 or values.get('uom') not in uom_ids):
303 result['uom'] = product.default_uom.id 341 result['uom'] = product.default_uom.id
304 result['uom.rec_name'] = product.default_uom.rec_name 342 result['uom.rec_name'] = product.default_uom.rec_name
305 result['unit_digits'] = product.default_uom.digits 343 result['unit_digits'] = product.default_uom.digits
306 else: 344 else:
307 result['uom'] = False 345 result['uom'] = None
308 result['uom.rec_name'] = '' 346 result['uom.rec_name'] = ''
309 result['unit_digits'] = 2 347 result['unit_digits'] = 2
310 348
311 values = values.copy() 349 values = values.copy()
312 values['uom'] = result['uom'] 350 values['uom'] = result['uom']
313 result.update(self.explode_bom(values)) 351 result.update(self.explode_bom(values))
314 return result 352 return result
315 353
316 def on_change_with_uom_category(self, values): 354 def on_change_with_uom_category(self, values):
317 product_obj = Pool().get('product.product') 355 product_obj = Pool().get('product.product')
318 if values.get('product'): 356 if values.get('product'):
319 product = product_obj.browse(values['product']) 357 product = product_obj.browse(values['product'])
320 return product.default_uom.category.id 358 return product.default_uom.category.id
321 return False
322 359
323 def get_uom_category(self, ids, name): 360 def get_uom_category(self, ids, name):
324 res = {} 361 res = {}
325 for input in self.browse(ids): 362 for production in self.browse(ids):
326 res[input.id] = input.product.default_uom.category.id 363 if production.product:
364 res[production.id] = production.product.default_uom.category.id
365 else:
366 res[production.id] = None
327 return res 367 return res
328 368
329 def on_change_with_unit_digits(self, values): 369 def on_change_with_unit_digits(self, values):
330 uom_obj = Pool().get('product.uom') 370 uom_obj = Pool().get('product.uom')
331 if values.get('uom'): 371 if values.get('uom'):
332 uom = uom_obj.browse(values['uom']) 372 uom = uom_obj.browse(values['uom'])
333 return uom.digits 373 return uom.digits
334 return 2 374 return 2
335 375
336 def get_unit_digits(self, ids, name): 376 def get_unit_digits(self, ids, name):
337 digits = {} 377 digits = {}
338 for production in self.browse(ids): 378 for production in self.browse(ids):
339 digits[production.id] = production.uom.digits 379 if production.uom:
380 digits[production.id] = production.uom.digits
381 else:
382 digits[production.id] = 2
340 return digits 383 return digits
341 384
342 def on_change_bom(self, values): 385 def on_change_bom(self, values):
343 return self.explode_bom(values) 386 return self.explode_bom(values)
344 387
345 def on_change_uom(self, values): 388 def on_change_uom(self, values):
346 return self.explode_bom(values) 389 return self.explode_bom(values)
347 390
348 def on_change_quantity(self, values): 391 def on_change_quantity(self, values):
349 return self.explode_bom(values) 392 return self.explode_bom(values)
393
394 def get_cost(self, ids, name):
395 costs = {}
396 for production in self.browse(ids):
397 costs[production.id] = Decimal(0)
398 for input_ in production.inputs:
399 if input_.cost_price is not None:
400 cost_price = input_.cost_price
401 else:
402 cost_price = input_.product.cost_price
403 costs[production.id] += (Decimal(str(input_.internal_quantity))
404 * cost_price)
405 return costs
406
407 def on_change_with_cost(self, values):
408 pool = Pool()
409 product_obj = pool.get('product.product')
410 uom_obj = pool.get('product.uom')
411
412 cost = Decimal(0)
413 if not values.get('inputs'):
414 return cost
415
416 product_ids = list(set(r['product'] for r in values['inputs'] if
417 r['product'] is not None))
418 id2product = dict((p.id, p) for p in product_obj.browse(product_ids))
419
420 uom_ids = list(set(r['uom'] for r in values['inputs']))
421 id2uom = dict((u.id, u) for u in uom_obj.browse(uom_ids))
422
423 for input_ in values['inputs']:
424 if (input_['product'] is None
425 or input_['uom'] is None
426 or input_['quantity'] is None):
427 continue
428 product = id2product[input_['product']]
429 quantity = uom_obj.compute_qty(id2uom[input_['uom']],
430 input_['quantity'], product.default_uom)
431 cost += Decimal(str(quantity)) * product.cost_price
432 return cost
433
434 def set_moves(self, production):
435 pool = Pool()
436 bom_obj = pool.get('production.bom')
437 move_obj = pool.get('stock.move')
438 input_obj = pool.get('production.bom.input')
439 output_obj = pool.get('production.bom.output')
440
441 storage_location = production.warehouse.storage_location
442 location = production.location
443 company = production.company
444
445 if not production.bom:
446 if production.product:
447 product = production.product
448 values = self._move_values(location, storage_location, company,
449 product, product.default_uom)
450 if values:
451 values['production_output'] = production.id
452 move_obj.create(values)
453 return
454
455 factor = bom_obj.compute_factor(production.bom, production.product,
456 production.quantity, production.uom)
457 cost = Decimal(0)
458 for input_ in production.bom.inputs:
459 quantity = input_obj.compute_quantity(input_, factor)
460 product = input_.product
461 values = self._move_values(storage_location, location, company,
462 product, product.default_uom, quantity)
463 if values:
464 values['production_input'] = production.id
465 move_obj.create(values)
466 cost += Decimal(str(quantity)) * product.cost_price
467
468 for output in production.bom.outputs:
469 quantity = output_obj.compute_quantity(output, factor)
470 product = output.product
471 values = self._move_values(location, storage_location, company,
472 product, product.default_uom, quantity)
473 if values:
474 values['production_output'] = production.id
475 if product == production.product:
476 values['unit_price'] = cost / Decimal(str(quantity))
477 move_obj.create(values)
478 self._set_move_planned_date([production.id])
479
480 def check_cost(self, ids):
481 pool = Pool()
482 currency_obj = pool.get('currency.currency')
483
484 for production in self.browse(ids):
485 if production.state != 'done':
486 continue
487 cost_price = Decimal(0)
488 for output in production.outputs:
489 cost_price += (Decimal(str(output.quantity))
490 * output.unit_price)
491 if not currency_obj.is_zero(production.company.currency,
492 production.cost - cost_price):
493 return False
494 return True
350 495
351 def create(self, values): 496 def create(self, values):
352 sequence_obj = Pool().get('ir.sequence') 497 sequence_obj = Pool().get('ir.sequence')
353 config_obj = Pool().get('production.configuration') 498 config_obj = Pool().get('production.configuration')
354 499
355 values = values.copy() 500 values = values.copy()
356 config = config_obj.browse(1) 501 config = config_obj.browse(1)
357 values['code'] = sequence_obj.get_id(config.production_sequence.id) 502 values['code'] = sequence_obj.get_id(config.production_sequence.id)
358 production_id = super(Production, self).create(values) 503 production_id = super(Production, self).create(values)
359 self._set_move_planned_date(production_id) 504 self._set_move_planned_date(production_id)
(...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after
526 return 'failed' 671 return 'failed'
527 672
528 def transition_force(self, session): 673 def transition_force(self, session):
529 pool = Pool() 674 pool = Pool()
530 production_obj = pool.get('production') 675 production_obj = pool.get('production')
531 676
532 production_obj.assign_force([Transaction().context['active_id']]) 677 production_obj.assign_force([Transaction().context['active_id']])
533 return 'end' 678 return 'end'
534 679
535 Assign() 680 Assign()
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b