~unifield-team/unifield-wm/us-826

« back to all changes in this revision

Viewing changes to service_purchasing/service_purchasing.py

UF-385 [ADD] Added consumption_calculation module

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    OpenERP, Open Source Management Solution
5
 
#    Copyright (C) 2011 MSF, TeMPO Consulting
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU Affero General Public License as
9
 
#    published by the Free Software Foundation, either version 3 of the
10
 
#    License, or (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU Affero General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU Affero General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from osv import osv, fields
23
 
from tools.translate import _
24
 
import netsvc
25
 
from datetime import datetime, timedelta
26
 
from dateutil.relativedelta import relativedelta
27
 
import decimal_precision as dp
28
 
import logging
29
 
import tools
30
 
from os import path
31
 
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
32
 
 
33
 
class product_template(osv.osv):
34
 
    '''
35
 
    add the new service with reception type
36
 
    '''
37
 
    _inherit = "product.template"
38
 
    
39
 
    PRODUCT_TYPE = [
40
 
        ('product','Stockable Product'),
41
 
        ('consu', 'Non-Stockable'),
42
 
        ('service_recep', 'Service with Reception'),
43
 
    ]
44
 
    
45
 
    _columns = {
46
 
        'type': fields.selection(PRODUCT_TYPE, 'Product Type', required=True, help="Will change the way procurements are processed. Consumables are stockable products with infinite stock, or for use when you have no inventory management in the system."),
47
 
    }
48
 
    
49
 
product_template()
50
 
 
51
 
 
52
 
class product_product(osv.osv):
53
 
    '''
54
 
    add on change on type
55
 
    '''
56
 
    _inherit = 'product.product'
57
 
    
58
 
    def on_change_type(self, cr, uid, ids, type, context=None):
59
 
        '''
60
 
        if type is service_with_reception, procure_method is set to make_to_order
61
 
        '''
62
 
        if context is None:
63
 
            context = {}
64
 
        
65
 
        if type in ('consu', 'service', 'service_recep'):
66
 
            return {'value': {'procure_method': 'make_to_order', 'supply_method': 'buy',}}
67
 
        return {}
68
 
    
69
 
    def _check_procurement_for_service_with_recep(self, cr, uid, ids, context=None):
70
 
        """
71
 
        You cannot select Service Location as Source Location.
72
 
        """
73
 
        if context is None:
74
 
            context = {}
75
 
        for obj in self.read(cr, uid, ids, ['type', 'procure_method'], context=context):
76
 
            if obj['type'] in ('consu', 'service', 'service_recep') and obj['procure_method'] != 'make_to_order':
77
 
                raise osv.except_osv(_('Error'), _('You must select on order procurement method for %s products.') % (obj['type']=='consu' and 'Non-stockable' or 'Service'))
78
 
        return True
79
 
    
80
 
    _constraints = [
81
 
        (_check_procurement_for_service_with_recep, 'You must select on order procurement method for Service with Reception products.', []),
82
 
    ]
83
 
    
84
 
product_product()
85
 
 
86
 
 
87
 
class stock_location(osv.osv):
88
 
    '''
89
 
    override stock location to add:
90
 
    - service location (checkbox - boolean)
91
 
    '''
92
 
    _inherit = 'stock.location'
93
 
    
94
 
    _columns = {
95
 
        'service_location': fields.boolean(string='Service Location', readonly=True,),
96
 
    }
97
 
    
98
 
    def get_service_location(self, cr, uid, context=None):
99
 
        ids = self.search(cr, uid, [('service_location', '=', True)])
100
 
        if not ids:
101
 
            raise osv.except_osv(_('Error'), _('You must have a location with "Service Location".'))
102
 
        return ids[0]
103
 
 
104
 
stock_location()
105
 
 
106
 
 
107
 
class stock_move(osv.osv):
108
 
    '''
109
 
    add constraints:
110
 
        - source location cannot be a Service location
111
 
        - if picking_id is not type 'in', cannot select a product service
112
 
        - if product is service, the destination location must be Service location
113
 
        - if destination location is Service, the product must be service
114
 
    
115
 
    on_change on product id
116
 
    '''
