~jgrandguillaume-c2c/openobject-addons/multi-company-cost-price

« back to all changes in this revision

Viewing changes to stock/stock.py

  • Committer: Joël Grand-Guillaume
  • Date: 2010-04-08 09:00:10 UTC
  • mfrom: (2533.3.664)
  • Revision ID: joel.grandguillaume@camptocamp.com-20100408090010-c0pqjan341s18bxs
[MRG] Merge from last trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
#
20
20
##############################################################################
21
21
 
22
 
from mx import DateTime
23
 
import time
24
 
import netsvc
 
22
from datetime import datetime
 
23
from dateutil.relativedelta import relativedelta
 
24
 
25
25
from osv import fields, osv
26
26
from tools import config
27
27
from tools.translate import _
 
28
import math
 
29
import netsvc
 
30
import time
28
31
import tools
29
32
 
 
33
import decimal_precision as dp
 
34
 
30
35
 
31
36
#----------------------------------------------------------
32
37
# Incoterms
361
366
        'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the tracking lots without removing it."),
362
367
        'serial': fields.char('Reference', size=64),
363
368
        'move_ids': fields.one2many('stock.move', 'tracking_id', 'Moves Tracked'),
364
 
        'date': fields.datetime('Date Created', required=True),
 
369
        'date': fields.datetime('Created Date', required=True),
365
370
    }
366
371
    _defaults = {
367
372
        'active': lambda *a: 1,
457
462
 
458
463
    _columns = {
459
464
        'name': fields.char('Reference', size=64, select=True),
460
 
        'origin': fields.char('Source document', size=64, help="Reference of the document that produced this picking."),
 
465
        'origin': fields.char('Origin', size=64, help="Reference of the document that produced this picking."),
461
466
        'backorder_id': fields.many2one('stock.picking', 'Back Order', help="If the picking is splitted then the picking id in available state of move for this picking is stored in Backorder."),
462
467
        'type': fields.selection([('out', 'Sending Goods'), ('in', 'Getting Goods'), ('internal', 'Internal'), ('delivery', 'Delivery')], 'Shipping Type', required=True, select=True, help="Shipping type specify, goods coming in or going out."),
463
468
        'active': fields.boolean('Active', help="If the active field is set to true, it will allow you to hide the picking without removing it."),
481
486
            \n* The \'Available\' state is set automatically when the products are ready to be moved.\
482
487
            \n* The \'Waiting\' state is used in MTO moves when a movement is waiting for another one.'),
483
488
        'min_date': fields.function(get_min_max_date, fnct_inv=_set_minimum_date, multi="min_max_date",
484
 
                 method=True, store=True, type='datetime', string='Planned Date', select=1, help="Planned date for Picking. Default it takes current date"),
485
 
        'date': fields.datetime('Date Order', help="Date of Order"),
 
489
                 method=True, store=True, type='datetime', string='Expected Date', select=1, help="Expected date for Picking. Default it takes current date"),
 
490
        'date': fields.datetime('Order Date', help="Date of Order"),
486
491
        'date_done': fields.datetime('Date Done', help="Date of completion"),
487
492
        'max_date': fields.function(get_min_max_date, fnct_inv=_set_maximum_date, multi="min_max_date",
488
 
                 method=True, store=True, type='datetime', string='Max. Planned Date', select=2),
 
493
                 method=True, store=True, type='datetime', string='Max. Expected Date', select=2),
