1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# Copyright (C) 2011 MSF, TeMPO Consulting
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU Affero General Public License as
8
# published by the Free Software Foundation, either version 3 of the
9
# License, or (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU Affero General Public License for more details.
16
# You should have received a copy of the GNU Affero General Public License
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
##############################################################################
22
from datetime import datetime, timedelta, date
23
from dateutil.relativedelta import relativedelta, relativedelta
24
from osv import osv, fields
25
from osv.orm import browse_record, browse_null
26
from tools.translate import _
28
import decimal_precision as dp
34
SHORT_SHELF_LIFE_MESS = 'Product with Short Shelf Life, check the accuracy of the order quantity, frequency and mode of transport.'
37
class sale_order_line(osv.osv):
39
override to add message at sale order creation and update
41
_inherit = 'sale.order.line'
43
def _kc_dg(self, cr, uid, ids, name, arg, context=None):
45
return 'KC' if cold chain or 'DG' if dangerous goods
51
for sol in self.browse(cr, uid, ids, context=context):
53
if sol.product_id.heat_sensitive_item:
55
elif sol.product_id.dangerous_goods:
60
_columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),}
62
def product_id_change(self, cr, uid, ids, pricelist, product, qty=0,
63
uom=False, qty_uos=0, uos=False, name='', partner_id=False,
64
lang=False, update_tax=True, date_order=False, packaging=False, fiscal_position=False, flag=False):
66
if the product is short shelf life we display a warning
69
result = super(sale_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty,
70
uom, qty_uos, uos, name, partner_id, lang, update_tax, date_order, packaging, fiscal_position, flag)
72
# if the product is short shelf life, display a warning
74
prod_obj = self.pool.get('product.product')
75
if prod_obj.browse(cr, uid, product).short_shelf_life:
77
'title': 'Short Shelf Life product',
78
'message': _(SHORT_SHELF_LIFE_MESS)
80
result.update(warning=warning)
87
class sale_order(osv.osv):
89
add message when so is written, i.e when we add new so lines
91
_inherit = 'sale.order'
93
def write(self, cr, uid, ids, vals, context=None):
95
display message if contains short shelf life
97
if isinstance(ids, (int, long)):
100
for obj in self.browse(cr, uid, ids, context=context):
101
for line in obj.order_line:
103
if line.product_id.short_shelf_life:
105
self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
107
return super(sale_order, self).write(cr, uid, ids, vals, context=context)
112
class purchase_order_line(osv.osv):
114
override to add message at purchase order creation and update
116
_inherit = 'purchase.order.line'
118
def _kc_dg(self, cr, uid, ids, name, arg, context=None):
120
return 'KC' if cold chain or 'DG' if dangerous goods
126
for pol in self.browse(cr, uid, ids, context=context):
128
if pol.product_id.heat_sensitive_item:
129
result[pol.id] = 'KC'
130
elif pol.product_id.dangerous_goods:
131
result[pol.id] = 'DG'
135
_columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),}
137
def product_id_change(self, cr, uid, ids, pricelist, product, qty, uom,
138
partner_id, date_order=False, fiscal_position=False, date_planned=False,
139
name=False, price_unit=False, notes=False):
141
if the product is short shelf life we display a warning
144
result = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom,
145
partner_id, date_order, fiscal_position, date_planned,
146
name, price_unit, notes)
148
# if the product is short shelf life, display a warning
150
prod_obj = self.pool.get('product.product')
151
if prod_obj.browse(cr, uid, product).short_shelf_life:
153
'title': 'Short Shelf Life product',
154
'message': _(SHORT_SHELF_LIFE_MESS)
156
result.update(warning=warning)
160
purchase_order_line()
163
class purchase_order(osv.osv):
165
add message when po is written, i.e when we add new po lines
167
no need to modify the wkf_confirm_order as the wrtie method is called during the workflow
169
_inherit = 'purchase.order'
171
def write(self, cr, uid, ids, vals, context=None):
173
display message if contains short shelf life
175
if isinstance(ids, (int, long)):
178
for obj in self.browse(cr, uid, ids, context=context):
179
for line in obj.order_line:
181
if line.product_id.short_shelf_life:
183
self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
185
return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
190
class stock_warehouse_orderpoint(osv.osv):
194
_inherit = 'stock.warehouse.orderpoint'
196
def create(self, cr, uid, vals, context=None):
200
new_id = super(stock_warehouse_orderpoint, self).create(cr, uid, vals, context=context)
202
product_obj = self.pool.get('product.product')
203
product_id = vals.get('product_id', False)
205
if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
206
self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
210
def write(self, cr, uid, ids, vals, context=None):
214
result = super(stock_warehouse_orderpoint, self).write(cr, uid, ids, vals, context=context)
216
if isinstance(ids, (int, long)):
219
product_obj = self.pool.get('product.product')
220
product_id = vals.get('product_id', False)
222
if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
223
for obj in self.browse(cr, uid, ids, context=context):
224
self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
228
stock_warehouse_orderpoint()
231
class stock_warehouse_automatic_supply(osv.osv):
235
_inherit = 'stock.warehouse.automatic.supply'
237
def create(self, cr, uid, vals, context=None):
241
new_id = super(stock_warehouse_automatic_supply, self).create(cr, uid, vals, context=context)
243
product_obj = self.pool.get('product.product')
244
product_id = vals.get('product_id', False)
246
if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
247
self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
251
def write(self, cr, uid, ids, vals, context=None):
255
result = super(stock_warehouse_automatic_supply, self).write(cr, uid, ids, vals, context=context)
257
if isinstance(ids, (int, long)):
260
product_obj = self.pool.get('product.product')
261
product_id = vals.get('product_id', False)
263
if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
264
for obj in self.browse(cr, uid, ids, context=context):
265
self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
269
stock_warehouse_automatic_supply()
272
class stock_warehouse_order_cycle(osv.osv):
276
_inherit = 'stock.warehouse.order.cycle'
278
def create(self, cr, uid, vals, context=None):
282
new_id = super(stock_warehouse_order_cycle, self).create(cr, uid, vals, context=context)
284
product_obj = self.pool.get('product.product')
285
product_id = vals.get('product_id', False)
287
if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
288
self.log(cr, uid, new_id, _(SHORT_SHELF_LIFE_MESS))
292
def write(self, cr, uid, ids, vals, context=None):
299
result = super(stock_warehouse_order_cycle, self).write(cr, uid, ids, vals, context=context)
301
if isinstance(ids, (int, long)):
304
product_obj = self.pool.get('product.product')
305
product_id = vals.get('product_id', False)
307
if product_obj.browse(cr, uid, product_id, context=context).short_shelf_life:
308
for obj in self.browse(cr, uid, ids, context=context):
309
self.log(cr, uid, obj.id, _(SHORT_SHELF_LIFE_MESS))
313
stock_warehouse_order_cycle()
316
class stock_picking(osv.osv):
320
_inherit = 'stock.picking'
322
def _do_partial_hook(self, cr, uid, ids, context, *args, **kwargs):
324
hook to update defaults data
326
# variable parameters
327
move = kwargs.get('move')
328
assert move, 'missing move'
329
partial_datas = kwargs.get('partial_datas')
330
assert partial_datas, 'missing partial_datas'
332
# calling super method
333
defaults = super(stock_picking, self)._do_partial_hook(cr, uid, ids, context, *args, **kwargs)
334
assetId = partial_datas.get('move%s'%(move.id), False).get('asset_id')
336
defaults.update({'asset_id': assetId})
345
class stock_move(osv.osv):
349
_inherit = 'stock.move'
351
def create(self, cr, uid, vals, context=None):
353
complete info normally generated by javascript on_change function
355
prod_obj = self.pool.get('product.product')
356
if vals.get('product_id', False):
357
# complete hidden flags - needed if not created from GUI
358
product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
359
if product.batch_management:
360
vals.update(hidden_batch_management_mandatory=True)
361
elif product.perishable:
362
vals.update(hidden_perishable_mandatory=True)
364
vals.update(hidden_batch_management_mandatory=False,
365
hidden_perishable_mandatory=False,
368
result = super(stock_move, self).create(cr, uid, vals, context=context)
371
def write(self, cr, uid, ids, vals, context=None):
373
complete info normally generated by javascript on_change function
375
prod_obj = self.pool.get('product.product')
376
if vals.get('product_id', False):
377
# complete hidden flags - needed if not created from GUI
378
product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
379
if product.batch_management:
380
vals.update(hidden_batch_management_mandatory=True)
381
elif product.perishable:
382
vals.update(hidden_perishable_mandatory=True)
384
vals.update(hidden_batch_management_mandatory=False,
385
hidden_perishable_mandatory=False,
388
result = super(stock_move, self).write(cr, uid, ids, vals, context=context)
391
def _kc_dg(self, cr, uid, ids, name, arg, context=None):
393
return 'KC' if cold chain or 'DG' if dangerous goods
399
for move in self.browse(cr, uid, ids, context=context):
401
if move.product_id.heat_sensitive_item:
402
result[move.id] = 'KC'
403
elif move.product_id.dangerous_goods:
404
result[move.id] = 'DG'
408
def _check_batch_management(self, cr, uid, ids, context=None):
410
check for batch management
411
@return: True or False
413
for move in self.browse(cr, uid, ids, context=context):
414
if move.state == 'done':
415
if move.product_id.batch_management:
416
if not move.prodlot_id:
420
def _check_perishable(self, cr, uid, ids, context=None):
423
@return: True or False
425
for move in self.browse(cr, uid, ids, context=context):
426
if move.state == 'done':
427
if move.product_id.perishable:
428
if not move.prodlot_id:
432
def _check_prodlot_need(self, cr, uid, ids, context=None):
434
If the move has a prodlot but does not need one, return False.
436
for move in self.browse(cr, uid, ids, context=context):
438
if not move.product_id.perishable and not move.product_id.batch_management:
442
def _check_prodlot_need_batch_management(self, cr, uid, ids, context=None):
444
If the product is batch management while the selected prodlot is 'internal'.
446
for move in self.browse(cr, uid, ids, context=context):
448
if move.prodlot_id.type == 'internal' and move.product_id.batch_management:
452
def _check_prodlot_need_perishable(self, cr, uid, ids, context=None):
454
If the product is perishable ONLY while the selected prodlot is 'standard'.
456
for move in self.browse(cr, uid, ids, context=context):
458
if move.prodlot_id.type == 'standard' and not move.product_id.batch_management and move.product_id.perishable:
462
def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, address_id=False):
464
the product changes, set the hidden flag if necessary
466
result = super(stock_move, self).onchange_product_id(cr, uid, ids, prod_id, loc_id,
467
loc_dest_id, address_id)
469
# product changes, prodlot is always cleared
470
result.setdefault('value', {})['prodlot_id'] = False
471
# reset the hidden flag
472
result.setdefault('value', {})['hidden_batch_management_mandatory'] = False
473
result.setdefault('value', {})['hidden_perishable_mandatory'] = False
475
product = self.pool.get('product.product').browse(cr, uid, prod_id)
476
if product.batch_management:
477
result.setdefault('value', {})['hidden_batch_management_mandatory'] = True
478
result['warning'] = {'title': _('Info'),
479
'message': _('The selected product is Batch Management.')}
481
elif product.perishable:
482
result.setdefault('value', {})['hidden_perishable_mandatory'] = True
483
result['warning'] = {'title': _('Info'),
484
'message': _('The selected product is Perishable.')}
488
def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
490
function for KC/SSL/DG/NP products
496
result[id].update({f: False})
498
for obj in self.browse(cr, uid, ids, context=context):
500
if obj.product_id.heat_sensitive_item:
501
result[obj.id]['kc_check'] = True
503
if obj.product_id.short_shelf_life:
504
result[obj.id]['ssl_check'] = True
506
if obj.product_id.dangerous_goods:
507
result[obj.id]['dg_check'] = True
509
if obj.product_id.narcotic:
510
result[obj.id]['np_check'] = True
514
_columns = {'kc_dg': fields.function(_kc_dg, method=True, string='KC/DG', type='char'),
515
# if prodlot needs to be mandatory, add 'required': ['|', ('hidden_batch_management_mandatory','=',True), ('hidden_perishable_mandatory','=',True)] in attrs
516
'hidden_batch_management_mandatory': fields.boolean(string='Hidden Flag for Batch Management product',),
517
'hidden_perishable_mandatory': fields.boolean(string='Hidden Flag for Perishable product',),
518
'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
519
'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
520
'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
521
'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
524
_constraints = [(_check_batch_management,
525
'You must assign a Batch Number for this product (Batch Number Mandatory)',
528
'You must assign an Expiry Date for this product (Expiry Date Mandatory)',
530
(_check_prodlot_need,
531
'The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory',
533
(_check_prodlot_need_batch_management,
534
'The selected product is Batch Number Mandatory while the selected Production Lot corresponds to Expiry Date Mandatory.',
536
(_check_prodlot_need_perishable,
537
'The selected product is Expiry Date Mandatory while the selected Production Lot corresponds to Batch Number Mandatory.',
544
class stock_production_lot(osv.osv):
546
productin lot modifications
548
_inherit = 'stock.production.lot'
550
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False):
552
Correct fields in order to have those from account_statement_from_invoice_lines (in case where account_statement_from_invoice is used)
556
# warehouse wizards or inventory screen
557
if view_type == 'tree' and ((context.get('expiry_date_check', False) and not context.get('batch_number_check', False)) or context.get('hidden_perishable_mandatory')):
558
view = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'specific_rules', 'view_production_lot_expiry_date_tree')
561
result = super(osv.osv, self).fields_view_get(cr, uid, view_id, view_type, context=context, toolbar=toolbar, submenu=submenu)
564
def copy(self, cr, uid, id, default=None, context=None):
566
increase the batch number
567
create a new sequence
573
lot_name = self.read(cr, uid, id, ['name'])['name']
574
default.update(name='%s (copy)'%lot_name, date=time.strftime('%Y-%m-%d'))
576
return super(stock_production_lot, self).copy(cr, uid, id, default, context=context)
578
def copy_data(self, cr, uid, id, default=None, context=None):
584
default.update(revisions=[])
585
return super(stock_production_lot, self).copy_data(cr, uid, id, default, context=context)
587
def create_sequence(self, cr, uid, vals, context=None):
589
Create new entry sequence for every new order
590
@param cr: cursor to database
591
@param user: id of current user
592
@param ids: list of record ids to be process
593
@param context: context arguments, like lang, time zone
594
@return: return a result
596
seq_pool = self.pool.get('ir.sequence')
597
seq_typ_pool = self.pool.get('ir.sequence.type')
599
name = 'Production Lot'
600
code = 'stock.production.lot'
606
seq_typ_pool.create(cr, uid, types)
614
return seq_pool.create(cr, uid, seq)
616
def create(self, cr, uid, vals, context=None):
618
create the sequence for the version management
623
sequence = self.create_sequence(cr, uid, vals, context=context)
624
vals.update({'sequence_id': sequence,})
626
if context.get('update_mode') in ['init', 'update']:
627
if not vals.get('life_date'):
628
# default value to today
629
vals.update(life_date=time.strftime('%Y-%m-%d'))
631
return super(stock_production_lot, self).create(cr, uid, vals, context=context)
633
def write(self, cr, uid, ids, vals, context=None):
635
update the sequence for the version management
637
if isinstance(ids, (int, long)):
640
revision_obj = self.pool.get('stock.production.lot.revision')
642
for lot in self.browse(cr, uid, ids, context=context):
643
# create revision object for each lot
644
version_number = lot.sequence_id.get_id(test='id', context=context)
645
values = {'name': 'Auto Revision Logging',
646
'description': 'The production lot has been modified, this revision log has been created automatically.',
647
'date': time.strftime('%Y-%m-%d'),
648
'indice': version_number,
651
revision_obj.create(cr, uid, values, context=context)
653
return super(stock_production_lot, self).write(cr, uid, ids, vals, context=context)
655
def remove_flag(self, flag, _list):
657
if we do not remove the flag, we fall into an infinite loop
665
def search_check_type(self, cr, uid, obj, name, args, context=None):
667
modify the query to take the type of prodlot into account according to product's attributes
668
'Batch Number mandatory' and 'Expiry Date Mandatory'
670
if batch management: display only 'standard' lot
671
if expiry and not batch management: display only 'internal' lot
672
else: display normally
674
product_obj = self.pool.get('product.product')
675
product_id = context.get('product_id', False)
677
# remove flag avoid infinite loop
678
args = self.remove_flag('check_type', args)
684
product = product_obj.browse(cr, uid, product_id, context=context)
686
if product.batch_management:
688
args.append(('type', '=', 'standard'))
689
elif product.perishable:
691
args.append(('type', '=', 'internal'))
695
def _get_false(self, cr, uid, ids, field_name, arg, context=None):
697
return false for each id
699
if isinstance(ids,(long, int)):
707
def _get_stock(self, cr, uid, ids, field_name, arg, context=None):
708
""" Gets stock of products for locations
709
@return: Dictionary of values
714
if isinstance(ids, (int, long)):
717
product_obj = self.pool.get('product.product')
723
for lot in self.browse(cr, uid, ids, context=context):
724
# because the lot_id changes we have to loop one lot id at a time
726
# if you remove the coma after done, it will no longer work properly
727
c.update({'what': ('in', 'out'),
728
'prodlot_id': lot.id,
729
#'to_date': time.strftime('%Y-%m-%d %H:%M:%S'),
730
#'warehouse': warehouse_id,
731
#'uom': product_uom_id
734
if field_name == 'stock_available':
736
c.update(states=('confirmed','waiting','assigned','done'))
737
elif field_name == 'stock_real':
739
c.update(states=('done',))
741
assert False, 'This line should not be reached: field_name: %s'%field_name
743
qty = product_obj.get_product_available(cr, uid, [lot.product_id.id], context=c)
744
overall_qty = sum(qty.values())
745
result[lot.id] = overall_qty
749
def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
751
function for KC/SSL/DG/NP products
757
result[id].update({f: False})
759
for obj in self.browse(cr, uid, ids, context=context):
761
if obj.product_id.heat_sensitive_item:
762
result[obj.id]['kc_check'] = True
764
if obj.product_id.short_shelf_life:
765
result[obj.id]['ssl_check'] = True
767
if obj.product_id.dangerous_goods:
768
result[obj.id]['dg_check'] = True
770
if obj.product_id.narcotic:
771
result[obj.id]['np_check'] = True
775
_columns = {'check_type': fields.function(_get_false, fnct_search=search_check_type, string='Check Type', type="boolean", readonly=True, method=True),
776
'type': fields.selection([('standard', 'Standard'),('internal', 'Internal'),], string="Type"),
777
#'expiry_date': fields.date('Expiry Date'),
778
'name': fields.char('Batch Number', size=1024, required=True, help="Unique production lot, will be displayed as: PREFIX/SERIAL [INT_REF]"),
779
'date': fields.datetime('Auto Creation Date', required=True),
780
'sequence_id': fields.many2one('ir.sequence', 'Lot Sequence', required=True,),
781
'stock_available': fields.function(_get_stock, method=True, type="float", string="Available", select=True,
782
help="Current quantity of products with this Production Lot Number available in company warehouses",
783
digits_compute=dp.get_precision('Product UoM'), readonly=True,),
784
'stock_real': fields.function(_get_stock, method=True, type="float", string="Real", select=True,
785
help="Current quantity of products with this Production Lot Number available in company warehouses",
786
digits_compute=dp.get_precision('Product UoM'), readonly=True,),
787
'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
788
'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
789
'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
790
'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
793
_defaults = {'type': 'standard',
794
'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'stock.production.lot', context=c),
799
_sql_constraints = [('name_uniq', 'unique (name)', 'The Batch Number must be unique !'),
802
def search(self, cr, uid, args=[], offset=0, limit=None, order=None, context={}, count=False):
804
search function of production lot
806
result = super(stock_production_lot, self).search(cr, uid, args, offset, limit, order, context, count)
810
def name_get(self, cr, uid, ids, context=None):
813
reads = self.read(cr, uid, ids, ['name', 'prefix', 'ref'], context)
816
name = record['name']
817
res.append((record['id'], name))
820
stock_production_lot()
823
class stock_production_lot_revision(osv.osv):
824
_inherit = 'stock.production.lot.revision'
825
_order = 'indice desc'
827
stock_production_lot_revision()
830
class stock_inventory(osv.osv):
832
override the action_confirm to create the production lot if needed
834
_inherit = 'stock.inventory'
836
def action_confirm(self, cr, uid, ids, context=None):
838
if the line is perishable without prodlot, we create the prodlot
840
prodlot_obj = self.pool.get('stock.production.lot')
841
# treat the needed production lot
842
for obj in self.browse(cr, uid, ids, context=context):
843
for line in obj.inventory_line_id:
844
# if perishable product
845
if line.hidden_perishable_mandatory and not line.hidden_batch_management_mandatory:
847
assert line.product_id.perishable, 'product is not perishable but line is'
848
assert line.expiry_date, 'expiry date is not set'
849
# if no production lot, we create a new one
850
if not line.prod_lot_id:
851
# double check to find the corresponding prodlot
852
prodlot_ids = prodlot_obj.search(cr, uid, [('life_date', '=', line.expiry_date),
853
('type', '=', 'internal'),
854
('product_id', '=', line.product_id.id)], context=context)
855
# no prodlot, create a new one
857
vals = {'product_id': line.product_id.id,
858
'life_date': line.expiry_date,
859
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.lot.serial'),
862
prodlot_id = prodlot_obj.create(cr, uid, vals, context=context)
864
prodlot_id = prodlot_ids[0]
866
line.write({'prod_lot_id': prodlot_id,},)
868
# super function after production lot creation - production lot are therefore taken into account at stock move creation
869
result = super(stock_inventory, self).action_confirm(cr, uid, ids, context=context)
875
class stock_inventory_line(osv.osv):
877
add mandatory or readonly behavior to prodlot
879
_inherit = 'stock.inventory.line'
881
def change_lot(self, cr, uid, id, prod_lot_id, context=None):
883
prod lot changes, update the expiry date
885
prodlot_obj = self.pool.get('stock.production.lot')
886
result = {'value':{}}
889
result['value'].update(expiry_date=prodlot_obj.browse(cr, uid, prod_lot_id, context).life_date)
891
result['value'].update(expiry_date=False)
895
def change_expiry(self, cr, uid, id, expiry_date, product_id, type_check, context=None):
897
expiry date changes, find the corresponding internal prod lot
899
prodlot_obj = self.pool.get('stock.production.lot')
900
result = {'value':{}}
902
if expiry_date and product_id:
903
prod_ids = prodlot_obj.search(cr, uid, [('life_date', '=', expiry_date),
904
('type', '=', 'internal'),
905
('product_id', '=', product_id)], context=context)
907
if type_check == 'in':
908
# the corresponding production lot will be created afterwards
909
result['warning'] = {'title': _('Info'),
910
'message': _('The selected Expiry Date does not exist in the system. It will be created during validation process.')}
912
result['value'].update(prod_lot_id=False)
915
result['warning'] = {'title': _('Error'),
916
'message': _('The selected Expiry Date does not exist in the system.')}
918
result['value'].update(expiry_date=False, prod_lot_id=False)
920
# return first prodlot
921
result['value'].update(prod_lot_id=prod_ids[0])
924
# clear expiry date, we clear production lot
925
result['value'].update(prod_lot_id=False,
931
def _get_checks_all(self, cr, uid, ids, name, arg, context=None):
933
function for KC/SSL/DG/NP products
939
result[id].update({f: False,})
941
for obj in self.browse(cr, uid, ids, context=context):
943
if obj.product_id.heat_sensitive_item:
944
result[obj.id]['kc_check'] = True
946
if obj.product_id.short_shelf_life:
947
result[obj.id]['ssl_check'] = True
949
if obj.product_id.dangerous_goods:
950
result[obj.id]['dg_check'] = True
952
if obj.product_id.narcotic:
953
result[obj.id]['np_check'] = True
957
def _check_batch_management(self, cr, uid, ids, context=None):
959
check for batch management
961
for obj in self.browse(cr, uid, ids, context=context):
962
if obj.product_id.batch_management:
963
if not obj.prod_lot_id or obj.prod_lot_id.type != 'standard':
967
def _check_perishable(self, cr, uid, ids, context=None):
969
check for perishable ONLY
971
for obj in self.browse(cr, uid, ids, context=context):
972
if obj.product_id.perishable and not obj.product_id.batch_management:
973
if (not obj.prod_lot_id and not obj.expiry_date) or (obj.prod_lot_id and obj.prod_lot_id.type != 'internal'):
977
def _check_prodlot_need(self, cr, uid, ids, context=None):
979
If the inv line has a prodlot but does not need one, return False.
981
for obj in self.browse(cr, uid, ids, context=context):
983
if not obj.product_id.perishable and not obj.product_id.batch_management:
987
_columns = {'hidden_perishable_mandatory': fields.boolean(string='Hidden Flag for Perishable product',),
988
'hidden_batch_management_mandatory': fields.boolean(string='Hidden Flag for Batch Management product',),
989
'expiry_date': fields.date(string='Expiry Date'),
990
'type_check': fields.char(string='Type Check', size=1024,),
991
'kc_check': fields.function(_get_checks_all, method=True, string='KC', type='boolean', readonly=True, multi="m"),
992
'ssl_check': fields.function(_get_checks_all, method=True, string='SSL', type='boolean', readonly=True, multi="m"),
993
'dg_check': fields.function(_get_checks_all, method=True, string='DG', type='boolean', readonly=True, multi="m"),
994
'np_check': fields.function(_get_checks_all, method=True, string='NP', type='boolean', readonly=True, multi="m"),
997
_defaults = {# in is used, meaning a new prod lot will be created if the specified expiry date does not exist
1001
_constraints = [(_check_batch_management,
1002
'You must assign a Production Lot which corresponds to Batch Number Mandatory Products.',
1005
'You must assign a Production Lot which corresponds to Expiry Date Mandatory Products.',
1007
(_check_prodlot_need,
1008
'The selected product is neither Batch Number Mandatory nor Expiry Date Mandatory',
1012
def on_change_product_id(self, cr, uid, ids, location_id, product, uom=False, to_date=False):
1014
the product changes, set the hidden flag if necessary
1016
result = super(stock_inventory_line, self).on_change_product_id(cr, uid, ids, location_id, product, uom, to_date)
1018
# product changes, prodlot is always cleared
1019
result.setdefault('value', {})['prod_lot_id'] = False
1020
result.setdefault('value', {})['expiry_date'] = False
1021
# reset the hidden flags
1022
result.setdefault('value', {})['hidden_batch_management_mandatory'] = False
1023
result.setdefault('value', {})['hidden_perishable_mandatory'] = False
1025
product_obj = self.pool.get('product.product').browse(cr, uid, product)
1026
if product_obj.batch_management:
1027
result.setdefault('value', {})['hidden_batch_management_mandatory'] = True
1028
elif product_obj.perishable:
1029
result.setdefault('value', {})['hidden_perishable_mandatory'] = True
1033
def create(self, cr, uid, vals, context=None):
1035
complete info normally generated by javascript on_change function
1037
prod_obj = self.pool.get('product.product')
1038
if vals.get('product_id', False):
1039
# complete hidden flags - needed if not created from GUI
1040
product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
1041
if product.batch_management:
1042
vals.update(hidden_batch_management_mandatory=True)
1043
elif product.perishable:
1044
vals.update(hidden_perishable_mandatory=True)
1046
vals.update(hidden_batch_management_mandatory=False,
1047
hidden_perishable_mandatory=False,
1049
# complete expiry date from production lot - needed if not created from GUI
1050
prodlot_obj = self.pool.get('stock.production.lot')
1051
if vals.get('prod_lot_id', False):
1052
vals.update(expiry_date=prodlot_obj.browse(cr, uid, vals.get('prod_lot_id'), context=context).life_date)
1054
result = super(stock_inventory_line, self).create(cr, uid, vals, context=context)
1057
def write(self, cr, uid, ids, vals, context=None):
1059
complete info normally generated by javascript on_change function
1061
prod_obj = self.pool.get('product.product')
1062
if vals.get('product_id', False):
1063
# complete hidden flags - needed if not created from GUI
1064
product = prod_obj.browse(cr, uid, vals.get('product_id'), context=context)
1065
if product.batch_management:
1066
vals.update(hidden_batch_management_mandatory=True)
1067
elif product.perishable:
1068
vals.update(hidden_perishable_mandatory=True)
1070
vals.update(hidden_batch_management_mandatory=False,
1071
hidden_perishable_mandatory=False,
1073
# complete expiry date from production lot - needed if not created from GUI
1074
prodlot_obj = self.pool.get('stock.production.lot')
1075
if vals.get('prod_lot_id', False):
1076
vals.update(expiry_date=prodlot_obj.browse(cr, uid, vals.get('prod_lot_id'), context=context).life_date)
1079
result = super(stock_inventory_line, self).write(cr, uid, ids, vals, context=context)
1082
stock_inventory_line()
1084
class report_stock_inventory(osv.osv):
1086
UF-565: add group by expired_date
1088
_inherit = "report.stock.inventory"
1091
tools.drop_view_if_exists(cr, 'report_stock_inventory')
1093
CREATE OR REPLACE view report_stock_inventory AS (
1095
min(m.id) as id, m.date as date,
1096
m.expired_date as expired_date,
1097
m.address_id as partner_id, m.location_id as location_id,
1098
m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type,
1100
m.state as state, m.prodlot_id as prodlot_id,
1101
coalesce(sum(-pt.standard_price * m.product_qty)::decimal, 0.0) as value,
1102
CASE when pt.uom_id = m.product_uom
1104
coalesce(sum(-m.product_qty)::decimal, 0.0)
1106
coalesce(sum(-m.product_qty * pu.factor)::decimal, 0.0) END as product_qty
1109
LEFT JOIN stock_picking p ON (m.picking_id=p.id)
1110
LEFT JOIN product_product pp ON (m.product_id=pp.id)
1111
LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
1112
LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
1113
LEFT JOIN product_uom u ON (m.product_uom=u.id)
1114
LEFT JOIN stock_location l ON (m.location_id=l.id)
1116
m.id, m.product_id, m.product_uom, pt.categ_id, m.address_id, m.location_id, m.location_dest_id,
1117
m.prodlot_id, m.expired_date, m.date, m.state, l.usage, m.company_id,pt.uom_id
1120
-m.id as id, m.date as date,
1121
m.expired_date as expired_date,
1122
m.address_id as partner_id, m.location_dest_id as location_id,
1123
m.product_id as product_id, pt.categ_id as product_categ_id, l.usage as location_type,
1125
m.state as state, m.prodlot_id as prodlot_id,
1126
coalesce(sum(pt.standard_price * m.product_qty )::decimal, 0.0) as value,
1127
CASE when pt.uom_id = m.product_uom
1129
coalesce(sum(m.product_qty)::decimal, 0.0)
1131
coalesce(sum(m.product_qty * pu.factor)::decimal, 0.0) END as product_qty
1134
LEFT JOIN stock_picking p ON (m.picking_id=p.id)
1135
LEFT JOIN product_product pp ON (m.product_id=pp.id)
1136
LEFT JOIN product_template pt ON (pp.product_tmpl_id=pt.id)
1137
LEFT JOIN product_uom pu ON (pt.uom_id=pu.id)
1138
LEFT JOIN product_uom u ON (m.product_uom=u.id)
1139
LEFT JOIN stock_location l ON (m.location_dest_id=l.id)
1141
m.id, m.product_id, m.product_uom, pt.categ_id, m.address_id, m.location_id, m.location_dest_id,
1142
m.prodlot_id, m.expired_date, m.date, m.state, l.usage, m.company_id,pt.uom_id
1147
_columns = {'expired_date': fields.date(string='Expiry Date'),
1150
report_stock_inventory()