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 trytond.model import ModelView, ModelSQL, fields |
4 from trytond.wizard import Wizard | 4 from trytond.wizard import Wizard, StateView, Button |
| 5 from trytond.transaction import Transaction |
5 from trytond.pyson import Eval | 6 from trytond.pyson import Eval |
| 7 from trytond.pool import Pool |
6 | 8 |
7 | 9 |
8 class BOM(ModelSQL, ModelView): | 10 class BOM(ModelSQL, ModelView): |
9 "Bill of Material" | 11 "Bill of Material" |
10 _name = 'production.bom' | 12 _name = 'production.bom' |
11 _description = __doc__ | 13 _description = __doc__ |
12 | 14 |
13 name = fields.Char('Name', required=True, translate=True) | 15 name = fields.Char('Name', required=True, translate=True) |
14 active = fields.Boolean('Active', select=1) | 16 active = fields.Boolean('Active', select=1) |
15 inputs = fields.One2Many('production.bom.input', 'bom', 'Inputs') | 17 inputs = fields.One2Many('production.bom.input', 'bom', 'Inputs') |
16 outputs = fields.One2Many('production.bom.output', 'bom', 'Outputs') | 18 outputs = fields.One2Many('production.bom.output', 'bom', 'Outputs') |
17 output_products = fields.Many2Many('production.bom.output', | 19 output_products = fields.Many2Many('production.bom.output', |
18 'bom', 'product', 'Output Products') | 20 'bom', 'product', 'Output Products') |
19 | 21 |
20 def default_active(self): | 22 def default_active(self): |
21 return True | 23 return True |
22 | 24 |
23 def compute_factor(self, bom, product, quantity, uom): | 25 def compute_factor(self, bom, product, quantity, uom): |
24 ''' | 26 ''' |
25 Compute factor for an output product | 27 Compute factor for an output product |
26 ''' | 28 ''' |
27 uom_obj = self.pool.get('product.uom') | 29 uom_obj = Pool().get('product.uom') |
28 for output in bom.outputs: | 30 for output in bom.outputs: |
29 if output.product == product: | 31 if output.product == product: |
30 quantity = uom_obj.compute_qty(uom, quantity, | 32 quantity = uom_obj.compute_qty(uom, quantity, |
31 output.uom, round=False) | 33 output.uom, round=False) |
32 return quantity / output.quantity | 34 return quantity / output.quantity |
| 35 |
| 36 def copy(self, ids, default=None): |
| 37 if default is None: |
| 38 default = {} |
| 39 else: |
| 40 default = default.copy() |
| 41 default['output_products'] = False |
| 42 return super(BOM, self).copy(ids, default=default) |
33 | 43 |
34 BOM() | 44 BOM() |
35 | 45 |
36 | 46 |
37 class BOMInput(ModelSQL, ModelView): | 47 class BOMInput(ModelSQL, ModelView): |
38 "Bill of Material Input" | 48 "Bill of Material Input" |
39 _name = 'production.bom.input' | 49 _name = 'production.bom.input' |
40 _description = __doc__ | 50 _description = __doc__ |
41 _rec_name = 'product' | 51 _rec_name = 'product' |
42 | 52 |
(...skipping 15 matching lines...) Expand all Loading... |
58 quantity = fields.Float('Quantity', required=True, | 68 quantity = fields.Float('Quantity', required=True, |
59 digits=(16, Eval('unit_digits', 2)), | 69 digits=(16, Eval('unit_digits', 2)), |
60 depends=['unit_digits']) | 70 depends=['unit_digits']) |
61 | 71 |
62 def __init__(self): | 72 def __init__(self): |
63 super(BOMInput, self).__init__() | 73 super(BOMInput, self).__init__() |
64 self._sql_constraints = [ | 74 self._sql_constraints = [ |
65 ('product_bom_uniq', 'UNIQUE(product, bom)', | 75 ('product_bom_uniq', 'UNIQUE(product, bom)', |
66 'product_bom_uniq'), | 76 'product_bom_uniq'), |
67 ] | 77 ] |
| 78 self._constraints += [ |
| 79 ('check_bom_recursion', 'recursive_bom'), |
| 80 ] |
68 self._error_messages.update({ | 81 self._error_messages.update({ |
69 'product_bom_uniq': 'Product must be unique per BOM!', | 82 'product_bom_uniq': 'Product must be unique per BOM!', |
| 83 'recursive_bom': 'You can not create recursive BOMs!', |
70 }) | 84 }) |
71 | 85 |
72 def on_change_product(self, vals): | 86 def on_change_product(self, vals): |
73 product_obj = self.pool.get('product.product') | 87 product_obj = Pool().get('product.product') |
74 | 88 |
75 res = {} | 89 res = {} |
76 if vals.get('product'): | 90 if vals.get('product'): |
77 product = product_obj.browse(vals['product']) | 91 product = product_obj.browse(vals['product']) |
78 uom_ids = [x.id for x in product.default_uom.category.uoms] | 92 uom_ids = [x.id for x in product.default_uom.category.uoms] |
79 if (not vals.get('uom') | 93 if (not vals.get('uom') |
80 or vals.get('uom') not in uom_ids): | 94 or vals.get('uom') not in uom_ids): |
81 res['uom'] = product.default_uom.id | 95 res['uom'] = product.default_uom.id |
82 res['uom.rec_name'] = product.default_uom.rec_name | 96 res['uom.rec_name'] = product.default_uom.rec_name |
83 res['unit_digits'] = product.default_uom.digits | 97 res['unit_digits'] = product.default_uom.digits |
84 else: | 98 else: |
85 res['uom'] = False | 99 res['uom'] = False |
86 res['uom.rec_name'] = '' | 100 res['uom.rec_name'] = '' |
87 res['unit_digits'] = 2 | 101 res['unit_digits'] = 2 |
88 return res | 102 return res |
89 | 103 |
90 def on_change_with_uom_category(self, vals): | 104 def on_change_with_uom_category(self, vals): |
91 product_obj = self.pool.get('product.product') | 105 product_obj = Pool().get('product.product') |
92 if vals.get('product'): | 106 if vals.get('product'): |
93 product = product_obj.browse(vals['product']) | 107 product = product_obj.browse(vals['product']) |
94 return product.default_uom.category.id | 108 return product.default_uom.category.id |
95 return False | 109 return False |
96 | 110 |
97 def get_uom_category(self, ids, name): | 111 def get_uom_category(self, ids, name): |
98 res = {} | 112 res = {} |
99 for input in self.browse(ids): | 113 for input in self.browse(ids): |
100 res[input.id] = input.product.default_uom.category.id | 114 res[input.id] = input.product.default_uom.category.id |
101 return res | 115 return res |
102 | 116 |
103 def on_change_with_unit_digits(self, vals): | 117 def on_change_with_unit_digits(self, vals): |
104 uom_obj = self.pool.get('product.uom') | 118 uom_obj = Pool().get('product.uom') |
105 if vals.get('uom'): | 119 if vals.get('uom'): |
106 uom = uom_obj.browse(vals['uom']) | 120 uom = uom_obj.browse(vals['uom']) |
107 return uom.digits | 121 return uom.digits |
108 return 2 | 122 return 2 |
109 | 123 |
110 def get_unit_digits(self, ids, name): | 124 def get_unit_digits(self, ids, name): |
111 res = {} | 125 res = {} |
112 for input in self.browse(ids): | 126 for input in self.browse(ids): |
113 res[input.id] = input.uom.digits | 127 res[input.id] = input.uom.digits |
114 return res | 128 return res |
115 | 129 |
| 130 def check_bom_recursion(self, ids): |
| 131 ''' |
| 132 Check BOM recursion |
| 133 ''' |
| 134 product_obj = Pool().get('product.product') |
| 135 |
| 136 inputs = self.browse(ids) |
| 137 product_ids = [input.product.id for input in inputs] |
| 138 return product_obj.check_bom_recursion(product_ids) |
| 139 |
116 def compute_quantity(self, line, factor): | 140 def compute_quantity(self, line, factor): |
117 uom_obj = self.pool.get('product.uom') | 141 uom_obj = Pool().get('product.uom') |
118 return uom_obj.round(line.quantity * factor, line.uom.rounding) | 142 return uom_obj.round(line.quantity * factor, line.uom.rounding) |
119 | 143 |
120 BOMInput() | 144 BOMInput() |
121 | 145 |
122 | 146 |
123 class BOMOutput(BOMInput): | 147 class BOMOutput(BOMInput): |
124 "Bill of Material OutPut" | 148 "Bill of Material Output" |
125 _name = 'production.bom.output' | 149 _name = 'production.bom.output' |
126 _description = __doc__ | 150 _description = __doc__ |
127 | 151 |
128 BOMOutput() | 152 BOMOutput() |
129 | 153 |
130 | 154 |
131 class BOMTree(ModelView): | 155 class BOMTree(ModelView): |
132 'BOM Tree' | 156 'BOM Tree' |
133 _name = 'production.bom.tree' | 157 _name = 'production.bom.tree' |
134 _description = __doc__ | 158 _description = __doc__ |
135 | 159 |
136 product = fields.Many2One('product.product', 'Product') | 160 product = fields.Many2One('product.product', 'Product') |
137 quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)), | 161 quantity = fields.Float('Quantity', digits=(16, Eval('unit_digits', 2)), |
138 depends=['unit_digits']) | 162 depends=['unit_digits']) |
139 uom = fields.Many2One('product.uom', 'Uom') | 163 uom = fields.Many2One('product.uom', 'Uom') |
140 unit_digits = fields.Integer('Unit Digits') | 164 unit_digits = fields.Integer('Unit Digits') |
141 childs = fields.One2Many('production.bom.tree', None, 'Childs') | 165 childs = fields.One2Many('production.bom.tree', None, 'Childs') |
142 | 166 |
143 def tree(self, product, quantity, uom, bom=None): | 167 def tree(self, product, quantity, uom, bom=None): |
144 bom_obj = self.pool.get('production.bom') | 168 bom_obj = Pool().get('production.bom') |
145 input_obj = self.pool.get('production.bom.input') | 169 input_obj = Pool().get('production.bom.input') |
146 | 170 |
147 result = [] | 171 result = [] |
148 if bom is None: | 172 if bom is None: |
149 if not product.boms: | 173 if not product.boms: |
150 return result | 174 return result |
151 bom = product.boms[0].bom | 175 bom = product.boms[0].bom |
152 | 176 |
153 factor = bom_obj.compute_factor(bom, product, quantity, uom) | 177 factor = bom_obj.compute_factor(bom, product, quantity, uom) |
154 for input in bom.inputs: | 178 for input in bom.inputs: |
155 quantity = input_obj.compute_quantity(input, factor) | 179 quantity = input_obj.compute_quantity(input, factor) |
156 childs = self.tree(input.product, quantity, input.uom) | 180 childs = self.tree(input.product, quantity, input.uom) |
157 values = { | 181 values = { |
158 'product': input.product.id, | 182 'product': input.product.id, |
159 'quantity': quantity, | 183 'quantity': quantity, |
160 'uom': input.uom.id, | 184 'uom': input.uom.id, |
161 'unit_digits': input.uom.digits, | 185 'unit_digits': input.uom.digits, |
162 'childs': childs, | 186 'childs': childs, |
163 } | 187 } |
164 result.append(values) | 188 result.append(values) |
165 return result | 189 return result |
166 | 190 |
167 BOMTree() | 191 BOMTree() |
168 | 192 |
169 | 193 |
170 class OpenBOMTreeInit(ModelView): | 194 class OpenBOMTreeStart(ModelView): |
171 'Open BOM Tree Init' | 195 'Open BOM Tree' |
172 _name = 'production.bom.tree.open.init' | 196 _name = 'production.bom.tree.open.start' |
173 | 197 |
174 quantity = fields.Float('Quantity', required=True) | 198 quantity = fields.Float('Quantity', required=True, |
| 199 digits=(16, Eval('unit_digits', 2)), depends=['unit_digits']) |
175 uom = fields.Many2One('product.uom', 'Unit', required=True, | 200 uom = fields.Many2One('product.uom', 'Unit', required=True, |
176 domain=[ | 201 domain=[ |
177 ('category', '=', Eval('category')), | 202 ('category', '=', Eval('category')), |
178 ], depends=['category']) | 203 ], depends=['category']) |
179 category = fields.Many2One('product.uom.category', 'Category') | 204 unit_digits = fields.Integer('Unit Digits', readonly=True, |
| 205 on_change_with=['uom']) |
| 206 category = fields.Many2One('product.uom.category', 'Category', |
| 207 readonly=True) |
180 bom = fields.Many2One('product.product-production.bom', | 208 bom = fields.Many2One('product.product-production.bom', |
181 'BOM', required=True, domain=[ | 209 'BOM', required=True, domain=[ |
182 ('product', '=', Eval('product')), | 210 ('product', '=', Eval('product')), |
183 ], depends=['product']) | 211 ], depends=['product']) |
184 product = fields.Many2One('product.product', 'Product') | 212 product = fields.Many2One('product.product', 'Product', readonly=True) |
185 | 213 |
186 OpenBOMTreeInit() | 214 def on_change_with_unit_digits(self, values): |
| 215 uom_obj = Pool().get('product.uom') |
| 216 if values.get('uom'): |
| 217 uom = uom_obj.browse(values['uom']) |
| 218 return uom.digits |
| 219 return 2 |
| 220 |
| 221 OpenBOMTreeStart() |
187 | 222 |
188 | 223 |
189 class OpenBOMTreeTree(ModelView): | 224 class OpenBOMTreeTree(ModelView): |
190 'Open BOM Tree Tree' | 225 'Open BOM Tree' |
191 _name = 'production.bom.tree.open.tree' | 226 _name = 'production.bom.tree.open.tree' |
192 | 227 |
193 bom_tree = fields.One2Many('production.bom.tree', None, 'BOM Tree') | 228 bom_tree = fields.One2Many('production.bom.tree', None, 'BOM Tree') |
194 | 229 |
195 def tree(self, bom_id, product_id, quantity, uom_id): | 230 def tree(self, bom, product, quantity, uom): |
196 tree_obj = self.pool.get('production.bom.tree') | 231 pool = Pool() |
197 bom_obj = self.pool.get('production.bom') | 232 tree_obj = pool.get('production.bom.tree') |
198 product_obj = self.pool.get('product.product') | 233 |
199 uom_obj = self.pool.get('product.uom') | |
200 | |
201 bom = bom_obj.browse(bom_id) | |
202 product = product_obj.browse(product_id) | |
203 uom = uom_obj.browse(uom_id) | |
204 childs = tree_obj.tree(product, quantity, uom, bom=bom) | 234 childs = tree_obj.tree(product, quantity, uom, bom=bom) |
205 bom_tree = [{ | 235 bom_tree = [{ |
206 'product': product_id, | 236 'product': product.id, |
207 'quantity': quantity, | 237 'quantity': quantity, |
208 'uom': uom_id, | 238 'uom': uom.id, |
209 'unit_digits': uom.digits, | 239 'unit_digits': uom.digits, |
210 'childs': childs, | 240 'childs': childs, |
211 }] | 241 }] |
212 return { | 242 return { |
213 'bom_tree': bom_tree, | 243 'bom_tree': bom_tree, |
214 } | 244 } |
215 | 245 |
216 OpenBOMTreeTree() | 246 OpenBOMTreeTree() |
217 | 247 |
218 | 248 |
219 class OpenBOMTree(Wizard): | 249 class OpenBOMTree(Wizard): |
220 'Open BOM Tree' | 250 'Open BOM Tree' |
221 _name = 'production.bom.tree.open' | 251 _name = 'production.bom.tree.open' |
222 | 252 |
223 states = { | 253 start = StateView('production.bom.tree.open.start', |
224 'init': { | 254 'production.bom_tree_open_start_view_form', [ |
225 'actions': ['_init'], | 255 Button('Cancel', 'end', 'tryton-cancel'), |
226 'result': { | 256 Button('Ok', 'tree', 'tryton-ok', True), |
227 'type': 'form', | 257 ]) |
228 'object': 'production.bom.tree.open.init', | 258 tree = StateView('production.bom.tree.open.tree', |
229 'state': [ | 259 'production.bom_tree_open_tree_view_form', [ |
230 ('end', 'Cancel', 'tryton-cancel'), | 260 Button('Change', 'start', 'tryton-go-previous'), |
231 ('tree', 'Ok', 'tryton-ok', True), | 261 Button('Close', 'end', 'tryton-close'), |
232 ], | 262 ]) |
233 }, | 263 |
234 }, | 264 def default_start(self, session, fields): |
235 'tree': { | 265 product_obj = Pool().get('product.product') |
236 'actions': ['_tree'], | 266 defaults = {} |
237 'result': { | 267 product = product_obj.browse(Transaction().context['active_id']) |
238 'type': 'form', | 268 defaults['category'] = product.default_uom.category.id |
239 'object': 'production.bom.tree.open.tree', | 269 if session.start.uom: |
240 'state': [ | 270 defaults['uom'] = session.start.uom.id |
241 ('init', 'Change', 'tryton-go-previous'), | 271 defaults['unit_digits'] = session.start.unit_digits |
242 ('end', 'Close', 'tryton-close', True), | 272 else: |
243 ], | 273 defaults['uom'] = product.default_uom.id |
244 }, | 274 defaults['unit_digits'] = product.default_uom.digits |
245 }, | 275 defaults['product'] = product.id |
246 } | 276 if session.start.bom: |
247 | 277 defaults['bom'] = session.start.bom.id |
248 def _init(self, data): | 278 elif product.boms: |
249 product_obj = self.pool.get('product.product') | 279 defaults['bom'] = product.boms[0].id |
250 result = {} | 280 defaults['quantity'] = session.start.quantity |
251 product = product_obj.browse(data['id']) | 281 return defaults |
252 result['category'] = product.default_uom.category.id | 282 |
253 result['uom'] = product.default_uom.id | 283 def default_tree(self, session, fields): |
254 result['product'] = product.id | 284 pool = Pool() |
255 if product.boms: | 285 bom_tree_obj = pool.get('production.bom.tree.open.tree') |
256 result['bom'] = product.boms[0].id | 286 return bom_tree_obj.tree(session.start.bom.bom, session.start.product, |
257 return result | 287 session.start.quantity, session.start.uom) |
258 | |
259 def _tree(self, data): | |
260 bom_tree_obj = self.pool.get('production.bom.tree.open.tree') | |
261 return bom_tree_obj.tree(data['form']['bom'], data['id'], | |
262 data['form']['quantity'], data['form']['uom']) | |
263 | 288 |
264 OpenBOMTree() | 289 OpenBOMTree() |
LEFT | RIGHT |