489
494
        'move_lines': fields.one2many('stock.move', 'picking_id', 'Entry lines', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
490
495
        'auto_picking': fields.boolean('Auto-Picking'),
491
496
        'address_id': fields.many2one('res.partner.address', 'Partner', help="Address of partner"),
864
869
    def name_get(self, cr, uid, ids, context={}):
865
870
        if not ids:
866
871
            return []
867
 
        reads = self.read(cr, uid, ids, ['name', 'ref'], context)
 
872
        reads = self.read(cr, uid, ids, ['name', 'prefix', 'ref'], context)
868
873
        res = []
869
874
        for record in reads:
870
875
            name = record['name']
 
876
            prefix = record['prefix']
 
877
            if prefix:
 
878
                name = prefix + '/' + name
871
879
            if record['ref']:
872
 
                name = name + '/' + record['ref']
 
880
                name = '%s [%s]' % (name, record['ref'])
873
881
            res.append((record['id'], name))
874
882
        return res
875
883
 
913
921
 
914
922
    _columns = {
915
923
        'name': fields.char('Serial', size=64, required=True),
916
 
        'ref': fields.char('Internal Reference', size=64),
 
924
        'ref': fields.char('Internal Reference', size=256),
 
925
        'prefix': fields.char('Prefix', size=64),
917
926
        'product_id': fields.many2one('product.product', 'Product', required=True),
918
927
        'date': fields.datetime('Created Date', required=True),
919
928
        'stock_available': fields.function(_get_stock, fnct_search=_stock_search, method=True, type="float", string="Available", select="2"),
931
940
 
932
941
stock_production_lot()
933
942
 
934
 
class stock_split_production_lots(osv.osv_memory):
935
 
    _name = "stock.split.production.lots"
936
 
    _description = "Split Production Lots"
937
 
 
938
 
    def _quantity_default_get(self, cr, uid, object=False, field=False, context=None):
939
 
        cr.execute("select %s from %s where id=%s" %(field,object,context.get('active_id')))
940
 
        res = cr.fetchone()[0]
941
 
        return res
942
 
 
943
 
    _columns = {
944
 
        'name': fields.char('Lot Number/Prefix', size=64, required=True),
945
 
        'qty': fields.float('Total Quantity'),
946
 
        'action': fields.selection([('split','Split'),('keepinone','Keep in one lot')],'Action'),
947
 
    }
948
 
    _defaults = {
949
 
                 'qty': lambda self,cr,uid,c: self.pool.get('stock.split.production.lots')._quantity_default_get(cr, uid, 'stock_move', 'product_qty',context=c) or 1,
950
 
    }
951
 
 
952
 
    def split_lines(self, cr, uid, ids, context={}):
953
 
        data = self.read(cr, uid, ids[0])
954
 
        prodlot_obj = self.pool.get('stock.production.lot')
955
 
        move_obj = self.pool.get('stock.move')
956
 
        move_browse = move_obj.browse(cr, uid, context['active_id'])
957
 
 
958
 
        quantity = data['qty']
959
 
        if quantity <= 0 or move_browse.product_qty == 0:
960
 
            return {}
961
 
        uos_qty = quantity/move_browse.product_qty*move_browse.product_uos_qty
962
 
 
963
 
        quantity_rest = move_browse.product_qty%quantity
964
 
        uos_qty_rest = quantity_rest/move_browse.product_qty*move_browse.product_uos_qty
965
 
 
966
 
        update_val = {
967
 
            'product_qty': quantity,
968
 
            'product_uos_qty': uos_qty,
969
 
        }
970
 
 
971
 
        new_move = []
972
 
        for idx in range(int(move_browse.product_qty//quantity)):
973
 
            if idx:
974
 
                current_move = move_obj.copy(cr, uid, move_browse.id, {'state': move_browse.state})
975
 
                new_move.append(current_move)
976
 
            else:
977
 
                current_move = move_browse.id
978
 
            new_prodlot = prodlot_obj.create(cr, uid, {'name': data['name'], 'ref': '%d'%idx}, {'product_id': move_browse.product_id.id})
979
 
            update_val['prodlot_id'] = new_prodlot
980
 
            move_obj.write(cr, uid, [current_move], update_val)
981
 
 
982
 
        if quantity_rest > 0:
983
 
            idx = int(move_browse.product_qty//quantity)
984
 
            update_val['product_qty'] = quantity_rest
985
 
            update_val['product_uos_qty'] = uos_qty_rest
986
 
            if idx:
987
 
                current_move = move_obj.copy(cr, uid, move_browse.id, {'state': move_browse.state})
988
 
                new_move.append(current_move)
989
 
            else:
990
 
                current_move = move_browse.id
991
 
            new_prodlot = prodlot_obj.create(cr, uid, {'name': data['name'], 'ref': '%d'%idx}, {'product_id': move_browse.product_id.id})
992
 
            update_val['prodlot_id'] = new_prodlot
993
 
            move_obj.write(cr, uid, [current_move], update_val)
994
 
 
995
 
        return {}
996
 
 
997
 
stock_split_production_lots()
998
 
 
999
 
 
1000
943
class stock_production_lot_revision(osv.osv):
1001
944
    _name = 'stock.production.lot.revision'
1002
945
    _description = 'Production lot revisions'
1062
1005
        'name': fields.char('Name', size=64, required=True, select=True),
1063
1006
        'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'),
1064
1007
 
1065
 
        'date': fields.datetime('Date Created'),
 
1008
        'date': fields.datetime('Created Date'),
1066
1009
        'date_planned': fields.datetime('Date', required=True, help="Scheduled date for the movement of the products or real date if the move is done."),
1067
1010
 
1068
1011
        'product_id': fields.many2one('product.product', 'Product', required=True, select=True),
1094
1037
                                  help='When the stock move is created it is in the \'Draft\' state.\n After that it is set to \'Confirmed\' state.\n If stock is available state is set to \'Avaiable\'.\n When the picking it done the state is \'Done\'.\
1095
1038
                                  \nThe state is \'Waiting\' if the move is waiting for another one.'),
1096
1039
        'price_unit': fields.float('Unit Price',
1097
 
            digits=(16, int(config['price_accuracy']))),
 
1040
            digits_compute= dp.get_precision('Account')),
1098
1041
        'company_id': fields.many2one('res.company', 'Company', required=True,select=1),
1099
1042
        'partner_id': fields.related('picking_id','address_id','partner_id',type='many2one', relation="res.partner", string="Partner"),
1100
1043
        'backorder_id': fields.related('picking_id','backorder_id',type='many2one', relation="stock.picking", string="Back Orders"),
1101
 
        'origin': fields.related('picking_id','origin',type='char', size=64, relation="stock.picking", string="Source document"),
 
1044
        'origin': fields.related('picking_id','origin',type='char', size=64, relation="stock.picking", string="Origin"),
1102
1045
        'move_stock_return_history': fields.many2many('stock.move', 'stock_move_return_history', 'move_id', 'return_move_id', 'Move Return History',readonly=True),
 
1046
        'scraped': fields.boolean('Scraped'),        
1103
1047
    }
1104
1048
    _constraints = [
1105
1049
        (_check_tracking,
1136
1080
        'location_dest_id': _default_location_destination,
1137
1081
        'state': lambda *a: 'draft',
1138
1082
        'priority': lambda *a: '1',
 
1083
        'scraped' : lambda *a:False,
1139
1084
        'product_qty': lambda *a: 1.0,
1140
1085
        'date_planned': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
1141
1086
        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
1198
1143
 
1199
1144
        return {'value': result}
1200
1145
 
1201
 
    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False):
 
1146
    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, address_id=False):
1202
1147
        if not prod_id:
1203
1148
            return {}
1204
 
        product = self.pool.get('product.product').browse(cr, uid, [prod_id])[0]
 
1149
        lang = False
 
1150
        if address_id:
 
1151
            addr_rec = self.pool.get('res.partner.address').browse(cr, uid, address_id)
 
1152
            if addr_rec:
 
1153
                lang = addr_rec.partner_id and addr_rec.partner_id.lang or False
 
1154
        ctx = {'lang': lang}
 
1155
 
 
1156
        product = self.pool.get('product.product').browse(cr, uid, [prod_id], context=ctx)[0]
1205
1157
        uos_id  = product.uos_id and product.uos_id.id or False
1206
1158
        result = {
1207
1159
            'name': product.partner_ref,
1231
1183
            if dest:
1232
1184
                if dest[1] == 'transparent':
1233
1185
                    self.write(cr, uid, [m.id], {
1234
 
                        'date_planned': (DateTime.strptime(m.date_planned, '%Y-%m-%d %H:%M:%S') + \
1235
 
                            DateTime.RelativeDateTime(days=dest[2] or 0)).strftime('%Y-%m-%d'),
 
1186
                        'date_planned': (datetime.strptime(m.date_planned, '%Y-%m-%d %H:%M:%S') + \
 
1187
                            relativedelta(days=dest[2] or 0)).strftime('%Y-%m-%d'),
1236
1188
                        'location_dest_id': dest[0].id})
1237
1189
                else:
1238
1190
                    result.setdefault(m.picking_id, [])
1268
1220
                        'picking_id': pickid,
1269
1221
                        'state': 'waiting',
1270
1222
                        'move_history_ids': [],
1271
 
                        'date_planned': (DateTime.strptime(move.date_planned, '%Y-%m-%d %H:%M:%S') + DateTime.RelativeDateTime(days=delay or 0)).strftime('%Y-%m-%d'),
 
1223
                        'date_planned': (datetime.strptime(move.date_planned, '%Y-%m-%d %H:%M:%S') + relativedelta(days=delay or 0)).strftime('%Y-%m-%d'),
1272
1224
                        'move_history_ids2': []}
1273
1225
                    )
