LEFT | RIGHT |
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 trytond.model import ModelView, ModelSQL, fields | 3 from decimal import Decimal |
4 from trytond.pyson import Eval, Bool | 4 |
| 5 from trytond.model import ModelView, ModelSQL, Workflow, fields |
| 6 from trytond.wizard import Wizard, StateTransition, StateView, Button |
| 7 from trytond.pyson import Eval, Bool, If, Id |
5 from trytond.pool import Pool | 8 from trytond.pool import Pool |
6 from trytond.transaction import Transaction | 9 from trytond.transaction import Transaction |
7 | 10 |
8 BOM_CHANGES = ['bom', 'product', 'quantity', 'uom', 'warehouse', 'location', | 11 BOM_CHANGES = ['bom', 'product', 'quantity', 'uom', 'warehouse', 'location', |
9 'company', 'inputs', 'outputs'] | 12 'company', 'inputs', 'outputs'] |
10 | 13 |
11 | 14 |
12 class Production(ModelSQL, ModelView): | 15 class Production(Workflow, ModelSQL, ModelView): |
13 "Production" | 16 "Production" |
14 _name = 'production' | 17 _name = 'production' |
15 _description = __doc__ | 18 _description = __doc__ |
16 _rec_name = 'code' | 19 _rec_name = 'code' |
17 | 20 |
18 code = fields.Char('Code', select=True, readonly=True) | 21 code = fields.Char('Code', select=True, readonly=True) |
19 reference = fields.Char('Reference', select=1, | 22 reference = fields.Char('Reference', select=1, |
20 states={ | 23 states={ |
21 'readonly': ~Eval('state').in_(['request', 'draft']), | 24 'readonly': ~Eval('state').in_(['request', 'draft']), |
22 }, | 25 }, |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
88 on_change_with=['uom']), 'get_unit_digits') | 91 on_change_with=['uom']), 'get_unit_digits') |
89 quantity = fields.Float('Quantity', | 92 quantity = fields.Float('Quantity', |
90 digits=(16, Eval('unit_digits', 2)), | 93 digits=(16, Eval('unit_digits', 2)), |
91 states={ | 94 states={ |
92 'readonly': ~Eval('state').in_(['request', 'draft']), | 95 'readonly': ~Eval('state').in_(['request', 'draft']), |
93 'required': Bool(Eval('bom')), | 96 'required': Bool(Eval('bom')), |
94 'invisible': ~Eval('bom'), | 97 'invisible': ~Eval('bom'), |
95 }, | 98 }, |
96 on_change=BOM_CHANGES, | 99 on_change=BOM_CHANGES, |
97 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') |
98 inputs = fields.One2Many('stock.move', 'production_input', 'Inputs', | 103 inputs = fields.One2Many('stock.move', 'production_input', 'Inputs', |
99 domain=[ | 104 domain=[ |
100 ('from_location', 'child_of', [Eval('warehouse')], 'parent'), | 105 ('from_location', 'child_of', [Eval('warehouse')], 'parent'), |
101 ('to_location', '=', Eval('location')), | 106 ('to_location', '=', Eval('location')), |
102 ], | 107 ], |
| 108 states={ |
| 109 'readonly': (~Eval('state').in_(['request', 'draft', 'waiting']) |
| 110 | ~Eval('location')), |
| 111 }, |
103 depends=['warehouse', 'location']) | 112 depends=['warehouse', 'location']) |
104 outputs = fields.One2Many('stock.move', 'production_output', 'Outputs', | 113 outputs = fields.One2Many('stock.move', 'production_output', 'Outputs', |
105 domain=[ | 114 domain=[ |
106 ('from_location', '=', Eval('location')), | 115 ('from_location', '=', Eval('location')), |
107 ('to_location', 'child_of', [Eval('warehouse')], 'parent'), | 116 ('to_location', 'child_of', [Eval('warehouse')], 'parent'), |
108 ], | 117 ], |
| 118 states={ |
| 119 'readonly': (Eval('state').in_(['done', 'cancel']) |
| 120 | ~Eval('location')), |
| 121 }, |
109 depends=['warehouse', 'location']) | 122 depends=['warehouse', 'location']) |
110 state = fields.Selection([ | 123 state = fields.Selection([ |
111 ('request', 'Request'), | 124 ('request', 'Request'), |
112 ('draft', 'Draft'), | 125 ('draft', 'Draft'), |
| 126 ('waiting', 'Waiting'), |
113 ('assigned', 'Assigned'), | 127 ('assigned', 'Assigned'), |
114 ('running', 'Running'), | 128 ('running', 'Running'), |
115 ('done', 'Done'), | 129 ('done', 'Done'), |
116 ('cancel', 'Canceled'), | 130 ('cancel', 'Canceled'), |
117 ], 'State', readonly=True) | 131 ], 'State', readonly=True) |
118 | 132 |
| 133 def __init__(self): |
| 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 }) |
| 141 self._transitions |= set(( |
| 142 ('request', 'draft'), |
| 143 ('draft', 'waiting'), |
| 144 ('waiting', 'assigned'), |
| 145 ('assigned', 'running'), |
| 146 ('running', 'done'), |
| 147 ('running', 'waiting'), |
| 148 ('assigned', 'waiting'), |
| 149 ('waiting', 'waiting'), |
| 150 ('waiting', 'draft'), |
| 151 ('request', 'cancel'), |
| 152 ('draft', 'cancel'), |
| 153 ('waiting', 'cancel'), |
| 154 ('assigned', 'cancel'), |
| 155 ('cancel', 'draft'), |
| 156 )) |
| 157 self._buttons.update({ |
| 158 'cancel': { |
| 159 'invisible': ~Eval('state').in_(['request', 'draft', |
| 160 'assigned']), |
| 161 }, |
| 162 'draft': { |
| 163 'invisible': ~Eval('state').in_(['request', 'waiting', |
| 164 'cancel']), |
| 165 'icon': If(Eval('state') == 'cancel', |
| 166 'tryton-clear', |
| 167 If(Eval('state') == 'request', |
| 168 'tryton-go-next', |
| 169 'tryton-go-previous')), |
| 170 }, |
| 171 'wait': { |
| 172 'invisible': ~Eval('state').in_(['draft', 'assigned', |
| 173 'waiting', 'running']), |
| 174 'icon': If(Eval('state').in_(['assigned', 'running']), |
| 175 'tryton-go-previous', |
| 176 If(Eval('state') == 'waiting', |
| 177 'tryton-clear', |
| 178 'tryton-go-next')), |
| 179 }, |
| 180 'run': { |
| 181 'invisible': Eval('state') != 'assigned', |
| 182 }, |
| 183 'done': { |
| 184 'invisible': Eval('state') != 'running', |
| 185 }, |
| 186 'assign_try': {}, |
| 187 'assign_force': {}, |
| 188 }) |
| 189 |
119 def default_state(self): | 190 def default_state(self): |
120 return 'draft' | 191 return 'draft' |
121 | 192 |
122 def default_warehouse(self): | 193 def default_warehouse(self): |
123 location_obj = Pool().get('stock.location') | 194 location_obj = Pool().get('stock.location') |
124 location_ids = location_obj.search(self.warehouse.domain) | 195 location_ids = location_obj.search(self.warehouse.domain) |
125 if len(location_ids) == 1: | 196 if len(location_ids) == 1: |
126 return location_ids[0] | 197 return location_ids[0] |
127 return False | |
128 | 198 |
129 def default_location(self): | 199 def default_location(self): |
130 location_obj = Pool().get('stock.location') | 200 location_obj = Pool().get('stock.location') |
131 warehouse_id = self.default_warehouse() | 201 warehouse_id = self.default_warehouse() |
132 if warehouse_id: | 202 if warehouse_id: |
133 warehouse = location_obj.browse(warehouse_id) | 203 warehouse = location_obj.browse(warehouse_id) |
134 return warehouse.production_location.id | 204 return warehouse.production_location.id |
135 return False | |
136 | 205 |
137 def default_company(self): | 206 def default_company(self): |
138 return Transaction().context.get('company') or False | 207 return Transaction().context.get('company') |
139 | 208 |
140 def _move_values(self, from_location, to_location, company, bom_io, | 209 def _move_values(self, from_location, to_location, company, product, uom, |
141 quantity): | 210 quantity): |
142 values = { | 211 values = { |
143 'product': bom_io.product.id, | 212 'product': product.id, |
144 'product.rec_name': bom_io.product.rec_name, | 213 'uom': uom.id, |
145 'uom': bom_io.uom.id, | |
146 'uom.rec_name': bom_io.uom.rec_name, | |
147 'quantity': quantity, | 214 'quantity': quantity, |
148 'from_location': None, | 215 'from_location': None, |
149 'to_location': None, | 216 'to_location': None, |
150 'company': None, | 217 'company': None, |
| 218 'state': 'draft', |
151 } | 219 } |
| 220 values['currency'] = company.currency.id if company else None |
152 if from_location: | 221 if from_location: |
153 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: |
154 values['from_location.rec_name'] = from_location.rec_name | 245 values['from_location.rec_name'] = from_location.rec_name |
155 if to_location: | 246 if to_location: |
156 values['to_location'] = to_location.id | |
157 values['to_location.rec_name'] = to_location.rec_name | 247 values['to_location.rec_name'] = to_location.rec_name |
158 if company: | 248 if company: |
159 values['company'] = company.id, | |
160 values['company.rec_name'] = company.rec_name | 249 values['company.rec_name'] = company.rec_name |
161 return values | 250 return values |
162 | 251 |
163 def explode_bom(self, values): | 252 def explode_bom(self, values): |
164 pool = Pool() | 253 pool = Pool() |
165 bom_obj = pool.get('production.bom') | 254 bom_obj = pool.get('production.bom') |
166 product_obj = pool.get('product.product') | 255 product_obj = pool.get('product.product') |
167 uom_obj = pool.get('product.uom') | 256 uom_obj = pool.get('product.uom') |
168 input_obj = pool.get('production.bom.input') | 257 input_obj = pool.get('production.bom.input') |
169 output_obj = pool.get('production.bom.output') | 258 output_obj = pool.get('production.bom.output') |
170 location_obj = pool.get('stock.location') | 259 location_obj = pool.get('stock.location') |
171 company_obj = pool.get('company.company') | 260 company_obj = pool.get('company.company') |
172 | 261 |
173 if not (values.get('bom') | 262 if not (values.get('bom') |
174 and values.get('product') | 263 and values.get('product') |
175 and values.get('uom')): | 264 and values.get('uom')): |
176 return {} | 265 return {} |
177 inputs = { | 266 inputs = { |
178 'remove': [r['id'] for r in values.get('inputs') or []], | 267 'remove': [r['id'] for r in values.get('inputs') or []], |
179 'add': [], | 268 'add': [], |
180 } | 269 } |
181 outputs = { | 270 outputs = { |
182 'remove': [r['id'] for r in values.get('outputs') or []], | 271 'remove': [r['id'] for r in values.get('outputs') or []], |
183 'add': [], | 272 'add': [], |
184 } | 273 } |
185 changes = { | 274 changes = { |
186 'inputs': inputs, | 275 'inputs': inputs, |
187 'outputs': outputs, | 276 'outputs': outputs, |
| 277 'cost': Decimal(0), |
188 } | 278 } |
189 | 279 |
190 bom = bom_obj.browse(values['bom']) | 280 bom = bom_obj.browse(values['bom']) |
191 product = product_obj.browse(values['product']) | 281 product = product_obj.browse(values['product']) |
192 quantity = values.get('quantity') or 0 | 282 quantity = values.get('quantity') or 0 |
193 uom = uom_obj.browse(values['uom']) | 283 uom = uom_obj.browse(values['uom']) |
194 if values.get('warehouse'): | 284 if values.get('warehouse'): |
195 warehouse = location_obj.browse(values['warehouse']) | 285 warehouse = location_obj.browse(values['warehouse']) |
196 storage_location = warehouse.storage_location | 286 storage_location = warehouse.storage_location |
197 else: | 287 else: |
198 storage_location = None | 288 storage_location = None |
199 if values.get('location'): | 289 if values.get('location'): |
200 location = location_obj.browse(values['location']) | 290 location = location_obj.browse(values['location']) |
201 else: | 291 else: |
202 location = None | 292 location = None |
203 if values.get('company'): | 293 if values.get('company'): |
204 company = company_obj.browse(values['company']) | 294 company = company_obj.browse(values['company']) |
205 else: | 295 else: |
206 company = None | 296 company = None |
207 | 297 |
208 factor = bom_obj.compute_factor(bom, product, quantity, uom) | 298 factor = bom_obj.compute_factor(bom, product, quantity, uom) |
209 for input_ in bom.inputs: | 299 for input_ in bom.inputs: |
210 quantity = input_obj.compute_quantity(input_, factor) | 300 quantity = input_obj.compute_quantity(input_, factor) |
211 values = self._move_values(storage_location, location, company, | 301 values = self._explode_move_values(storage_location, location, |
212 input_, quantity) | 302 company, input_, quantity) |
213 if values: | 303 if values: |
214 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) |
215 | 309 |
216 for output in bom.outputs: | 310 for output in bom.outputs: |
217 quantity = output_obj.compute_quantity(output, factor) | 311 quantity = output_obj.compute_quantity(output, factor) |
218 values = self._move_values(location, storage_location, company, | 312 values = self._explode_move_values(location, storage_location, |
219 output, quantity) | 313 company, output, quantity) |
220 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))) |
221 outputs['add'].append(values) | 319 outputs['add'].append(values) |
222 return changes | 320 return changes |
223 | 321 |
224 def on_change_warehouse(self, values): | 322 def on_change_warehouse(self, values): |
225 location_obj = Pool().get('stock.location') | 323 location_obj = Pool().get('stock.location') |
226 changes = { | 324 changes = { |
227 'location': False, | 325 'location': None, |
228 } | 326 } |
229 if values.get('warehouse'): | 327 if values.get('warehouse'): |
230 warehouse = location_obj.browse(values['warehouse']) | 328 warehouse = location_obj.browse(values['warehouse']) |
231 changes['location'] = warehouse.production_location.id | 329 changes['location'] = warehouse.production_location.id |
232 return changes | 330 return changes |
233 | 331 |
234 def on_change_product(self, values): | 332 def on_change_product(self, values): |
235 product_obj = Pool().get('product.product') | 333 product_obj = Pool().get('product.product') |
236 | 334 |
237 result = {} | 335 result = {} |
238 if values.get('product'): | 336 if values.get('product'): |
239 product = product_obj.browse(values['product']) | 337 product = product_obj.browse(values['product']) |
240 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] |
241 if (not values.get('uom') | 339 if (not values.get('uom') |
242 or values.get('uom') not in uom_ids): | 340 or values.get('uom') not in uom_ids): |
243 result['uom'] = product.default_uom.id | 341 result['uom'] = product.default_uom.id |
244 result['uom.rec_name'] = product.default_uom.rec_name | 342 result['uom.rec_name'] = product.default_uom.rec_name |
245 result['unit_digits'] = product.default_uom.digits | 343 result['unit_digits'] = product.default_uom.digits |
246 else: | 344 else: |
247 result['uom'] = False | 345 result['uom'] = None |
248 result['uom.rec_name'] = '' | 346 result['uom.rec_name'] = '' |
249 result['unit_digits'] = 2 | 347 result['unit_digits'] = 2 |
250 | 348 |
251 values = values.copy() | 349 values = values.copy() |
252 values['uom'] = result['uom'] | 350 values['uom'] = result['uom'] |
253 result.update(self.explode_bom(values)) | 351 result.update(self.explode_bom(values)) |
254 return result | 352 return result |
255 | 353 |
256 def on_change_with_uom_category(self, values): | 354 def on_change_with_uom_category(self, values): |
257 product_obj = Pool().get('product.product') | 355 product_obj = Pool().get('product.product') |
258 if values.get('product'): | 356 if values.get('product'): |
259 product = product_obj.browse(values['product']) | 357 product = product_obj.browse(values['product']) |
260 return product.default_uom.category.id | 358 return product.default_uom.category.id |
261 return False | |
262 | 359 |
263 def get_uom_category(self, ids, name): | 360 def get_uom_category(self, ids, name): |
264 res = {} | 361 res = {} |
265 for input in self.browse(ids): | 362 for production in self.browse(ids): |
266 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 |
267 return res | 367 return res |
268 | 368 |
269 def on_change_with_unit_digits(self, values): | 369 def on_change_with_unit_digits(self, values): |
270 uom_obj = Pool().get('product.uom') | 370 uom_obj = Pool().get('product.uom') |
271 if values.get('uom'): | 371 if values.get('uom'): |
272 uom = uom_obj.browse(values['uom']) | 372 uom = uom_obj.browse(values['uom']) |
273 return uom.digits | 373 return uom.digits |
274 return 2 | 374 return 2 |
275 | 375 |
276 def get_unit_digits(self, ids, name): | 376 def get_unit_digits(self, ids, name): |
277 digits = {} | 377 digits = {} |
278 for production in self.browse(ids): | 378 for production in self.browse(ids): |
279 digits[production.id] = production.uom.digits | 379 if production.uom: |
| 380 digits[production.id] = production.uom.digits |
| 381 else: |
| 382 digits[production.id] = 2 |
280 return digits | 383 return digits |
281 | 384 |
282 def on_change_bom(self, values): | 385 def on_change_bom(self, values): |
283 return self.explode_bom(values) | 386 return self.explode_bom(values) |
284 | 387 |
285 def on_change_uom(self, values): | 388 def on_change_uom(self, values): |
286 return self.explode_bom(values) | 389 return self.explode_bom(values) |
287 | 390 |
288 def on_change_quantity(self, values): | 391 def on_change_quantity(self, values): |
289 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 |
290 | 495 |
291 def create(self, values): | 496 def create(self, values): |
292 sequence_obj = Pool().get('ir.sequence') | 497 sequence_obj = Pool().get('ir.sequence') |
293 config_obj = Pool().get('production.configuration') | 498 config_obj = Pool().get('production.configuration') |
294 | 499 |
295 values = values.copy() | 500 values = values.copy() |
296 config = config_obj.browse(1) | 501 config = config_obj.browse(1) |
297 values['code'] = sequence_obj.get_id(config.production_sequence.id) | 502 values['code'] = sequence_obj.get_id(config.production_sequence.id) |
298 production_id = super(Production, self).create(values) | 503 production_id = super(Production, self).create(values) |
299 # call set_move_planned_date | 504 self._set_move_planned_date(production_id) |
300 return production_id | 505 return production_id |
301 | 506 |
302 def write(self, ids, values): | 507 def write(self, ids, values): |
303 result = super(Production, self).write(ids, values) | 508 result = super(Production, self).write(ids, values) |
304 # call set_move_planned_date | 509 self._set_move_planned_date(ids) |
305 return result | 510 return result |
306 | 511 |
| 512 def _get_move_planned_date(self, production): |
| 513 "Return the planned dates for input and output moves" |
| 514 return production.planned_date, production.planned_date |
| 515 |
| 516 def _set_move_planned_date(self, ids): |
| 517 "Set planned date of moves for the shipments" |
| 518 pool = Pool() |
| 519 move_obj = pool.get('stock.move') |
| 520 if isinstance(ids, (int, long)): |
| 521 ids = [ids] |
| 522 for production in self.browse(ids): |
| 523 dates = self._get_move_planned_date(production) |
| 524 input_date, output_date = dates |
| 525 move_obj.write([m.id for m in production.inputs |
| 526 if m.state not in ('assigned', 'done', 'cancel')], { |
| 527 'planned_date': input_date, |
| 528 }) |
| 529 move_obj.write([m.id for m in production.outputs |
| 530 if m.state not in ('assigned', 'done', 'cancel')], { |
| 531 'planned_date': output_date, |
| 532 }) |
| 533 |
| 534 @ModelView.button |
| 535 @Workflow.transition('cancel') |
| 536 def cancel(self, ids): |
| 537 pool = Pool() |
| 538 move_obj = pool.get('stock.move') |
| 539 productions = self.browse(ids) |
| 540 move_obj.write([m.id for p in productions |
| 541 for m in p.inputs + p.outputs |
| 542 if m.state != 'cancel'], { |
| 543 'state': 'cancel', |
| 544 }) |
| 545 |
| 546 @ModelView.button |
| 547 @Workflow.transition('draft') |
| 548 def draft(self, ids): |
| 549 pool = Pool() |
| 550 move_obj = pool.get('stock.move') |
| 551 productions = self.browse(ids) |
| 552 move_obj.write([m.id for p in productions |
| 553 for m in p.inputs + p.outputs |
| 554 if m.state != 'draft'], { |
| 555 'state': 'draft', |
| 556 }) |
| 557 |
| 558 @ModelView.button |
| 559 @Workflow.transition('waiting') |
| 560 def wait(self, ids): |
| 561 pool = Pool() |
| 562 move_obj = pool.get('stock.move') |
| 563 productions = self.browse(ids) |
| 564 move_obj.write([m.id for p in productions |
| 565 for m in p.inputs + p.outputs |
| 566 if m.state not in ('draft', 'done')], { |
| 567 'state': 'draft', |
| 568 }) |
| 569 |
| 570 @Workflow.transition('assigned') |
| 571 def assign(self, ids): |
| 572 pass |
| 573 |
| 574 @ModelView.button |
| 575 @Workflow.transition('running') |
| 576 def run(self, ids): |
| 577 pool = Pool() |
| 578 move_obj = pool.get('stock.move') |
| 579 productions = self.browse(ids) |
| 580 move_obj.write([m.id for p in productions |
| 581 for m in p.inputs |
| 582 if m.state not in ('done', 'cancel')], { |
| 583 'state': 'done', |
| 584 }) |
| 585 |
| 586 @ModelView.button |
| 587 @Workflow.transition('done') |
| 588 def done(self, ids): |
| 589 pool = Pool() |
| 590 move_obj = pool.get('stock.move') |
| 591 date_obj = pool.get('ir.date') |
| 592 productions = self.browse(ids) |
| 593 move_obj.write([m.id for p in productions |
| 594 for m in p.outputs |
| 595 if m.state not in ('done', 'cancel')], { |
| 596 'state': 'done', |
| 597 }) |
| 598 self.write(ids, { |
| 599 'effective_date': date_obj.today(), |
| 600 }) |
| 601 |
| 602 @ModelView.button |
| 603 def assign_try(self, ids): |
| 604 pool = Pool() |
| 605 move_obj = pool.get('stock.move') |
| 606 productions = self.browse(ids) |
| 607 if move_obj.assign_try([m for p in productions |
| 608 for m in p.inputs]): |
| 609 self.assign(ids) |
| 610 return True |
| 611 else: |
| 612 return False |
| 613 |
| 614 @ModelView.button |
| 615 def assign_force(self, ids): |
| 616 pool = Pool() |
| 617 move_obj = pool.get('stock.move') |
| 618 productions = self.browse(ids) |
| 619 move_obj.write([m.id for p in productions for m in p.inputs |
| 620 if m.state != 'done'], { |
| 621 'state': 'assigned', |
| 622 }) |
| 623 self.assign(ids) |
| 624 |
307 Production() | 625 Production() |
| 626 |
| 627 |
| 628 class AssignFailed(ModelView): |
| 629 'Assign Production' |
| 630 _name = 'production.assign.failed' |
| 631 _description = __doc__ |
| 632 |
| 633 moves = fields.Many2Many('stock.move', None, None, 'Moves', readonly=True) |
| 634 |
| 635 def default_moves(self): |
| 636 pool = Pool() |
| 637 production_obj = pool.get('production') |
| 638 production_id = Transaction().context.get('active_id') |
| 639 if not production_id: |
| 640 return [] |
| 641 production = production_obj.browse(production_id) |
| 642 return [m.id for m in production.inputs if m.state == 'draft'] |
| 643 |
| 644 AssignFailed() |
| 645 |
| 646 |
| 647 class Assign(Wizard): |
| 648 'Assign Production' |
| 649 _name = 'production.assign' |
| 650 |
| 651 start = StateTransition() |
| 652 failed = StateView('production.assign.failed', |
| 653 'production.assign_failed_view_form', [ |
| 654 Button('Force Assign', 'force', 'tryton-go-next', |
| 655 states={ |
| 656 'invisible': ~Id('stock', |
| 657 'group_stock_force_assignment').in_( |
| 658 Eval('context', {}).get('groups', [])), |
| 659 }), |
| 660 Button('Ok', 'end', 'tryton-ok', True), |
| 661 ]) |
| 662 force = StateTransition() |
| 663 |
| 664 def transition_start(self, session): |
| 665 pool = Pool() |
| 666 production_obj = pool.get('production') |
| 667 |
| 668 if production_obj.assign_try([Transaction().context['active_id']]): |
| 669 return 'end' |
| 670 else: |
| 671 return 'failed' |
| 672 |
| 673 def transition_force(self, session): |
| 674 pool = Pool() |
| 675 production_obj = pool.get('production') |
| 676 |
| 677 production_obj.assign_force([Transaction().context['active_id']]) |
| 678 return 'end' |
| 679 |
| 680 Assign() |
LEFT | RIGHT |