28
28
_name = 'product.pack.line'
29
29
_rec_name = 'product_id'
31
'parent_product_id': fields.many2one('product.product', 'Parent Product', ondelete='cascade', required=True),
31
'parent_product_id': fields.many2one(
32
'product.product', 'Parent Product',
33
ondelete='cascade', required=True
32
35
'quantity': fields.float('Quantity', required=True),
33
'product_id': fields.many2one('product.product', 'Product', required=True),
36
'product_id': fields.many2one(
37
'product.product', 'Product', required=True
36
42
class product_product(orm.Model):
37
43
_inherit = 'product.product'
39
'stock_depends': fields.boolean('Stock depends of components', help='Mark if pack stock is calcualted from component stock'),
40
'pack_fixed_price': fields.boolean('Pack has fixed price', help='Mark this field if the public price of the pack should be fixed. Do not mark it if the price should be calculated from the sum of the prices of the products in the pack.'),
41
'pack_line_ids': fields.one2many('product.pack.line','parent_product_id', 'Pack Products', help='List of products that are part of this pack.'),
45
'stock_depends': fields.boolean(
46
'Stock depends of components',
47
help='Mark if pack stock is calcualted from component stock'
49
'pack_fixed_price': fields.boolean(
50
'Pack has fixed price',
52
Mark this field if the public price of the pack should be fixed.
53
Do not mark it if the price should be calculated from the sum of
54
the prices of the products in the pack.
57
'pack_line_ids': fields.one2many(
58
'product.pack.line', 'parent_product_id', 'Pack Products',
59
help='List of products that are part of this pack.'
44
63
def get_product_available(self, cr, uid, ids, context=None):
45
""" Calulate stock for packs, return maximum stock that lets complete pack """
65
Calulate stock for packs
66
:return: maximum stock that lets complete pack
47
69
for product in self.browse(cr, uid, ids, context=context):
48
stock = super(product_product, self).get_product_available(cr, uid, [product.id], context=context)
70
stock = super(product_product, self).get_product_available(
71
cr, uid, [product.id], context=context)
50
73
# Check if product stock depends on it's subproducts stock.
51
74
if not product.stock_depends:
59
82
if product.pack_line_ids:
61
84
# Take the stock/virtual stock of all subproducts
62
subproducts_stock = self.get_product_available(cr, uid, [line.product_id.id for line in product.pack_line_ids], context=context)
85
subproducts_stock = self.get_product_available(
88
[line.product_id.id for line in product.pack_line_ids],
64
# Go over all subproducts, take quantity needed for the pack and its available stock
92
""" Go over all subproducts, take quantity needed for the pack
93
and its available stock """
65
94
for subproduct in product.pack_line_ids:
67
96
# if subproduct is a service don't calculate the stock
70
99
if first_subproduct:
71
100
subproduct_quantity = subproduct.quantity
72
subproduct_stock = subproducts_stock[subproduct.product_id.id]
102
subproducts_stock[subproduct.product_id.id])
73
103
if subproduct_quantity == 0:
76
# Calculate real stock for current pack from the subproduct stock and needed quantity
77
pack_stock = math.floor(subproduct_stock / subproduct_quantity)
106
""" Calculate real stock for current pack from the
107
subproduct stock and needed quantity """
108
pack_stock = math.floor(
109
subproduct_stock / subproduct_quantity)
78
110
first_subproduct = False
81
113
# Take the info of the next subproduct
82
114
subproduct_quantity_next = subproduct.quantity
83
subproduct_stock_next = subproducts_stock[subproduct.product_id.id]
84
if subproduct_quantity_next == 0 or subproduct_quantity_next == 0.0:
115
subproduct_stock_next = (
116
subproducts_stock[subproduct.product_id.id])
119
subproduct_quantity_next == 0
120
or subproduct_quantity_next == 0.0
86
pack_stock_next = math.floor(subproduct_stock_next / subproduct_quantity_next)
124
pack_stock_next = math.floor(
125
subproduct_stock_next / subproduct_quantity_next)
88
127
# compare the stock of a subproduct and the next subproduct
89
128
if pack_stock_next < pack_stock:
99
138
class sale_order_line(orm.Model):
100
139
_inherit = 'sale.order.line'
102
'pack_depth': fields.integer('Depth', required=True, help='Depth of the product if it is part of a pack.'),
103
'pack_parent_line_id': fields.many2one('sale.order.line', 'Pack', help='The pack that contains this product.'),
104
'pack_child_line_ids': fields.one2many('sale.order.line', 'pack_parent_line_id', 'Lines in pack', help=''),
141
'pack_depth': fields.integer(
142
'Depth', required=True,
143
help='Depth of the product if it is part of a pack.'
145
'pack_parent_line_id': fields.many2one(
146
'sale.order.line', 'Pack',
147
help='The pack that contains this product.'
149
'pack_child_line_ids': fields.one2many(
150
'sale.order.line', 'pack_parent_line_id', 'Lines in pack'),
107
153
'pack_depth': lambda *a: 0,
112
158
_inherit = 'sale.order'
114
160
def create(self, cr, uid, vals, context=None):
115
result = super(sale_order,self).create(cr, uid, vals, context)
161
result = super(sale_order, self).create(cr, uid, vals, context)
116
162
self.expand_packs(cr, uid, [result], context)
119
165
def write(self, cr, uid, ids, vals, context=None):
120
result = super(sale_order,self).write(cr, uid, ids, vals, context)
166
result = super(sale_order, self).write(cr, uid, ids, vals, context)
121
167
self.expand_packs(cr, uid, ids, context)
129
175
updated_orders = []
130
176
for order in self.browse(cr, uid, ids, context):
132
fiscal_position = order.fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, order.fiscal_position.id, context) or False
134
# The reorder variable is used to ensure lines of the same pack go right after their parent.
135
# What the algorithm does is check if the previous item had children. As children items
136
# must go right after the parent if the line we're evaluating doesn't have a parent it
137
# means it's a new item (and probably has the default 10 sequence number - unless the
138
# appropiate c2c_sale_sequence module is installed). In this case we mark the item for
139
# reordering and evaluate the next one. Note that as the item is not evaluated and it might
140
# have to be expanded it's put on the queue for another iteration (it's simple and works well).
141
# Once the next item has been evaluated the sequence of the item marked for reordering is updated
142
# with the next value.
179
order.fiscal_position
180
and self.pool.get('account.fiscal.position').browse(
181
cr, uid, order.fiscal_position.id, context
186
The reorder variable is used to ensure lines of the same pack go
187
right after their parent. What the algorithm does is check if the
188
previous item had children. As children items must go right after
189
the parent if the line we're evaluating doesn't have a parent it
190
means it's a new item (and probably has the default 10 sequence
191
number - unless the appropiate c2c_sale_sequence module is
192
installed). In this case we mark the item for reordering and
193
evaluate the next one. Note that as the item is not evaluated and
194
it might have to be expanded it's put on the queue for another
195
iteration (it's simple and works well). Once the next item has been
196
evaluated the sequence of the item marked for reordering is updated
145
201
last_had_children = False
146
202
for line in order.order_line:
147
203
if last_had_children and not line.pack_parent_line_id:
148
204
reorder.append(line.id)
149
if line.product_id.pack_line_ids and not order.id in updated_orders:
206
line.product_id.pack_line_ids
207
and not order.id in updated_orders
150
209
updated_orders.append(order.id)
155
214
if sequence > line.sequence:
156
self.pool.get('sale.order.line').write(cr, uid, [line.id], {
157
'sequence': sequence,
215
self.pool.get('sale.order.line').write(
216
cr, uid, [line.id], {'sequence': sequence, }, context)
160
218
sequence = line.sequence
184
242
pricelist = order.pricelist_id.id
185
price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
186
subproduct.id, quantity, order.partner_id.id, {
187
'uom': subproduct.uom_id.id,
188
'date': order.date_order,
243
price = self.pool.get('product.pricelist').price_get(
244
cr, uid, [pricelist], subproduct.id, quantity,
245
order.partner_id.id, {
246
'uom': subproduct.uom_id.id,
247
'date': order.date_order,
190
250
discount = line.discount
192
252
# Obtain product name in partner's language
193
253
ctx = {'lang': order.partner_id.lang}
194
subproduct_name = self.pool.get('product.product').browse(cr, uid, subproduct.id, ctx).name
254
subproduct_name = self.pool.get('product.product').browse(
255
cr, uid, subproduct.id, ctx).name
196
tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fiscal_position, subproduct.taxes_id)
257
tax_ids = self.pool.get('account.fiscal.position').map_tax(
258
cr, uid, fiscal_position, subproduct.taxes_id)
198
260
if subproduct.uos_id:
199
261
uos_id = subproduct.uos_id.id
206
268
'order_id': order.id,
207
'name': '%s%s' % ('> '* (line.pack_depth+1), subproduct_name),
270
'> ' * (line.pack_depth+1), subproduct_name
208
272
'sequence': sequence,
209
273
'delay': subproduct.sale_delay or 0.0,
210
274
'product_id': subproduct.id,
211
'procurement_id': line.procurement_id and line.procurement_id.id or False,
277
and line.procurement_id.id
212
280
'price_unit': price,
213
'tax_id': [(6,0,tax_ids)],
281
'tax_id': [(6, 0, tax_ids)],
214
282
'type': subproduct.procure_method,
215
'property_ids': [(6,0,[])],
283
'property_ids': [(6, 0, [])],
216
284
'address_allotment_id': False,
217
285
'product_uom_qty': quantity,
218
286
'product_uom': subproduct.uom_id.id,
219
287
'product_uos_qty': uos_qty,
220
288
'product_uos': uos_id,
221
289
'product_packaging': False,
222
'move_ids': [(6,0,[])],
290
'move_ids': [(6, 0, [])],
223
291
'discount': discount,
224
292
'number_packages': False,
229
297
'pack_depth': line.pack_depth + 1,
232
# It's a control for the case that the nan_external_prices was installed with the product pack
300
""" It's a control for the case that the
301
nan_external_prices was installed with the product pack """
233
302
if 'prices_used' in line:
234
303
vals['prices_used'] = line.prices_used
236
self.pool.get('sale.order.line').create(cr, uid, vals, context)
305
self.pool.get('sale.order.line').create(
306
cr, uid, vals, context)
237
307
if not order.id in updated_orders:
238
308
updated_orders.append(order.id)
240
310
for id in reorder:
242
self.pool.get('sale.order.line').write(cr, uid, [id], {
243
'sequence': sequence,
312
self.pool.get('sale.order.line').write(
313
cr, uid, [id], {'sequence': sequence, }, context)
246
315
if updated_orders:
248
# Try to expand again all those orders that had a pack in this iteration.
249
# This way we support packs inside other packs.
316
""" Try to expand again all those orders that had a pack in this
317
iteration. This way we support packs inside other packs. """
250
318
self.expand_packs(cr, uid, ids, context, depth + 1)
254
322
class purchase_order_line(orm.Model):
255
323
_inherit = 'purchase.order.line'
257
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of purchase order lines."),
258
'pack_depth': fields.integer('Depth', required=True, help='Depth of the product if it is part of a pack.'),
259
'pack_parent_line_id': fields.many2one('purchase.order.line', 'Pack', help='The pack that contains this product.'),
260
'pack_child_line_ids': fields.one2many('purchase.order.line', 'pack_parent_line_id', 'Lines in pack', help=''),
325
'sequence': fields.integer(
327
help="""Gives the sequence order when displaying a list of
328
purchase order lines. """
330
'pack_depth': fields.integer(
331
'Depth', required=True,
332
help='Depth of the product if it is part of a pack.'
334
'pack_parent_line_id': fields.many2one(
335
'purchase.order.line', 'Pack',
336
help='The pack that contains this product.'
338
'pack_child_line_ids': fields.one2many(
339
'purchase.order.line', 'pack_parent_line_id', 'Lines in pack'
263
343
'pack_depth': lambda *a: 0,
268
348
_inherit = 'purchase.order'
270
350
def create(self, cr, uid, vals, context=None):
271
result = super(purchase_order,self).create(cr, uid, vals, context)
351
result = super(purchase_order, self).create(cr, uid, vals, context)
272
352
self.expand_packs(cr, uid, [result], context)
275
355
def write(self, cr, uid, ids, vals, context=None):
276
result = super(purchase_order,self).write(cr, uid, ids, vals, context)
356
result = super(purchase_order, self).write(cr, uid, ids, vals, context)
277
357
self.expand_packs(cr, uid, ids, context)
285
365
updated_orders = []
286
366
for order in self.browse(cr, uid, ids, context):
288
fiscal_position = order.fiscal_position and self.pool.get('account.fiscal.position').browse(cr, uid, order.fiscal_position.id, context) or False
290
# The reorder variable is used to ensure lines of the same pack go right after their parent.
291
# What the algorithm does is check if the previous item had children. As children items
292
# must go right after the parent if the line we're evaluating doesn't have a parent it
293
# means it's a new item (and probably has the default 10 sequence number - unless the
294
# appropiate c2c_sale_sequence module is installed). In this case we mark the item for
295
# reordering and evaluate the next one. Note that as the item is not evaluated and it might
296
# have to be expanded it's put on the queue for another iteration (it's simple and works well).
297
# Once the next item has been evaluated the sequence of the item marked for reordering is updated
298
# with the next value.
368
order.fiscal_position
369
and self.pool.get('account.fiscal.position').browse(
370
cr, uid, order.fiscal_position.id, context
375
The reorder variable is used to ensure lines of the same pack go
376
right after their parent. What the algorithm does is check if the
377
previous item had children. As children items must go right after
378
the parent if the line we're evaluating doesn't have a parent it
379
means it's a new item (and probably has the default 10 sequence
380
number - unless the appropiate c2c_sale_sequence module is
381
installed). In this case we mark the item for reordering and
382
evaluate the next one. Note that as the item is not evaluated and
383
it might have to be expanded it's put on the queue for another
384
iteration (it's simple and works well). Once the next item has been
385
evaluated the sequence of the item marked for reordering is updated
301
390
last_had_children = False
302
391
for line in order.order_line:
303
392
if last_had_children and not line.pack_parent_line_id:
304
393
reorder.append(line.id)
305
if line.product_id.pack_line_ids and not order.id in updated_orders:
395
line.product_id.pack_line_ids
396
and not order.id in updated_orders
306
398
updated_orders.append(order.id)
311
403
if sequence > line.sequence:
312
self.pool.get('purchase.order.line').write(cr, uid, [line.id], {
313
'sequence': sequence,
404
self.pool.get('purchase.order.line').write(
405
cr, uid, [line.id], {'sequence': sequence, }, context)
316
407
sequence = line.sequence
339
430
pricelist = order.pricelist_id.id
340
price = self.pool.get('product.pricelist').price_get(cr, uid, [pricelist],
341
subproduct.id, quantity, order.partner_id.id, {
342
'uom': subproduct.uom_id.id,
343
'date': order.date_order,
431
price = self.pool.get('product.pricelist').price_get(
432
cr, uid, [pricelist], subproduct.id, quantity,
433
order.partner_id.id, {
434
'uom': subproduct.uom_id.id,
435
'date': order.date_order,
346
439
# Obtain product name in partner's language
347
440
ctx = {'lang': order.partner_id.lang}
348
subproduct_name = self.pool.get('product.product').browse(cr, uid, subproduct.id, ctx).name
441
subproduct_name = self.pool.get('product.product').browse(
442
cr, uid, subproduct.id, ctx).name
350
tax_ids = self.pool.get('account.fiscal.position').map_tax(cr, uid, fiscal_position, subproduct.taxes_id)
444
tax_ids = self.pool.get('account.fiscal.position').map_tax(
445
cr, uid, fiscal_position, subproduct.taxes_id)
353
448
'order_id': order.id,
354
'name': '%s%s' % ('> '* (line.pack_depth + 1), subproduct_name),
450
'> ' * (line.pack_depth + 1), subproduct_name),
355
451
'date_planned': line.date_planned or 0.0,
356
452
'sequence': sequence,
357
453
'product_id': subproduct.id,
358
454
'price_unit': price,
359
'taxes_id': [(6,0,tax_ids)],
455
'taxes_id': [(6, 0, tax_ids)],
360
456
'product_qty': quantity,
361
457
'product_uom': subproduct.uom_id.id,
362
'move_ids': [(6,0,[])],
458
'move_ids': [(6, 0, [])],
364
460
'state': 'draft',
365
461
'pack_parent_line_id': line.id,
366
462
'pack_depth': line.pack_depth + 1,
369
# It's a control for the case that the nan_external_prices was installed with the product pack
465
# It's a control for the case that the nan_external_prices
466
# was installed with the product pack
370
467
if 'prices_used' in line:
371
468
vals['prices_used'] = line.prices_used
373
self.pool.get('purchase.order.line').create(cr, uid, vals, context)
470
self.pool.get('purchase.order.line').create(
471
cr, uid, vals, context)
374
472
if not order.id in updated_orders:
375
473
updated_orders.append(order.id)
377
475
for id in reorder:
379
self.pool.get('purchase.order.line').write(cr, uid, [id], {
380
'sequence': sequence,
477
self.pool.get('purchase.order.line').write(
478
cr, uid, [id], {'sequence': sequence, }, context)
383
480
if updated_orders:
385
# Try to expand again all those orders that had a pack in this iteration.
386
# This way we support packs inside other packs.
481
# Try to expand again all those orders that had a pack in
482
# this iteration. This way we support packs inside other packs.
387
483
self.expand_packs(cr, uid, ids, context, depth + 1)
b'\\ No newline at end of file'