1274
1226
                    self.pool.get('stock.move').write(cr, uid, [move.id], {
1492
1444
                raise osv.except_osv(_('UserError'),
1493
1445
                        _('You can only delete draft moves.'))
1494
1446
        return super(stock_move, self).unlink(
1495
 
            cr, uid, ids, context=context)
 
1447
            cr, uid, ids, context=context)    
 
1448
 
 
1449
    def _create_lot(self, cr, uid, ids, product_id, prefix=False):
 
1450
        prodlot_obj = self.pool.get('stock.production.lot')
 
1451
        ir_sequence_obj = self.pool.get('ir.sequence')
 
1452
        sequence = ir_sequence_obj.get(cr, uid, 'stock.lot.serial')
 
1453
        if not sequence:
 
1454
            raise wizard.except_wizard(_('Error!'), _('No production sequence defined'))
 
1455
        prodlot_id = prodlot_obj.create(cr, uid, {'name': sequence, 'prefix': prefix}, {'product_id': product_id})
 
1456
        prodlot = prodlot_obj.browse(cr, uid, prodlot_id) 
 
1457
        ref = ','.join(map(lambda x:str(x),ids))
 
1458
        if prodlot.ref:
 
1459
            ref = '%s, %s' % (prodlot.ref, ref) 
 
1460
        prodlot_obj.write(cr, uid, [prodlot_id], {'ref': ref})
 
1461
        return prodlot_id
 
1462
 
 
1463
    def action_scrap(self, cr, uid, ids, quantity, location_id, context=None):
 
1464
        '''
 
1465
        Move the scrap/damaged product into scrap location       
 
1466
        
 
1467
        @ param cr: the database cursor
 
1468
        @ param uid: the user id
 
1469
        @ param ids: ids of stock move object to be scraped
 
1470
        @ param quantity : specify scrap qty
 
1471
        @ param location_id : specify scrap location
 
1472
        @ param context: context arguments
 
1473
 
 
1474
        @ return: Scraped lines
 
1475
        '''   
 
1476
        if quantity <= 0:
 
1477
            raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
 
1478
        res = []       
 
1479
        for move in self.browse(cr, uid, ids, context=context):            
 
1480
            move_qty = move.product_qty
 
1481
            uos_qty = quantity / move_qty * move.product_uos_qty
 
1482
            default_val = {
 
1483
                    'product_qty': quantity, 
 
1484
                    'product_uos_qty': uos_qty, 
 
1485
                    'state': move.state, 
 
1486
                    'scraped' : True,                                     
 
1487
                    'location_dest_id': location_id
 
1488
                }
 
1489
            new_move = self.copy(cr, uid, move.id, default_val)
 
1490
            #self.write(cr, uid, [new_move], {'move_history_ids':[(4,move.id)]}) #TODO : to track scrap moves
 
1491
            res += [new_move]  
 
1492
        self.action_done(cr, uid, res)          
 
1493
        return res
 
1494
 
 
1495
    def action_split(self, cr, uid, ids, quantity, split_by_qty=1, prefix=False, with_lot=True, context=None):
 
1496
        '''
 
1497
        Split Stock Move lines into production lot which specified split by quantity.
 
1498
        
 
1499
        @ param cr: the database cursor
 
1500
        @ param uid: the user id
 
1501
        @ param ids: ids of stock move object to be splited
 
1502
        @ param split_by_qty : specify split by qty
 
1503
        @ param prefix : specify prefix of production lot
 
1504
        @ param with_lot : if true, prodcution lot will assign for split line otherwise not.
 
1505
        @ param context: context arguments
 
1506
 
 
1507
        @ return: splited move lines
 
1508
        '''   
 
1509
 
 
1510
        if quantity <= 0:
 
1511
            raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
 
1512
 
 
1513
        res = []
 
1514
 
 
1515
        for move in self.browse(cr, uid, ids):            
 
1516
            if split_by_qty <= 0 or quantity == 0:
 
1517
                return res
 
1518
 
 
1519
            uos_qty = split_by_qty / move.product_qty * move.product_uos_qty
 
1520
 
 
1521
            quantity_rest = quantity % split_by_qty
 
1522
            uos_qty_rest = split_by_qty / move.product_qty * move.product_uos_qty
 
1523
 
 
1524
            update_val = {
 
1525
                'product_qty': split_by_qty,
 
1526
                'product_uos_qty': uos_qty,
 
1527
            }                         
 
1528
            for idx in range(int(quantity//split_by_qty)):                 
 
1529
                if not idx and move.product_qty<=quantity:
 
1530
                    current_move = move.id
 
1531
                else:
 
1532
                    current_move = self.copy(cr, uid, move.id, {'state': move.state})
 
1533
                res.append(current_move)
 
1534
                if with_lot:
 
1535
                    update_val['prodlot_id'] = self._create_lot(cr, uid, [current_move], move.product_id.id)
 
1536
 
 
1537
                self.write(cr, uid, [current_move], update_val)
 
1538
        
 
1539
            
 
1540
            if quantity_rest > 0:    
 
1541
                idx = int(quantity//split_by_qty)            
 
1542
                update_val['product_qty'] = quantity_rest
 
1543
                update_val['product_uos_qty'] = uos_qty_rest    
 
1544
                if not idx and move.product_qty<=quantity:        
 
1545
                    current_move = move.id                    
 
1546
                else:
 
1547
                    current_move = self.copy(cr, uid, move.id, {'state': move.state}) 
 
1548
                                                    
 
1549
                res.append(current_move)
 
1550
                 
 
1551
                
 
1552
                if with_lot:             
 
1553
                    update_val['prodlot_id'] = self._create_lot(cr, uid, [current_move], move.product_id.id)
 
1554
 
 
1555
                self.write(cr, uid, [current_move], update_val)
 
1556
        return res    
 
1557
 
 
1558
    def action_consume(self, cr, uid, ids, quantity, location_id=False,  context=None):        
 
1559
        '''
 
1560
        Consumed product with specific quatity from specific source location
 
1561
        
 
1562
        @ param cr: the database cursor
 
1563
        @ param uid: the user id
 
1564
        @ param ids: ids of stock move object to be consumed
 
1565
        @ param quantity : specify consume quantity
 
1566
        @ param location_id : specify source location         
 
1567
        @ param context: context arguments
 
1568
 
 
1569
        @ return: Consumed lines
 
1570
        '''   
 
1571
        if not context:
 
1572
            context = {}
 
1573
                
 
1574
        if quantity <= 0:
 
1575
            raise osv.except_osv(_('Warning!'), _('Please provide Proper Quantity !'))
 
1576
 
 
1577
        res = []       
 
1578
        for move in self.browse(cr, uid, ids, context=context):            
 
1579
            move_qty = move.product_qty
 
1580
            quantity_rest = move.product_qty
 
1581
 
 
1582
            quantity_rest -= quantity            
 
1583
            uos_qty_rest = quantity_rest / move_qty * move.product_uos_qty            
 
1584
            if quantity_rest <= 0:
 
1585
                quantity_rest = 0 
 
1586
                uos_qty_rest = 0
 
1587
                quantity = move.product_qty
 
1588
 
 
1589
            uos_qty = quantity / move_qty * move.product_uos_qty
 
1590
 
 
1591
            if quantity_rest > 0:  
 
1592
                default_val = {
 
1593
                    'product_qty': quantity, 
 
1594
                    'product_uos_qty': uos_qty, 
 
1595
                    'state': move.state, 
 
1596
                    'location_id': location_id
 
1597
                }                                              
 
1598
                if move.product_id.track_production and location_id:
 
1599
                    # IF product has checked track for production lot, move lines will be split by 1
 
1600
                    res += self.action_split(cr, uid, [move.id], quantity, split_by_qty=1, context=context)
 
1601
                else:
 
1602
                    current_move = self.copy(cr, uid, move.id, default_val)
 
1603
                    res += [current_move]
 
1604
 
 
1605
                update_val = {}                                  
 
1606
                update_val['product_qty'] = quantity_rest
 
1607
                update_val['product_uos_qty'] = uos_qty_rest                          
 
1608
                self.write(cr, uid, [move.id], update_val) 
 
1609
 
 
1610
            else: 
 
1611
                quantity_rest = quantity    
 
1612
                uos_qty_rest =  uos_qty
 
1613
                
 
1614
                if move.product_id.track_production and location_id:
 
1615
                    res += self.split_lines(cr, uid, [move.id], quantity_rest, split_by_qty=1, context=context)
 
1616
                else:                     
 
1617
                    res += [move.id] 
 
1618
                    update_val = {
 
1619
                        'product_qty' : quantity_rest,
 
1620
                        'product_uos_qty' : uos_qty_rest,
 
1621
                        'location_id': location_id
 
1622
                    }                                                    
 
1623
 
 
1624
                    self.write(cr, uid, [move.id], update_val)
 
1625
 
 
1626
        self.action_done(cr, uid, res)          
 
1627
        return res    
1496
1628
 
1497
1629
stock_move()
1498
1630