117
 
    _inherit = 'stock.move'
118
 
    
119
 
    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, address_id=False, parent_type=False, purchase_line_id=False, out=False):
120
 
        '''
121
 
        if the product is "service with reception" or "service", the destination location is Service
122
 
        '''
123
 
        prod_obj = self.pool.get('product.product')
124
 
        location_obj = self.pool.get('stock.location')
125
 
        
126
 
        result = super(stock_move, self).onchange_product_id(cr, uid, ids, prod_id, loc_id, loc_dest_id, address_id, parent_type, purchase_line_id,out)
127
 
        
128
 
        product_type = False
129
 
        location_id = loc_id and location_obj.browse(cr, uid, loc_id) or False
130
 
        location_dest_id = loc_dest_id and location_obj.browse(cr, uid, loc_dest_id) or False
131
 
        service_loc = location_obj.get_service_location(cr, uid)
132
 
        non_stockable_loc = location_obj.search(cr, uid, [('non_stockable_ok', '=', True)])
133
 
        id_cross = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
134
 
        input_id = location_obj.search(cr, uid, [('input_ok', '=', True)])
135
 
        po = purchase_line_id and self.pool.get('purchase.order.line').browse(cr, uid, purchase_line_id) or False
136
 
        cd = po and po.order_id.cross_docking_ok or False
137
 
        packing_ids = []
138
 
        stock_ids = []
139
 
        
140
 
        wh_ids = self.pool.get('stock.warehouse').search(cr, uid, [])
141
 
        for wh in self.pool.get('stock.warehouse').browse(cr, uid, wh_ids):
142
 
            packing_ids.append(wh.lot_packing_id.id)
143
 
            stock_ids.append(wh.lot_stock_id.id)
144
 
        
145
 
        vals = {}
146
 
        
147
 
        if prod_id:
148
 
            product_type = prod_obj.browse(cr, uid, prod_id).type
149
 
            vals.update({'product_type': product_type})
150
 
        else:
151
 
            vals.update({'product_type': False})
152
 
 
153
 
        if non_stockable_loc:
154
 
            non_stockable_loc = non_stockable_loc[0]
155
 
            
156
 
        if input_id:
157
 
            input_id = input_id[0]
158
 
            
159
 
        if not prod_id:
160
 
            if parent_type == 'in':
161
 
                vals.update(location_dest_id=False)
162
 
            elif parent_type == 'out':
163
 
                vals.update(location_id=False)
164
 
            else:
165
 
                vals.update(location_id=False, location_dest_id=False)
166
 
            
167
 
        # Case when incoming shipment
168
 
        if product_type and parent_type == 'in':
169
 
            # Set service location as destination for service products
170
 
            if product_type in ('service_recep', 'service'):
171
 
                if service_loc:
172
 
                    vals.update(location_dest_id=service_loc, product_type=product_type)
173
 
            # Set cross-docking as destination for non-stockable with cross-docking context
174
 
            elif product_type == 'consu' and cd and loc_dest_id not in (id_cross, service_loc):
175
 
                vals.update(location_dest_id=id_cross)
176
 
            # Set non-stockable as destination for non-stockable without cross-docking context
177
 
            elif product_type == 'consu' and not cd and loc_dest_id not in (id_cross, service_loc):
178
 
                vals.update(location_dest_id=non_stockable_loc)
179
 
            # Set input for standard products
180
 
            elif product_type == 'product' and not (loc_dest_id and (not location_dest_id.non_stockable_ok and (location_dest_id.usage == 'internal' or location_dest_id.virtual_ok))):
181
 
                vals.update(location_dest_id=input_id)
182
 
        # Case when internal picking
183
 
        elif product_type and parent_type == 'internal':
184
 
            # Source location
185
 
            # Only cross-docking is available for Internal move for non-stockable products
186
 
            if product_type == 'consu' and not (loc_id and (location_id.cross_docking_location_ok or location_id.quarantine_location)):
187
 
                vals.update(location_id=id_cross)
188
 
            elif product_type == 'product' and not (loc_id and (location_id.usage == 'internal' or location_dest_id.virtual_ok)):
