1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 MSF, TeMPO Consulting
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.
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.
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/>.
20
##############################################################################
22
from osv import osv, fields
23
from tools.translate import _
25
from datetime import datetime, timedelta
26
from dateutil.relativedelta import relativedelta
27
import decimal_precision as dp
31
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
33
class product_template(osv.osv):
35
add the new service with reception type
37
_inherit = "product.template"
40
('product','Stockable Product'),
41
('consu', 'Non-Stockable'),
42
('service_recep', 'Service with Reception'),
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."),
52
class product_product(osv.osv):
56
_inherit = 'product.product'
58
def on_change_type(self, cr, uid, ids, type, context=None):
60
if type is service_with_reception, procure_method is set to make_to_order
65
if type in ('consu', 'service', 'service_recep'):
66
return {'value': {'procure_method': 'make_to_order', 'supply_method': 'buy',}}
69
def _check_procurement_for_service_with_recep(self, cr, uid, ids, context=None):
71
You cannot select Service Location as Source Location.
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'))
81
(_check_procurement_for_service_with_recep, 'You must select on order procurement method for Service with Reception products.', []),
87
class stock_location(osv.osv):
89
override stock location to add:
90
- service location (checkbox - boolean)
92
_inherit = 'stock.location'
95
'service_location': fields.boolean(string='Service Location', readonly=True,),
98
def get_service_location(self, cr, uid, context=None):
99
ids = self.search(cr, uid, [('service_location', '=', True)])
101
raise osv.except_osv(_('Error'), _('You must have a location with "Service Location".'))
107
class stock_move(osv.osv):
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
115
on_change on product id
117
_inherit = 'stock.move'
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):
121
if the product is "service with reception" or "service", the destination location is Service
123
prod_obj = self.pool.get('product.product')
124
location_obj = self.pool.get('stock.location')
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)
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
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)
148
product_type = prod_obj.browse(cr, uid, prod_id).type
149
vals.update({'product_type': product_type})
151
vals.update({'product_type': False})
153
if non_stockable_loc:
154
non_stockable_loc = non_stockable_loc[0]
157
input_id = input_id[0]
160
if parent_type == 'in':
161
vals.update(location_dest_id=False)
162
elif parent_type == 'out':
163
vals.update(location_id=False)
165
vals.update(location_id=False, location_dest_id=False)
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'):
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':
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':
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)
215
if not result.get('value'):
216
result['value'] = vals
218
result['value'].update(vals)
222
def _check_constaints_service(self, cr, uid, ids, context=None):
224
You cannot select Service Location as Source Location.
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)
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():
244
raise osv.except_osv(_('Error'), _('Service Products must have Service or Cross Docking Location as Destination Location.'))
246
raise osv.except_osv(_('Error'), _('Service Products must have Cross Docking Location as Source Location.'))
248
raise osv.except_osv(_('Error'), _('Service Products must have Service Location as Destination Location.'))
250
raise osv.except_osv(_('Error'), _('Service Products must have Cross Docking Location as Source Location.'))
252
raise osv.except_osv(_('Error'), _('Service Location cannot be used for non Service Products.'))
257
(_check_constaints_service, 'You cannot select Service Location as Source Location.', []),
263
class sale_order_line(osv.osv):
265
add a constraint as service with reception products are only available with on order procurement method
267
_inherit = 'sale.order.line'
269
def _check_procurement_for_service_with_recep(self, cr, uid, ids, context=None):
271
You cannot select Service Location as Source Location.
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.'))
281
(_check_procurement_for_service_with_recep, 'You must select on order procurement method for Service with Reception products.', []),
287
class sourcing_line(osv.osv):
289
add a constraint as service with reception products are only available with on order procurement method
291
_inherit = 'sourcing.line'
293
def _check_procurement_for_service_with_recep(self, cr, uid, ids, context=None):
295
You cannot select Service Location as Source Location.
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.'))
305
(_check_procurement_for_service_with_recep, 'You must select on order procurement method for Service with Reception products.', []),
311
class purchase_order(osv.osv):
314
the function is modified to take into account the new service with reception as stockable product
316
_inherit = 'purchase.order'
318
# def _check_purchase_category(self, cr, uid, ids, context=None):
320
# Purchase Order of type Category Service should contain only Service Products.
322
# if context is None:
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',):
331
def has_stockable_product(self,cr, uid, ids, *args):
333
service with reception is considered as stockable product and produce therefore an incoming shipment and corresponding stock moves
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':
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
346
# (_check_purchase_category, 'Purchase Order of type Category Service should contain only Service Products.', ['categ']),
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):
358
# _inherit = 'purchase.order.line'
360
# def _check_purchase_order_category(self, cr, uid, ids, context=None):
362
# Purchase Order of type Category Service should contain only Service Products.
364
# if context is None:
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':
372
# (_check_purchase_order_category, 'Purchase Order of type Category Service should contain only Service Products.', ['product_id']),
375
#purchase_order_line()
378
class stock_picking(osv.osv):
380
add a new field order_category, which reflects the order_category of corresponding sale order/purchase order
382
_inherit = 'stock.picking'
384
def _vals_get23(self, cr, uid, ids, fields, arg, context=None):
386
get the order category if sale_id or purchase_id exists
393
for obj in self.browse(cr, uid, ids, context=context):
397
result[obj.id].update({f:False,})
398
# a sale order is linked, we gather the categ
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
407
def _get_purchase_ids(self, cr, uid, ids, context=None):
409
ids represents the ids of purchase order objects for which categ has changed
411
return the list of ids of stock picking object which need to get their category field updated
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)
420
def _get_sale_ids(self, cr, uid, ids, context=None):
422
ids represents the ids of sale order objects for which categ has changed
424
return the list of ids of stock picking object which need to get their category field updated
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)
434
'order_category': fields.function(_vals_get23, method=True, type='selection', selection=ORDER_CATEGORY, string='Order Category', multi='vals_get23', readonly=True,
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),