189
 
                vals.update(location_id=stock_ids and stock_ids[0] or False)
190
 
            elif product_type == 'service_recep':
191
 
                vals.update(location_id=id_cross)
192
 
            # Destination location
193
 
            if product_type == 'consu' and not (loc_dest_id and (location_dest_id.usage == 'inventory' or location_dest_id.destruction_location or location_dest_id.quarantine_location)):
194
 
                vals.update(location_dest_id=non_stockable_loc)
195
 
            elif product_type == 'product' and not (loc_dest_id and (not location_dest_id.non_stockable_ok and (location_dest_id.usage == 'internal' or location_dest_id.virtual_ok))):
196
 
                vals.update(location_dest_id=False)
197
 
            elif product_type == 'service_recep':
198
 
                vals.update(location_dest_id=service_loc)
199
 
        # Case when outgoing delivery or picking ticket
200
 
        elif product_type and parent_type == 'out':
201
 
            # Source location
202
 
            # Only cross-docking is available for Outgoing moves for non-stockable products
203
 
            if product_type == 'consu' and not (loc_id and location_id.cross_docking_location_ok):
204
 
                vals.update(location_id=id_cross)
205
 
            elif product_type == 'product' and not (loc_id and (location_id.usage == 'internal' or not location_id.quarantine_location or not location_id.output_ok or not location_id.input_ok)):
206
 
                vals.update(location_id=stock_ids and stock_ids[0] or False)
207
 
            elif product_type == 'service_recep':
208
 
                vals.update(location_id=id_cross)
209
 
            # Destinatio location
210
 
            if product_type == 'consu' and not (loc_dest_id and (location_dest_id.output_ok or location_dest_id.usage == 'customer')):
211
 
                # If we are not in Picking ticket and the dest. loc. is not output or customer, unset the dest.
212
 
                if loc_id and loc_id not in packing_ids:
213
 
                    vals.update(location_dest_id=False)
214
 
                
215
 
        if not result.get('value'):
216
 
            result['value'] = vals
217
 
        else:
218
 
            result['value'].update(vals)
219
 
 
220
 
        return result
221
 
    
222
 
    def _check_constaints_service(self, cr, uid, ids, context=None):
223
 
        """
224
 
        You cannot select Service Location as Source Location.
225
 
        """
226
 
        if context is None:
227
 
            context = {}
228
 
        if ids:
229
 
            cr.execute("""select 
230
 
                count(pick.type = 'in' and t.type in ('service_recep', 'service') and not dest.service_location and not dest.cross_docking_location_ok or NULL),
231
 
                count(pick.type = 'internal' and not src.cross_docking_location_ok and t.type in ('service_recep', 'service') or NULL),
232
 
                count(pick.type = 'internal' and not dest.service_location and t.type in ('service_recep', 'service') or NULL),
233
 
                count(t.type in ('service_recep', 'service') and pick.type = 'out' and pick.subtype in ('standard', 'picking') and not src.cross_docking_location_ok or NULL),
234
 
                count(t.type not in ('service_recep', 'service') and (dest.service_location or src.service_location ) or NULL)
235
 
                from stock_move m
236
 
                left join stock_picking pick on m.picking_id = pick.id
237
 
                left join product_product p on m.product_id = p.id
238
 
                left join product_template t on p.product_tmpl_id = t.id
239
 
                left join stock_location src on m.location_id = src.id
240
 
                left join stock_location dest on m.location_dest_id = dest.id
241
 
            where m.id in %s""", (tuple(ids),))
242
 
            for c in cr.fetchall():
243
 
                if c[0]:
244
 
                    raise osv.except_osv(_('Error'), _('Service Products must have Service or Cross Docking Location as Destination Location.'))
245
 
                if c[1]:
246
 
                    raise osv.except_osv(_('Error'), _('Service Products must have Cross Docking Location as Source Location.'))
247
 
                if c[2]:
248
 
                    raise osv.except_osv(_('Error'), _('Service Products must have Service Location as Destination Location.'))
249
 
                if c[3]:
250
 
                    raise osv.except_osv(_('Error'), _('Service Products must have Cross Docking Location as Source Location.'))
251
 
                if c[4]:
252
 
                    raise osv.except_osv(_('Error'), _('Service Location cannot be used for non Service Products.'))
253
 
        return True
254
 
 
255
 
    
256
 
    _constraints = [
257
 
        (_check_constaints_service, 'You cannot select Service Location as Source Location.', []),
258
 
    ]
259
 
 
260
 
stock_move()
261
 
 
262
 
 
263
 
class sale_order_line(osv.osv):
264
 
    '''
265
 
    add a constraint as service with reception products are only available with on order procurement method
266
 
    '''
267
 
    _inherit = 'sale.order.line'
268
 
    
269
 
    def _check_procurement_for_service_with_recep(self, cr, uid, ids, context=None):
270
 
        """
271
 
        You cannot select Service Location as Source Location.
272
 
        """
273
 
        if context is None:
274
 
            context = {}
275
 
        for obj in self.browse(cr, uid, ids, context=context):
276
 
            if obj.product_id.type == 'service_recep' and obj.type != 'make_to_order':
277
 
                raise osv.except_osv(_('Error'), _('You must select on order procurement method for Service with Reception products.'))
278
 
        return True
279
 
    
280
 
    _constraints = [
281
 
        (_check_procurement_for_service_with_recep, 'You must select on order procurement method for Service with Reception products.', []),
282
 
    ]
283
 
    
284
 
sale_order_line()
285
 
 
286
 
 
287
 
class sourcing_line(osv.osv):
288
 
    '''
289
 
    add a constraint as service with reception products are only available with on order procurement method
290
 
    '''
291
 
    _inherit = 'sourcing.line'
292
 
    
293
 
    def _check_procurement_for_service_with_recep(self, cr, uid, ids, context=None):
294
 
        """
295
 
        You cannot select Service Location as Source Location.
296
 
        """
297
 
        if context is None:
298
 
            context = {}
299
 
        for obj in self.browse(cr, uid, ids, context=context):
300
 
            if obj.product_id.type == 'service_recep' and obj.type != 'make_to_order':
301
 
                raise osv.except_osv(_('Error'), _('You must select on order procurement method for Service with Reception products.'))
302
 
        return True
303
 
    
304
 
    _constraints = [
305
 
        (_check_procurement_for_service_with_recep, 'You must select on order procurement method for Service with Reception products.', []),
306
 
    ]
307
 
    
308
 
sourcing_line()
309
 
 
310
 
 
311
 
class purchase_order(osv.osv):
312
 
    '''
313
 
    add constraint
314
 
    the function is modified to take into account the new service with reception as stockable product
315
 
    '''
316
 
    _inherit = 'purchase.order'
317
 
    
318
 
#    def _check_purchase_category(self, cr, uid, ids, context=None):
319
 
#        """
320
 
#        Purchase Order of type Category Service should contain only Service Products.
321
 
#        """
322
 
#        if context is None:
323
 
#            context = {}
324
 
#        for obj in self.browse(cr, uid, ids, context=context):
325
 
#            if obj.categ == 'service':
326
 
#                for line in obj.order_line:
327
 
#                    if not line.product_id or line.product_id.type not in ('service_recep', 'service',):
328
 
#                        return False
329
 
#        return True
330
 
    
331
 
    def has_stockable_product(self,cr, uid, ids, *args):
332
 
        '''
333
 
        service with reception is considered as stockable product and produce therefore an incoming shipment and corresponding stock moves
334
 
        '''
335
 
        result = super(purchase_order, self).has_stockable_product(cr, uid, ids, *args)
336
 
        for order in self.browse(cr, uid, ids):
337
 
            for order_line in order.order_line:
338
 
                if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('service_recep',) and order.order_type != 'direct':
339
 
                    return True
340
 
                
341
 
        return result
342
 
     
343
 
#    by QT : Remove the constraint because if you change the Order category from 'Service' to 'Medical' and try to add a non-service product,
344
 
#            the constraint returns False
345
 
    _constraints = [
346
 
#        (_check_purchase_category, 'Purchase Order of type Category Service should contain only Service Products.', ['categ']),
347
 
    ]
348
 
    
349
 
purchase_order()
350
 
 
351
 
 
352
 
#    by QT : Remove the constraint because if you change the Order category from 'Service' to 'Medical' and try to add a non-service product,
353
 
#            the constraint returns False
354
 
#class purchase_order_line(osv.osv):
355
 
#    '''
356
 
#    add constraint
357
 
#    '''
358
 
#    _inherit = 'purchase.order.line'
359
 
#    
360
 
#    def _check_purchase_order_category(self, cr, uid, ids, context=None):
361
 
#        """
362
 
#        Purchase Order of type Category Service should contain only Service Products.
363
 
#        """
364
 
#        if context is None:
365
 
#            context = {}
366
 
#        for obj in self.browse(cr, uid, ids, context=context):
367
 
#            if obj.product_id.type not in ('service_recep', 'service',) and obj.order_id.categ == 'service':
368
 
#                return False
369
 
#        return True
370
 
#    
371
 
#    _constraints = [
372
 
#        (_check_purchase_order_category, 'Purchase Order of type Category Service should contain only Service Products.', ['product_id']),
373
 
#    ]
374
 
#    
375
 
#purchase_order_line()
376
 
 
377
 
 
378
 
class stock_picking(osv.osv):
379
 
    '''
380
 
    add a new field order_category, which reflects the order_category of corresponding sale order/purchase order
381
 
    '''
382
 
    _inherit = 'stock.picking'
383
 
    
384
 
    def _vals_get23(self, cr, uid, ids, fields, arg, context=None):
385
 
        '''
386
 
        get the order category if sale_id or purchase_id exists
387
 
        '''
388
 
        if context is None:
389
 
            context = {}
390
 
        
391
 
        result = {}
392
 
            
393
 
        for obj in self.browse(cr, uid, ids, context=context):
394
 
            result[obj.id] = {}
395
 
            # initialize the dic
396
 
            for f in fields:
397
 
                result[obj.id].update({f:False,})
398
 
            # a sale order is linked, we gather the categ
399
 
            if obj.sale_id:
400
 
                result[obj.id]['order_category'] = obj.sale_id.categ
401
 
            # a purchase order is linked, we gather the categ    
402
 
            elif obj.purchase_id:
403
 
                result[obj.id]['order_category'] = obj.purchase_id.categ
404
 
                
405
 
        return result
406
 
    
407
 
    def _get_purchase_ids(self, cr, uid, ids, context=None):
408
 
        '''
409
 
        ids represents the ids of purchase order objects for which categ has changed
410
 
        
411
 
        return the list of ids of stock picking object which need to get their category field updated
412
 
        '''
413
 
        if context is None:
414
 
            context = {}
415
 
        picking_obj = self.pool.get('stock.picking')
416
 
        # all stock picking which are linked to the changing purchase order
417
 
        result = picking_obj.search(cr, uid, [('purchase_id', 'in', ids)], context=context)
418
 
        return result
419
 
    
420
 
    def _get_sale_ids(self, cr, uid, ids, context=None):
421
 
        '''
422
 
        ids represents the ids of sale order objects for which categ has changed
423
 
        
424
 
        return the list of ids of stock picking object which need to get their category field updated
425
 
        '''
426
 
        if context is None:
427
 
            context = {}
428
 
        picking_obj = self.pool.get('stock.picking')
429
 
        # all stock picking which are linked to the changing sale order
430
 
        result = picking_obj.search(cr, uid, [('sale_id', 'in', ids)], context=context)
431
 
        return result
432
 
    
433
 
    _columns = {
434
 
            'order_category': fields.function(_vals_get23, method=True, type='selection', selection=ORDER_CATEGORY, string='Order Category', multi='vals_get23', readonly=True,
435
 
                store= {
436
 
                    'stock.picking': (lambda obj, cr, uid, ids, context: ids, ['purchase_id', 'sale_id'], 10),
437
 
                    'purchase.order': (_get_purchase_ids, ['categ',], 10),
438
 
                    'sale.order': (_get_sale_ids, ['categ',], 10),
439
 
                },
440
 
            ),
441
 
    }
442
 
 
443
 
stock_picking()