23
23
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
24
from tools.translate import _
26
from mx.DateTime import Parser
27
from mx.DateTime import RelativeDateTime
28
from time import strftime
29
from osv.orm import browse_record, browse_null
26
from mx.DateTime import *
30
29
from workflow.wkf_expr import _eval_expr
32
32
from dateutil.relativedelta import relativedelta
33
33
from datetime import datetime
35
import decimal_precision as dp
37
35
from purchase_override import PURCHASE_ORDER_STATE_SELECTION
39
37
class purchase_order_confirm_wizard(osv.osv):
40
38
_name = 'purchase.order.confirm.wizard'
43
41
'order_id': fields.many2one('purchase.order', string='Purchase Order', readonly=True),
44
42
'errors': fields.text(string='Error message', readonly=True),
47
45
def validate_order(self, cr, uid, ids, context=None):
48
46
wf_service = netsvc.LocalService("workflow")
49
47
for wiz in self.browse(cr, uid, ids, context=context):
50
48
wf_service.trg_validate(uid, 'purchase.order', wiz.order_id.id, 'purchase_confirmed_wait', cr)
51
49
return {'type': 'ir.actions.act_window_close'}
53
51
purchase_order_confirm_wizard()
55
53
class purchase_order(osv.osv):
56
54
_name = 'purchase.order'
57
55
_inherit = 'purchase.order'
59
def update_supplier_info(self, cr, uid, ids, context=None, *args, **kwargs):
61
update the supplier info of corresponding products
63
info_obj = self.pool.get('product.supplierinfo')
64
pricelist_info_obj = self.pool.get('pricelist.partnerinfo')
65
for rfq in self.browse(cr, uid, ids, context=context):
66
for line in rfq.order_line:
67
# if the price is updated and a product selected
68
if line.price_unit and line.product_id:
70
product = line.product_id
71
# find the corresponding suppinfo with sequence -99
72
info_99_list = info_obj.search(cr, uid, [('product_id', '=', product.product_tmpl_id.id),
73
('sequence', '=', -99),], context=context)
77
info_obj.unlink(cr, uid, info_99_list, context=context)
80
values = {'name': rfq.partner_id.id,
81
'product_name': False,
82
'product_code': False,
84
#'product_uom': line.product_uom.id,
87
'product_id' : product.product_tmpl_id.id,
88
'delay' : int(rfq.partner_id.default_delay),
89
#'pricelist_ids': created just after
90
#'company_id': default value
93
new_info_id = info_obj.create(cr, uid, values, context=context)
94
# price lists creation - 'pricelist.partnerinfo
95
values = {'suppinfo_id': new_info_id,
97
'price': line.price_unit,
98
'uom_id': line.product_uom.id,
99
'currency_id': line.currency_id.id,
100
'valid_till': rfq.valid_till,
101
'purchase_order_line_id': line.id,
102
'comment': 'RfQ original quantity for price : %s' % line.product_qty,
104
pricelist_info_obj.create(cr, uid, values, context=context)
108
def generate_po_from_rfq(self, cr, uid, ids, context=None):
110
generate a po from the selected request for quotation
113
line_obj = self.pool.get('purchase.order.line')
118
if isinstance(ids, (int, long)):
122
self.update_supplier_info(cr, uid, ids, context=context)
123
# copy the po with rfq_ok set to False
124
data = self.read(cr, uid, ids[0], ['name'], context=context)
125
new_po_id = self.copy(cr, uid, ids[0], {'name': False, 'rfq_ok': False, 'origin': data['name']}, context=dict(context,keepOrigin=True))
126
# Remove lines with 0.00 as unit price
127
no_price_line_ids = line_obj.search(cr, uid, [
128
('order_id', '=', new_po_id),
129
('price_unit', '=', 0.00),
131
line_obj.unlink(cr, uid, no_price_line_ids, context=context)
133
data = self.read(cr, uid, new_po_id, ['name'], context=context)
134
# log message describing the previous action
135
self.log(cr, uid, new_po_id, _('The Purchase Order %s has been generated from Request for Quotation.')%data['name'])
136
# close the current po
137
wf_service = netsvc.LocalService("workflow")
138
wf_service.trg_validate(uid, 'purchase.order', ids[0], 'rfq_done', cr)
142
def copy(self, cr, uid, p_id, default=None, context=None):
57
def copy(self, cr, uid, id, default=None, context=None):
144
59
Remove loan_id field on new purchase.order
151
# if the copy comes from the button duplicate
152
if context.get('from_button'):
153
default.update({'is_a_counterpart': False})
154
default.update({'loan_id': False, 'merged_line_ids': False, 'partner_ref': False})
155
if not context.get('keepOrigin', False):
156
default.update({'origin': False})
158
return super(purchase_order, self).copy(cr, uid, p_id, default, context=context)
63
default.update({'loan_id': False, 'merged_line_ids': False, 'origin': False})
64
return super(purchase_order, self).copy(cr, uid, id, default, context=context)
160
66
# @@@purchase.purchase_order._invoiced
161
67
def _invoiced(self, cursor, user, ids, name, arg, context=None):
186
92
res[purchase.id] = 0.0
190
96
def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
192
98
Returns the Unifield configuration value
195
101
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
197
103
for order in ids:
198
104
res[order] = setup.allocation_setup
202
def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
204
for order in self.browse(cr, uid, ids, context=context):
207
res[order.id] = False
210
def _po_from_x(self, cr, uid, ids, field_names, args, context=None):
211
"""fields.function multi for 'po_from_ir' and 'po_from_fo' fields."""
213
pol_obj = self.pool.get('purchase.order.line')
214
sol_obj = self.pool.get('sale.order.line')
215
for po_data in self.read(cr, uid, ids, ['order_line'], context=context):
216
res[po_data['id']] = {'po_from_ir': False, 'po_from_fo': False}
217
pol_ids = po_data.get('order_line')
219
pol_datas = pol_obj.read(
220
cr, uid, pol_ids, ['procurement_id'], context=context)
221
proc_ids = [pol['procurement_id'][0]
222
for pol in pol_datas if pol.get('procurement_id')]
225
sol_ids = sol_obj.search(
227
[('procurement_id', 'in', proc_ids)],
229
res[po_data['id']]['po_from_ir'] = bool(sol_ids)
231
sol_ids = sol_obj.search(
233
[('procurement_id', 'in', proc_ids),
234
('order_id.procurement_request', '=', False)],
236
res[po_data['id']]['po_from_fo'] = bool(sol_ids)
239
def _get_dest_partner_names(self, cr, uid, ids, field_name, args, context=None):
241
for po_r in self.read(cr, uid, ids, ['dest_partner_ids'], context=context):
243
if po_r['dest_partner_ids']:
244
name_tuples = self.pool.get('res.partner').name_get(cr, uid, po_r['dest_partner_ids'], context=context)
246
names_list = [nt[1] for nt in name_tuples]
247
names = "; ".join(names_list)
248
res[po_r['id']] = names
251
def _get_project_ref(self, cr, uid, ids, field_name, args, context=None):
253
Get the name of the POs at project side
255
if isinstance(ids, (int, long)):
261
so_ids = self.get_so_ids_from_po_ids(cr, uid, po, context=context)
262
for so in self.pool.get('sale.order').browse(cr, uid, so_ids, context=context):
263
if so.client_order_ref:
266
res[po] += so.client_order_ref
271
'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
272
('donation_st', 'Standard donation'), ('loan', 'Loan'),
109
'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
110
('donation_st', 'Standard donation'), ('loan', 'Loan'),
273
111
('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
274
('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
112
('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
275
113
'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
276
114
'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
277
115
'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
278
# we increase the size of the 'details' field from 30 to 86
279
'details': fields.char(size=86, string='Details', states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
116
'details': fields.char(size=30, string='Details', states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
280
117
'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
281
118
'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
282
119
'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
283
120
'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
284
121
'date_order':fields.date(string='Creation Date', readonly=True, required=True,
285
122
states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
286
'name': fields.char('Order Reference', size=64, required=True, select=True, readonly=True,
123
'name': fields.char('Order Reference', size=64, required=True, select=True, states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)]},
287
124
help="unique number of the purchase order,computed automatically when the purchase order is created"),
288
125
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
289
126
'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'rfq_sent':[('readonly',False)], 'confirmed': [('readonly',False)]}),
290
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)],'cancel':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
127
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)],'cancel':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
291
128
'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
292
states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
129
states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
293
130
'dest_partner_id': fields.many2one('res.partner', string='Destination partner', domain=[('partner_type', '=', 'internal')]),
294
'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
131
'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
295
132
help="The address where the invoice will be sent."),
296
133
'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
297
134
help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
306
143
('unallocated', 'Unallocated'),
307
144
('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
308
145
'unallocation_ok': fields.boolean(string='Unallocated PO'),
309
# we increase the size of the partner_ref field from 64 to 128
310
'partner_ref': fields.char('Supplier Reference', size=128),
146
'partner_ref': fields.char('Supplier Reference', size=64),
311
147
'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
312
'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
313
'active': fields.boolean('Active', readonly=True),
314
'po_from_ir': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from IR ?', multi='po_from_x'),
315
'po_from_fo': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from FO ?', multi='po_from_x'),
316
'canceled_end': fields.boolean(string='Canceled End', readonly=True),
317
'is_a_counterpart': fields.boolean('Counterpart?', help="This field is only for indicating that the order is a counterpart"),
318
'po_updated_by_sync': fields.boolean('PO updated by sync', readonly=False),
319
'origin': fields.text('Source Document',
320
help="Reference of the document that generated this purchase order request."),
321
# UF-2267: Store also the parent PO as reference in the sourced PO
322
'parent_order_name': fields.many2one('purchase.order', string='Parent PO name', help='If the PO is created from a re-source FO, this field contains the relevant original PO name'),
323
'project_ref': fields.char(size=256, string='Project Ref.'),
324
'message_esc': fields.text(string='ESC Message'),
325
'fnct_project_ref': fields.function(_get_project_ref, method=True, string='Project Ref.',
326
type='char', size=256, store=False,),
327
'dest_partner_ids': fields.many2many('res.partner', 'res_partner_purchase_order_rel', 'purchase_order_id', 'partner_id', 'Customers'), # uf-2223
328
'dest_partner_names': fields.function(_get_dest_partner_names, type='string', string='Customers', method=True), # uf-2223
329
'split_po': fields.boolean('Created by split PO', readonly=True),
333
151
'order_type': lambda *a: 'regular',
334
152
'priority': lambda *a: 'normal',
335
153
'categ': lambda *a: 'other',
336
154
'loan_duration': 2,
337
155
'from_yml_test': lambda *a: False,
338
'invoice_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.partner_id.id, ['invoice'])['invoice'],
156
'invoice_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.id, ['invoice'])['invoice'],
339
157
'invoice_method': lambda *a: 'picking',
340
'dest_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.partner_id.id, ['delivery'])['delivery'],
341
'no_line': lambda *a: True,
343
'name': lambda *a: False,
344
'is_a_counterpart': False,
345
'parent_order_name': False,
346
'canceled_end': False,
158
'dest_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.id, ['delivery'])['delivery']
350
def _check_po_from_fo(self, cr, uid, ids, context=None):
354
for po in self.browse(cr, uid, ids, context=context):
355
if po.partner_id.partner_type == 'internal' and po.po_from_fo:
360
(_check_po_from_fo, 'You cannot choose an internal supplier for this purchase order', []),
363
def _check_service(self, cr, uid, ids, vals, context=None):
365
Avoid the saving of a PO with non service products on Service PO
367
# UTP-871 : Remove check of service
370
if isinstance(ids, (int, long)):
374
if context.get('import_in_progress'):
377
for order in self.browse(cr, uid, ids, context=context):
378
for line in order.order_line:
379
if vals.get('categ', order.categ) == 'transport' and line.product_id and (line.product_id.type not in ('service', 'service_recep') or not line.product_id.transport_ok):
380
raise osv.except_osv(_('Error'), _('The product [%s]%s is not a \'Transport\' product. You can purchase only \'Transport\' products on a \'Transport\' purchase order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
382
elif vals.get('categ', order.categ) == 'service' and line.product_id and line.product_id.type not in ('service', 'service_recep'):
383
raise osv.except_osv(_('Error'), _('The product [%s] %s is not a \'Service\' product. You can purchase only \'Service\' products on a \'Service\' purchase order. Please remove this line.') % (line.product_id.default_code, line.product_id.name))
388
def purchase_cancel(self, cr, uid, ids, context=None):
390
Call the wizard to ask if you want to re-source the line
392
line_obj = self.pool.get('purchase.order.line')
393
wiz_obj = self.pool.get('purchase.order.cancel.wizard')
394
wf_service = netsvc.LocalService("workflow")
399
if isinstance(ids, (int, long)):
402
for po in self.browse(cr, uid, ids, context=context):
403
for l in po.order_line:
404
if line_obj.get_sol_ids_from_pol_ids(cr, uid, [l.id], context=context):
405
wiz_id = wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
406
return {'type': 'ir.actions.act_window',
407
'res_model': 'purchase.order.cancel.wizard',
414
wf_service.trg_validate(uid, 'purchase.order', po.id, 'purchase_cancel', cr)
418
def unlink(self, cr, uid, ids, context=None):
420
No unlink for PO linked to a FO
422
if self.get_so_ids_from_po_ids(cr, uid, ids, context=context):
423
raise osv.except_osv(_('Error'), _('You cannot remove a Purchase order that is linked to a Field Order or an Internal Request. Please cancel it instead.'))
425
return super(purchase_order, self).unlink(cr, uid, ids, context=context)
427
def _check_restriction_line(self, cr, uid, ids, context=None):
429
Check restriction on products
431
if isinstance(ids, (int, long)):
434
line_obj = self.pool.get('purchase.order.line')
437
for order in self.browse(cr, uid, ids, context=context):
438
res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in order.order_line], context=context)
443
161
def default_get(self, cr, uid, fields, context=None):
445
163
Fill the unallocated_ok field according to Unifield setup
447
165
res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
449
166
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
450
168
res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
451
169
if setup.allocation_setup == 'unallocated':
452
170
res.update({'unallocation_ok': True})
454
res.update({'name': False})
459
174
def _check_user_company(self, cr, uid, company_id, context=None):
461
176
Remove the possibility to make a PO to user's company
503
207
partner_obj = self.pool.get('res.partner')
505
# the domain on the onchange was replace by a several fields.function that you can retrieve in the
506
# file msf_custom_settings/view/purchase_view.xml: domain="[('supplier', '=', True), ('id', '!=', company_id), ('check_partner_po', '=', order_type), ('check_partner_rfq', '=', tender_id)]"
507
# d = {'partner_id': []}
209
d = {'partner_id': []}
509
211
local_market = None
511
213
# Search the local market partner id
512
214
data_obj = self.pool.get('ir.model.data')
513
215
data_id = data_obj.search(cr, uid, [('module', '=', 'order_types'), ('model', '=', 'res.partner'), ('name', '=', 'res_partner_local_market')] )
515
217
local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
517
219
if order_type == 'loan':
518
220
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
520
222
if not setup.field_orders_ok:
521
223
return {'value': {'order_type': 'regular'},
522
224
'warning': {'title': 'Error',
523
225
'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
525
227
if order_type in ['donation_exp', 'donation_st', 'loan']:
526
228
v['invoice_method'] = 'manual'
527
elif order_type in ['direct']:
229
elif order_type in ['direct', 'purchase_list']:
528
230
v['invoice_method'] = 'order'
529
elif order_type in ['in_kind', 'purchase_list']:
231
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
232
elif order_type in ['in_kind']:
530
233
v['invoice_method'] = 'picking'
234
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
532
236
v['invoice_method'] = 'picking'
534
company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
536
if order_type == 'direct' and dest_partner_id and dest_partner_id != company_id:
238
if order_type == 'direct' and dest_partner_id:
537
239
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
538
240
v.update({'dest_address_id': cp_address_id})
241
d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
539
242
elif order_type == 'direct':
540
v.update({'dest_address_id': False, 'dest_partner_id': False})
243
v.update({'dest_address_id': False})
244
d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
542
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, company_id, ['delivery'])['delivery']
543
v.update({'dest_address_id': cp_address_id, 'dest_partner_id': company_id})
246
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.id, ['delivery'])['delivery']
247
v.update({'dest_address_id': cp_address_id})
248
d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
545
250
if partner_id and partner_id != local_market:
546
251
partner = partner_obj.browse(cr, uid, partner_id)
547
if partner.partner_type in ('internal', 'esc') and order_type in ('regular', 'direct'):
252
if partner.partner_type == 'internal' and order_type == 'regular':
548
253
v['invoice_method'] = 'manual'
549
254
elif partner.partner_type not in ('external', 'esc') and order_type == 'direct':
550
255
v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
256
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
551
257
w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
552
258
'title': 'An error has occured !'})
553
259
elif partner_id and partner_id == local_market and order_type != 'purchase_list':
554
260
v['partner_id'] = None
555
261
v['partner_address_id'] = None
556
262
v['pricelist_id'] = None
558
264
if order_type == 'purchase_list':
560
266
partner = self.pool.get('res.partner').browse(cr, uid, local_market)
630
307
if order_type == 'direct' or dest_address_id:
631
308
if 'dest_address_id' in res.get('value', {}):
632
309
res['value'].pop('dest_address_id')
636
313
def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
638
315
Fill automatically the destination address according to the destination partner
323
company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
645
325
if not dest_partner_id:
646
326
v.update({'dest_address_id': False})
327
d.update({'dest_address_id': [('partner_id', '=', company_id)]})
329
d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
648
331
delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
649
332
v.update({'dest_address_id': delivery_addr['delivery']})
334
return {'value': v, 'domain': d}
652
def change_currency(self, cr, uid, ids, context=None):
654
Launches the wizard to change the currency and update lines
336
def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
338
Change the logged message
659
if isinstance(ids, (int, long)):
662
for order in self.browse(cr, uid, ids, context=context):
663
data = {'order_id': order.id,
664
'partner_id': order.partner_id.id,
665
'partner_type': order.partner_id.partner_type,
666
'new_pricelist_id': order.pricelist_id.id,
667
'currency_rate': 1.00,
668
'old_pricelist_id': order.pricelist_id.id}
669
wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
670
return {'type': 'ir.actions.act_window',
671
'res_model': 'purchase.order.change.currency',
679
def order_line_change(self, cr, uid, ids, order_line):
680
res = {'no_line': True}
683
res = {'no_line': False}
685
return {'value': res}
687
def _get_destination_ok(self, cr, uid, lines, context):
690
is_inkind = line.order_id and line.order_id.order_type == 'in_kind' or False
691
dest_ok = line.account_4_distribution and line.account_4_distribution.destination_ids or False
694
raise osv.except_osv(_('Error'), _('No destination found. An In-kind Donation account is probably missing for this line: %s.') % (line.name or ''))
695
raise osv.except_osv(_('Error'), _('No destination found for this line: %s.') % (line.name or '',))
344
return _("Purchase order '%s' is validated.") % (po.name,)
346
return super(purchase_order, self)._hook_confirm_order_message(cr, uid, context, args, kwargs)
698
348
def check_analytic_distribution(self, cr, uid, ids, context=None):
700
350
Check analytic distribution validity for given PO.
701
351
Also check that partner have a donation account (is PO is in_kind)
704
ad_obj = self.pool.get('analytic.distribution')
705
ccdl_obj = self.pool.get('cost.center.distribution.line')
706
pol_obj = self.pool.get('purchase.order.line')
711
353
if isinstance(ids, (int, long)):
714
355
# Analytic distribution verification
715
356
for po in self.browse(cr, uid, ids, context=context):
717
intermission_cc = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
718
'analytic_account_project_intermission')[1]
722
if po.order_type == 'in_kind' and not po.partner_id.donation_payable_account:
357
if po.order_type and po.order_type == 'in_kind':
723
358
if not po.partner_id.donation_payable_account:
724
359
raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
726
if po.partner_id and po.partner_id.partner_type == 'intermission':
727
if not intermission_cc:
728
raise osv.except_osv(_('Error'), _('No Intermission Cost Center found!'))
730
360
for pol in po.order_line:
731
distrib = pol.analytic_distribution_id or po.analytic_distribution_id or False
361
# Forget check if we come from YAML tests
364
distrib_id = (pol.analytic_distribution_id and pol.analytic_distribution_id.id) or (po.analytic_distribution_id and po.analytic_distribution_id.id) or False
732
365
# Raise an error if no analytic distribution found
734
if not po.order_type in ('loan', 'donation_st', 'donation_exp'):
735
raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
737
# UF-2031: If no distrib accepted (for loan, donation), then do not process the distrib
739
elif pol.analytic_distribution_state != 'valid':
740
id_ad = ad_obj.create(cr, uid, {})
741
ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_centre_lines or po.analytic_distribution_id.cost_center_lines
742
bro_dests = self._get_destination_ok(cr, uid, [pol], context=context)
743
for line in ad_lines:
744
# fetch compatible destinations then use on of them:
745
# - destination if compatible
746
# - else default destination of given account
747
if line.destination_id in bro_dests:
748
bro_dest_ok = line.destination_id
750
bro_dest_ok = pol.account_4_distribution.default_destination_id
751
# Copy cost center line to the new distribution
752
ccdl_obj.copy(cr, uid, line.id, {'distribution_id': id_ad, 'destination_id': bro_dest_ok.id})
754
pol_obj.write(cr, uid, [pol.id], {'analytic_distribution_id': id_ad})
367
raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
368
if pol.analytic_distribution_state != 'valid':
369
raise osv.except_osv(_('Warning'), _("Analytic distribution is not valid for '%s'!") % (pol.name or '',))
372
def wkf_confirm_order(self, cr, uid, ids, context=None):
374
Update the confirmation date of the PO at confirmation.
375
Check analytic distribution.
377
res = super(purchase_order, self).wkf_confirm_order(cr, uid, ids, context=context)
378
self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
379
# CODE MOVED TO self.check_analytic_distribution()
380
self.check_analytic_distribution(cr, uid, ids, context=context)
757
383
def wkf_picking_done(self, cr, uid, ids, context=None):
759
385
Change the shipped boolean and the state of the PO
765
391
self.write(cr, uid, order.id, {'shipped':1,'state':'approved'}, context=context)
769
def confirm_button(self, cr, uid, ids, context=None):
771
check the supplier partner type (partner_type)
773
confirmation is needed for internal, inter-mission and inter-section
775
('internal', 'Internal'), ('section', 'Inter-section'), ('intermission', 'Intermission')
778
name = _("You're about to confirm a PO that is synchronized and should be consequently confirmed by the supplier (automatically at his equivalent FO confirmation). Are you sure you want to force the confirmation at your level (you won't get the supplier's update)?")
781
question = "You're about to confirm a PO that is synchronized and should be consequently confirmed by the supplier (automatically at his equivalent FO confirmation). Are you sure you want to force the confirmation at your level (you won't get the supplier's update)?"
782
clazz = 'purchase.order'
783
func = '_purchase_approve'
787
for obj in self.browse(cr, uid, ids, context=context):
788
if obj.partner_id.partner_type in ('internal', 'section', 'intermission'):
790
wiz_obj = self.pool.get('wizard')
791
# open the selected wizard
792
res = wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=dict(context, question=question,
793
callback={'clazz': clazz,
799
# otherwise call function directly
800
return self.purchase_approve(cr, uid, ids, context=context)
802
def _purchase_approve(self, cr, uid, ids, context=None):
804
interface for call from wizard
806
if called from wizard without opening a new dic -> return close
807
if called from wizard with new dic -> open new wizard
809
if called from button directly, this interface is not called
811
res = self.purchase_approve(cr, uid, ids, context=context)
812
if not isinstance(res, dict):
813
return {'type': 'ir.actions.act_window_close'}
816
395
def purchase_approve(self, cr, uid, ids, context=None):
818
397
If the PO is a DPO, check the state of the stock moves
821
sale_line_obj = self.pool.get('sale.order.line')
822
stock_move_obj = self.pool.get('stock.move')
823
wiz_obj = self.pool.get('purchase.order.confirm.wizard')
825
399
if isinstance(ids, (int, long)):
828
402
wf_service = netsvc.LocalService("workflow")
829
403
move_obj = self.pool.get('stock.move')
831
405
for order in self.browse(cr, uid, ids, context=context):
832
406
if not order.delivery_confirmed_date:
833
407
raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
835
412
if order.order_type == 'direct':
839
413
for line in order.order_line:
840
414
if line.procurement_id: todo.append(line.procurement_id.id)
843
todo2 = sale_line_obj.search(cr, uid, [('procurement_id', 'in', todo)], context=context)
846
sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
848
for move in move_obj.browse(cr, uid, sm_ids, context=context):
849
backmove_ids = stock_move_obj.search(cr, uid, [('backmove_id', '=', move.id)])
850
if move.state == 'done':
851
error_moves.append(move)
853
for bmove in move_obj.browse(cr, uid, backmove_ids):
854
error_moves.append(bmove)
857
errors = '''You are trying to confirm a Direct Purchase Order.
858
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected
417
todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
420
sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
422
for move in move_obj.browse(cr, uid, sm_ids, context=context):
423
backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
424
if move.state == 'done':
425
error_moves.append(move)
427
for bmove in move_obj.browse(cr, uid, backmove_ids):
428
error_moves.append(bmove)
431
errors = '''You are trying to confirm a Direct Purchase Order.
432
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected
859
433
stock moves which are already processed : '''
860
for m in error_moves:
861
errors = '%s \n %s' % (errors, '''
434
for m in error_moves:
435
errors = '%s \n %s' % (errors, '''
862
436
* Picking : %s - Product : [%s] %s - Product Qty. : %s %s \n''' % (m.picking_id.name, m.product_id.default_code, m.product_id.name, m.product_qty, m.product_uom.name))
864
errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
866
wiz_id = wiz_obj.create(cr, uid, {'order_id': order.id,
868
return {'type': 'ir.actions.act_window',
869
'res_model': 'purchase.order.confirm.wizard',
438
errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
440
wiz_id = self.pool.get('purchase.order.confirm.wizard').create(cr, uid, {'order_id': order.id,
442
return {'type': 'ir.actions.act_window',
443
'res_model': 'purchase.order.confirm.wizard',
875
449
# If no errors, validate the DPO
876
450
wf_service.trg_validate(uid, 'purchase.order', order.id, 'purchase_confirmed_wait', cr)
880
def get_so_ids_from_po_ids(self, cr, uid, ids, context=None, sol_ids=[]):
454
def get_so_ids_from_po_ids(self, cr, uid, ids, context=None):
882
456
receive the list of purchase order ids
884
458
return the list of sale order ids corresponding (through procurement process)
886
460
# Some verifications
940
507
sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
943
# @@@override purchase->purchase.py>purchase_order>wkf_confirm_order
944
def wkf_confirm_order(self, cr, uid, ids, context=None):
946
Update the confirmation date of the PO at confirmation.
947
Check analytic distribution.
950
po_line_obj = self.pool.get('purchase.order.line')
955
if isinstance(ids, (int, long)):
960
for po in self.browse(cr, uid, ids, context=context):
961
# Check if the pricelist of the order is good according to currency of the partner
962
pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', po.partner_id.partner_type)], context=context)
963
if po.pricelist_id.id not in pricelist_ids:
964
raise osv.except_osv(_('Error'), _('The currency used on the order is not compatible with the supplier. Please change the currency to choose a compatible currency.'))
966
if not po.split_po and not po.order_line:
967
raise osv.except_osv(_('Error !'), _('You can not validate a purchase order without Purchase Order Lines.'))
969
if po.amount_total == 0: # UFTP-69
970
raise osv.except_osv(_('Error'), _('You can not validate a purchase order with a total amount of 0.'))
972
for line in po.order_line:
973
if line.state=='draft':
976
message = _("Purchase order '%s' is validated.") % (po.name,)
977
self.log(cr, uid, po.id, message)
978
# hook for corresponding Fo update
979
self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
981
po_line_obj.action_confirm(cr, uid, todo, context)
983
self.write(cr, uid, ids, {'state' : 'confirmed',
985
'date_confirm': strftime('%Y-%m-%d')}, context=context)
987
self.check_analytic_distribution(cr, uid, ids, context=context)
991
510
def common_code_from_wkf_approve_order(self, cr, uid, ids, context=None):
993
512
delivery confirmed date at po level is mandatory
994
513
update corresponding date at line level if needed.
995
514
Check analytic distribution
996
Check that no line have a 0 price unit.
999
po_line_obj = self.pool.get('purchase.order.line')
1004
if isinstance(ids, (int, long)):
517
ana_obj = self.pool.get('analytic.distribution')
1007
519
# Check analytic distribution
1008
520
self.check_analytic_distribution(cr, uid, ids, context=context)
1009
521
for po in self.browse(cr, uid, ids, context=context):
1010
# prepare some values
1011
is_regular = po.order_type == 'regular' # True if order_type is regular, else False
522
# CODE MOVED TO self.check_analytic_distribution()
1013
523
# msf_order_date checks
1014
524
if not po.delivery_confirmed_date:
1015
525
raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
1016
526
# for all lines, if the confirmed date is not filled, we copy the header value
1017
lines_to_update = []
1018
527
for line in po.order_line:
1019
# Don't accept the confirmation of regular PO with 0.00 unit price lines
1020
if is_regular and line.price_unit == 0.00:
1021
line_error.append(line.line_number)
1022
elif not line.confirmed_delivery_date:
1023
lines_to_update.append(line.id)
1025
if len(line_error) > 0:
1026
errors = ' / '.join(str(x) for x in line_error)
1027
raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price. Lines in exception : %s') % errors)
1029
po_line_obj.write(cr, uid, lines_to_update, {'confirmed_delivery_date': po.delivery_confirmed_date}, context=context)
528
if not line.confirmed_delivery_date:
529
line.write({'confirmed_delivery_date': po.delivery_confirmed_date,}, context=context)
1030
530
# MOVE code for COMMITMENT into wkf_approve_order
1033
def create_extra_lines_on_fo(self, cr, uid, ids, context=None):
1035
Creates FO/IR lines according to PO extra lines
1038
sol_obj = self.pool.get('sale.order.line')
1039
so_obj = self.pool.get('sale.order')
1040
ad_obj = self.pool.get('analytic.distribution')
1045
if isinstance(ids, (int, long)):
1050
for order in self.browse(cr, uid, ids, context=context):
1051
for l in order.order_line:
1052
link_so_id = l.link_so_id and l.link_so_id.state in ('sourced', 'progress', 'manual')
1053
if link_so_id and (not l.procurement_id or not l.procurement_id.sale_id):
1059
if l.analytic_distribution_id:
1060
new_distrib = ad_obj.copy(cr, uid, l.analytic_distribution_id.id, {}, context=context)
1061
elif not l.analytic_distribution_id and l.order_id and l.order_id.analytic_distribution_id:
1062
new_distrib = ad_obj.copy(cr, uid, l.order_id.analytic_distribution_id.id, {}, context=context)
1063
# Creates the FO lines
1064
tmp_sale_context = context.get('sale_id')
1065
# create new line in FOXXXX-Y
1066
context['sale_id'] = l.link_so_id.id
1067
vals = {'order_id': l.link_so_id.id,
1068
'product_id': l.product_id.id,
1069
'product_uom': l.product_uom.id,
1070
'product_uom_qty': l.product_qty,
1071
'price_unit': l.price_unit,
1072
'procurement_id': l.procurement_id and l.procurement_id.id or False,
1073
'type': 'make_to_order',
1074
'analytic_distribution_id': new_distrib,
1075
'created_by_po': l.order_id.id,
1076
'created_by_po_line': l.id,
1077
'name': '[%s] %s' % (l.product_id.default_code, l.product_id.name)}
1078
sol_obj.create(cr, uid, vals, context=context)
1079
# Create new line in FOXXXX (original FO)
1080
if l.link_so_id.original_so_id_sale_order:
1081
context['sale_id'] = l.link_so_id.original_so_id_sale_order.id
1082
vals.update({'order_id': l.link_so_id.original_so_id_sale_order.id,
1084
sol_obj.create(cr, uid, vals, context=context)
1085
context['sale_id'] = tmp_sale_context
1087
sol_ids.add(l.link_so_id.id)
1089
so_obj.action_ship_proc_create(cr, uid, list(sol_ids), context=context)
1093
533
def wkf_confirm_wait_order(self, cr, uid, ids, context=None):
1096
536
1/ if all purchase line could take an analytic distribution
1097
537
2/ if a commitment voucher should be created after PO approbation
1099
539
_> originally in purchase.py from analytic_distribution_supply
1101
541
Checks if the Delivery Confirmed Date has been filled
1103
543
_> originally in order_dates.py from msf_order_date
1105
545
# Some verifications
1383
774
line_obj = self.pool.get('purchase.order.line')
1384
775
move_obj = self.pool.get('stock.move')
1385
uf_config = self.pool.get('unifield.setup.configuration')
1386
776
wf_service = netsvc.LocalService("workflow")
1388
778
if isinstance(ids, (int, long)):
1391
781
# duplicated code with wkf_confirm_wait_order because of backward compatibility issue with yml tests for dates,
1392
782
# which doesnt execute wkf_confirm_wait_order (null value in column "date_expected" violates not-null constraint for stock.move otherwise)
1393
783
# msf_order_date checks
1394
784
self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
1396
setup = uf_config.get_config(cr, uid)
1398
786
for order in self.browse(cr, uid, ids):
1399
if order.amount_total == 0: # UFTP-69
1400
# total amount could be set to 0 after it was Validated
1402
# (after wkf_confirm_order total amount check)
1403
raise osv.except_osv(_('Error'), _('You can not confirm a purchase order with a total amount of 0.'))
1405
787
# Create commitments for each PO only if po is "from picking"
1406
# UTP-114: No Commitment Voucher on PO that are 'purchase_list'!
1407
if (order.invoice_method in ['picking', 'order'] and not order.from_yml_test and order.order_type not in ['in_kind', 'purchase_list'] and order.partner_id.partner_type != 'intermission') or (order.invoice_method == 'manual' and order.order_type == 'direct' and order.partner_id.partner_type == 'esc'):
1408
# UTP-827: no commitment if they are imported for ESC partners
1409
if not (order.partner_id.partner_type == 'esc' and setup.import_commitments):
1410
self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
788
if order.invoice_method in ['picking', 'order'] and not order.from_yml_test and order.order_type != 'in_kind' and order.partner_id.partner_type != 'intermission':
789
self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
790
# Don't accept the confirmation of regular PO with 0.00 unit price lines
791
if order.order_type == 'regular':
793
for line in order.order_line:
794
if line.price_unit == 0.00:
795
line_error.append(line.line_number)
797
if len(line_error) > 0:
798
errors = ' / '.join(str(x) for x in line_error)
799
raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price. Lines in exception : %s') % errors)
1415
if order.partner_id.partner_type in ('internal', 'esc') and order.order_type == 'regular' or \
804
if order.partner_id.partner_type == 'internal' and order.order_type == 'regular' or \
1416
805
order.order_type in ['donation_exp', 'donation_st', 'loan']:
1417
806
self.write(cr, uid, [order.id], {'invoice_method': 'manual'})
1418
807
line_obj.write(cr, uid, [x.id for x in order.order_line], {'invoiced': 1})
1420
809
message = _("Purchase order '%s' is confirmed.") % (order.name,)
1421
810
self.log(cr, uid, order.id, message)
1423
812
if order.order_type == 'direct':
1424
if order.partner_id.partner_type != 'esc':
1425
self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
813
self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
1426
814
for line in order.order_line:
1427
if line.procurement_id:
1428
todo.append(line.procurement_id.id)
1429
todo4.update({line.procurement_id.id: line.id})
815
if line.procurement_id: todo.append(line.procurement_id.id)
1432
818
todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
1435
821
sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
1436
822
self.pool.get('stock.move').action_confirm(cr, uid, sm_ids, context=context)
1437
823
stock_location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')[1]
1438
cross_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'msf_cross_docking', 'stock_location_cross_docking')[1]
1439
non_stock_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock_override', 'stock_location_non_stockable')[1]
1440
824
for move in move_obj.browse(cr, uid, sm_ids, context=context):
1441
# Reset the location_id to Stock
1442
location_id = stock_location_id
1443
825
# Search if this move has been processed
1444
826
backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
1445
827
if move.state != 'done' and not backmove_ids and not move.backmove_id:
1446
if move.product_id.type in ('service', 'service_recep'):
1447
location_id = cross_id
1448
elif move.product_id.type == 'consu':
1449
location_id = non_stock_id
1450
move_obj.write(cr, uid, [move.id], {'dpo_id': order.id,
1452
'dpo_line_id': todo4.get(move.sale_line_id.procurement_id.id, False),
1453
'location_id': location_id,
1454
'location_dest_id': location_id,
1455
'date': strftime('%Y-%m-%d %H:%M:%S')}, context=context)
828
move_obj.write(cr, uid, sm_ids, {'dpo_id': order.id, 'state': 'done',
829
'location_id': stock_location_id,
830
'location_dest_id': stock_location_id,
831
'date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
1456
832
wf_service.trg_trigger(uid, 'stock.move', move.id, cr)
1458
834
all_move_closed = True
1459
835
# Check if the picking should be updated
1460
836
if move.picking_id.subtype == 'picking':
1633
986
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation_expiry')[1]
1634
987
if order.order_type == 'in_kind':
1635
988
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_in_kind_donation')[1]
1637
990
if reason_type_id:
1638
991
picking_values.update({'reason_type_id': reason_type_id})
1640
993
picking_id = self.pool.get('stock.picking').create(cr, uid, picking_values, context=context)
1642
995
for order_line in order.order_line:
1643
# Reload the data of the line because if the line comes from an ISR and it's a duplicate line,
1644
# the move_dest_id field has been changed by the _hook_action_picking_create_modify_out_source_loc_check method
1645
order_line = self.pool.get('purchase.order.line').browse(cr, uid, order_line.id, context=context)
1646
996
if not order_line.product_id:
1648
dest = order.location_id.id
1649
# service with reception are directed to Service Location
1650
if order_line.product_id.type == 'service_recep' and not order.cross_docking_ok:
1651
dest = self.pool.get('stock.location').get_service_location(cr, uid)
1654
'name': order.name + ': ' +(order_line.name or ''),
1655
'product_id': order_line.product_id.id,
1656
'product_qty': order_line.product_qty,
1657
'product_uos_qty': order_line.product_qty,
1658
'product_uom': order_line.product_uom.id,
1659
'product_uos': order_line.product_uom.id,
1660
'date': order_line.date_planned,
1661
'date_expected': order_line.date_planned,
1662
'location_id': loc_id,
1663
'location_dest_id': dest,
1664
'picking_id': picking_id,
1665
'move_dest_id': order_line.move_dest_id.id,
1667
'purchase_line_id': order_line.id,
1668
'company_id': order.company_id.id,
1669
'price_currency_id': order.pricelist_id.currency_id.id,
1670
'price_unit': order_line.price_unit,
1671
'date': order_line.confirmed_delivery_date,
1672
'date_expected': order_line.confirmed_delivery_date,
1673
'line_number': order_line.line_number,
1677
move_values.update({'reason_type_id': reason_type_id})
1679
ctx = context.copy()
1680
ctx['bypass_store_function'] = [('stock.picking', ['dpo_incoming', 'dpo_out', 'overall_qty', 'line_state'])]
1681
move = self.pool.get('stock.move').create(cr, uid, move_values, context=ctx)
1682
if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
1683
moves_to_update.append(order_line.move_dest_id.id)
1684
todo_moves.append(move)
1685
# compute function fields
1687
compute_store = self.pool.get('stock.move')._store_get_values(cr, uid, todo_moves, None, context)
1688
compute_store.sort()
1690
for _, store_object, store_ids, store_fields2 in compute_store:
1691
if store_fields2 in ('dpo_incoming', 'dpo_out', 'overall_qty', 'line_state') and not (store_object, store_ids, store_fields2) in done:
1692
self.pool.get(store_object)._store_set_values(cr, uid, store_ids, store_fields2, context)
1693
done.append((store_object, store_ids, store_fields2))
1694
move_obj.write(cr, uid, moves_to_update, {'location_id':order.location_id.id})
1695
move_obj.action_confirm(cr, uid, todo_moves)
1696
move_obj.force_assign(cr, uid, todo_moves)
998
if order_line.product_id.product_tmpl_id.type in ('product', 'consu', 'service_recep',):
999
dest = order.location_id.id
1000
# service with reception are directed to Service Location
1001
if order_line.product_id.product_tmpl_id.type == 'service_recep':
1002
service_loc = self.pool.get('stock.location').search(cr, uid, [('service_location', '=', True)], context=context)
1004
dest = service_loc[0]
1007
'name': order.name + ': ' +(order_line.name or ''),
1008
'product_id': order_line.product_id.id,
1009
'product_qty': order_line.product_qty,
1010
'product_uos_qty': order_line.product_qty,
1011
'product_uom': order_line.product_uom.id,
1012
'product_uos': order_line.product_uom.id,
1013
'date': order_line.date_planned,
1014
'date_expected': order_line.date_planned,
1015
'location_id': loc_id,
1016
'location_dest_id': dest,
1017
'picking_id': picking_id,
1018
'move_dest_id': order_line.move_dest_id.id,
1020
'purchase_line_id': order_line.id,
1021
'company_id': order.company_id.id,
1022
'price_currency_id': order.pricelist_id.currency_id.id,
1023
'price_unit': order_line.price_unit
1025
# hook for stock move values modification
1026
move_values = self._hook_action_picking_create_stock_picking(cr, uid, ids, context=context, move_values=move_values, order_line=order_line,)
1029
move_values.update({'reason_type_id': reason_type_id})
1031
move = self.pool.get('stock.move').create(cr, uid, move_values, context=context)
1032
if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
1033
self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
1034
todo_moves.append(move)
1035
self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
1036
self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
1697
1037
wf_service = netsvc.LocalService("workflow")
1698
1038
wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
1699
1039
return picking_id
1702
def _get_location_id(self, cr, uid, vals, warehouse_id=False, context=None):
1704
Get the location_id according to the cross_docking_ok option
1707
if 'cross_docking_ok' not in vals:
1710
if not warehouse_id:
1711
warehouse_id = self.pool.get('stock.warehouse').search(cr, uid, [], context=context)[0]
1713
if not vals.get('cross_docking_ok', False):
1714
vals.update({'location_id': self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context).lot_input_id.id})
1715
elif vals.get('cross_docking_ok', False):
1716
vals.update({'location_id': self.pool.get('stock.location').get_cross_docking_location(cr, uid)})
1721
1042
def create(self, cr, uid, vals, context=None):
1723
1044
Filled in 'from_yml_test' to True if we come from tests
1724
# UTP-114 demands purchase_list PO to be 'from picking'.
1726
1046
if not context:
1048
if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
1049
logging.getLogger('init').info('PO: set from yml test to True')
1050
vals['from_yml_test'] = True
1729
1052
if vals.get('order_type'):
1730
1053
if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
1731
1054
vals.update({'invoice_method': 'manual'})
1732
elif vals.get('order_type') in ['direct']:
1055
elif vals.get('order_type') in ['direct', 'purchase_list']:
1733
1056
vals.update({'invoice_method': 'order'})
1734
if vals.get('partner_id'):
1735
if self.pool.get('res.partner').browse(cr, uid, vals.get('partner_id'), context=context).partner_type == 'esc':
1736
vals.update({'invoice_method': 'manual'})
1738
1058
vals.update({'invoice_method': 'picking'})
1740
1060
if 'partner_id' in vals:
1741
1061
self._check_user_company(cr, uid, vals['partner_id'], context=context)
1742
# we need to update the location_id because it is readonly and so does not pass in the vals of create and write
1743
vals = self._get_location_id(cr, uid, vals, warehouse_id=vals.get('warehouse_id', False), context=context)
1745
res = super(purchase_order, self).create(cr, uid, vals, context=context)
1746
self._check_service(cr, uid, [res], vals, context=context)
1063
return super(purchase_order, self).create(cr, uid, vals, context=context)
1750
1065
def wkf_action_cancel_po(self, cr, uid, ids, context=None):
1864
1151
# Search the method called when the workflow enter in last activity
1865
1152
wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'act_done')[1]
1866
1153
activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
1867
_eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
1154
res = _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
1871
def _hook_order_infos(self, cr, uid, *args, **kwargs):
1873
Hook to change the values of the PO
1875
order_infos = super(purchase_order, self)._hook_order_infos(cr, uid, *args, **kwargs)
1876
order_id = kwargs['order_id']
1878
fields = ['invoice_method', 'minimum_planned_date', 'order_type',
1879
'categ', 'priority', 'internal_type', 'arrival_date',
1880
'transport_type', 'shipment_date', 'ready_to_ship_date',
1881
'cross_docking_ok', 'delivery_confirmed_date',
1882
'est_transport_lead_time', 'transport_mode', 'location_id',
1883
'dest_address_id', 'incoterm_id']
1886
delivery_requested_date = getattr(order_id, 'delivery_requested_date')
1887
if not order_infos.get('delivery_requested_date') or delivery_requested_date < order_infos['delivery_requested_date']:
1888
order_infos['delivery_requested_date'] = delivery_requested_date
1891
for field in fields:
1892
field_val = getattr(order_id, field)
1893
if isinstance(field_val, browse_record):
1894
field_val = field_val.id
1895
elif isinstance(field_val, browse_null):
1897
elif isinstance(field_val, list):
1898
field_val = ((6, 0, tuple([v.id for v in field_val])),)
1899
order_infos[field] = field_val
1903
def _hook_o_line_value(self, cr, uid, *args, **kwargs):
1904
o_line = super(purchase_order, self)._hook_o_line_value(cr, uid, *args, **kwargs)
1905
order_line = kwargs['order_line']
1907
# Copy all fields except order_id and analytic_distribution_id
1908
fields = ['product_uom', 'price_unit', 'move_dest_id', 'product_qty', 'partner_id',
1909
'confirmed_delivery_date', 'nomenclature_description', 'default_code',
1910
'nomen_manda_0', 'nomen_manda_1', 'nomen_manda_2', 'nomen_manda_3',
1911
'nomenclature_code', 'name', 'default_name', 'comment', 'date_planned',
1912
'to_correct_ok', 'text_error',
1913
'nomen_sub_0', 'nomen_sub_1', 'nomen_sub_2', 'nomen_sub_3', 'nomen_sub_4',
1914
'nomen_sub_5', 'procurement_id', 'change_price_manually', 'old_price_unit',
1915
'origin', 'account_analytic_id', 'product_id', 'company_id', 'notes', 'taxes_id']
1917
for field in fields:
1918
field_val = getattr(order_line, field)
1919
if isinstance(field_val, browse_record):
1920
field_val = field_val.id
1921
elif isinstance(field_val, browse_null):
1923
elif isinstance(field_val, list):
1924
field_val = ((6, 0, tuple([v.id for v in field_val])),)
1925
o_line[field] = field_val
1928
# Set the analytic distribution
1930
if order_line.analytic_distribution_id:
1931
distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.analytic_distribution_id.id)
1932
elif order_line.order_id.analytic_distribution_id:
1933
distrib_id = self.pool.get('analytic.distribution').copy(cr, uid, order_line.order_id.analytic_distribution_id.id)
1935
o_line['analytic_distribution_id'] = distrib_id
1939
1158
purchase_order()
1942
class purchase_order_line1(osv.osv):
1944
this modification is placed before merged, because unit price of merged should be Computation as well
1946
_name = 'purchase.order.line'
1947
_inherit = 'purchase.order.line'
1948
_columns = {'price_unit': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Purchase Price Computation')),
1951
purchase_order_line1()
1954
1161
class purchase_order_merged_line(osv.osv):
1956
1163
A purchase order merged line is a special PO line.
2162
1364
change_price_ok = line.change_price_ok
2163
1365
c = context.copy()
2164
1366
c.update({'change_price_ok': change_price_ok})
2165
noraise_ctx = context.copy()
2166
noraise_ctx.update({'noraise': True})
2167
1367
# Need removing the merged_id link before update the merged line because the merged line
2168
1368
# will be removed if it hasn't attached PO line
2169
self.write(cr, uid, [line.id], {'merged_id': False}, context=noraise_ctx)
1369
self.write(cr, uid, [line.id], {'merged_id': False}, context=context)
2170
1370
res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
2174
def _check_restriction_line(self, cr, uid, ids, context=None):
2176
Check if there is restriction on lines
2178
if isinstance(ids, (int, long)):
2184
for line in self.browse(cr, uid, ids, context=context):
2185
if line.order_id and line.order_id.partner_id and line.order_id.state != 'done' and line.product_id:
2186
if not self.pool.get('product.product')._get_restriction_error(cr, uid, line.product_id.id, vals={'partner_id': line.order_id.partner_id.id}, context=context):
2191
def _relatedFields(self, cr, uid, vals, context=None):
2193
related fields for create and write
2195
# recreate description because in readonly
2196
if ('product_id' in vals) and (vals['product_id']):
2197
# no nomenclature description
2198
vals.update({'nomenclature_description':False})
2199
# update the name (comment) of order line
2200
# the 'name' is no more the get_name from product, but instead
2201
# the name of product
2202
productObj = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context)
2203
vals.update({'name':productObj.name})
2204
vals.update({'default_code':productObj.default_code})
2205
vals.update({'default_name':productObj.name})
2206
# erase the nomenclature - readonly
2207
self.pool.get('product.product')._resetNomenclatureFields(vals)
2208
elif ('product_id' in vals) and (not vals['product_id']):
2209
sale = self.pool.get('sale.order.line')
2210
sale._setNomenclatureInfo(cr, uid, vals, context)
2211
# erase default code
2212
vals.update({'default_code':False})
2213
vals.update({'default_name':False})
2215
if 'comment' in vals:
2216
vals.update({'name':vals['comment']})
2217
# clear nomenclature filter values
2218
#self.pool.get('product.product')._resetNomenclatureFields(vals)
2220
def _update_name_attr(self, cr, uid, vals, context=None):
2221
"""Update the name attribute in `vals` if a product is selected."""
2224
prod_obj = self.pool.get('product.product')
2225
if vals.get('product_id'):
2226
product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
2227
vals['name'] = product.name
2228
elif vals.get('comment'):
2229
vals['name'] = vals.get('comment', False)
2231
def _check_product_uom(self, cr, uid, product_id, uom_id, context=None):
2232
"""Check the product UoM."""
2235
uom_tools_obj = self.pool.get('uom.tools')
2236
if not uom_tools_obj.check_uom(cr, uid, product_id, uom_id, context=context):
2237
raise osv.except_osv(
2239
_('You have to select a product UOM in the same '
2240
'category than the purchase UOM of the product !'))
2242
1374
def create(self, cr, uid, vals, context=None):
2244
1376
Create or update a merged line
2249
po_obj = self.pool.get('purchase.order')
2250
seq_pool = self.pool.get('ir.sequence')
2251
sol_obj = self.pool.get('sale.order.line')
1381
order_id = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context)
1382
if order_id.from_yml_test:
1383
vals.update({'change_price_manually': True})
1384
if not vals.get('product_qty', False):
1385
vals['product_qty'] = 1.00
1387
# If we are on a RfQ, use the last entered unit price and update other lines with this price
1389
vals.update({'change_price_manually': True})
1391
if vals.get('product_qty', 0.00) == 0.00:
1392
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
2253
1394
order_id = vals.get('order_id')
2254
1395
product_id = vals.get('product_id')
2255
1396
product_uom = vals.get('product_uom')
2256
order = po_obj.browse(cr, uid, order_id, context=context)
2258
if order.from_yml_test:
2259
vals.update({'change_price_manually': True})
2260
if not vals.get('product_qty', False):
2261
vals['product_qty'] = 1.00
2262
# [imported and adapted from 'analytic_distribution_supply']
2263
if not vals.get('price_unit', False):
2264
vals['price_unit'] = 1.00
2267
# Update the name attribute if a product is selected
2268
self._update_name_attr(cr, uid, vals, context=context)
2270
# If we are on a RfQ, use the last entered unit price and update other lines with this price
2272
vals.update({'change_price_manually': True})
2274
if order.po_from_fo or order.po_from_ir:
2275
vals['from_fo'] = True
2276
if vals.get('product_qty', 0.00) == 0.00 and not context.get('noraise'):
2277
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
1397
order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
2279
1398
other_lines = self.search(cr, uid, [('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
2280
1399
stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
2282
if vals.get('origin'):
2284
if vals.get('procurement_id'):
2285
proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id'))
2286
if not proc or not proc.sale_id:
2287
vals.update(self.update_origin_link(cr, uid, vals.get('origin'), context=context))
2289
1401
if (other_lines and stages and order.state != 'confirmed'):
2290
1402
context.update({'change_price_ok': False})
2292
if not context.get('offline_synchronization'):
2293
vals = self._update_merged_line(cr, uid, False, vals, context=dict(context, skipResequencing=True))
1404
vals = self._update_merged_line(cr, uid, False, vals, context=context)
2295
1406
vals.update({'old_price_unit': vals.get('price_unit', False)})
2297
# [imported from 'order_nomenclature']
2298
# Don't save filtering data
2299
self._relatedFields(cr, uid, vals, context)
2302
# [imported from 'order_line_number']
2303
# Add the corresponding line number
2304
# I leave this line from QT related to purchase.order.merged.line for compatibility and safety reasons
2305
# merged lines, set the line_number to 0 when calling create function
2306
# the following line should *logically* be removed safely
2307
# copy method should work as well, as merged line do *not* need to keep original line number with copy function (QT confirmed)
2308
if self._name != 'purchase.order.merged.line':
2310
# gather the line number from the sale order sequence if not specified in vals
2311
# either line_number is not specified or set to False from copy, we need a new value
2312
if not vals.get('line_number', False):
2313
# new number needed - gather the line number from the sequence
2314
sequence_id = order.sequence_id.id
2315
line = seq_pool.get_id(cr, uid, sequence_id, code_or_id='id', context=context)
2316
vals.update({'line_number': line})
2319
# Check the selected product UoM
2320
if not context.get('import_in_progress', False):
2321
if vals.get('product_id') and vals.get('product_uom'):
2322
self._check_product_uom(
2323
cr, uid, vals['product_id'], vals['product_uom'], context=context)
2325
# utp-518:we write the comment from the sale.order.line on the PO line through the procurement (only for the create!!)
2326
po_procurement_id = vals.get('procurement_id', False)
2327
if po_procurement_id:
2328
sale_id = sol_obj.search(cr, uid, [('procurement_id', '=', po_procurement_id)], context=context)
2330
comment_so = sol_obj.read(cr, uid, sale_id, ['comment'], context=context)[0]['comment']
2331
vals.update(comment=comment_so)
2333
# add the database Id to the sync_order_line_db_id
1408
# add the database Id to the sync_pol_db_id
2334
1409
po_line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context)
2335
if not vals.get('sync_order_line_db_id', False): #'sync_order_line_db_id' not in vals or vals:
2337
super(purchase_order_line, self).write(cr, uid, [po_line_id], {'sync_order_line_db_id': name + "_" + str(po_line_id),}, context=context)
1410
if 'sync_pol_db_id' not in vals:
1411
super(purchase_order_line, self).write(cr, uid, po_line_id, {'sync_pol_db_id': po_line_id}, context=context)
2339
1413
return po_line_id
2341
def default_get(self, cr, uid, fields, context=None):
2345
if context.get('purchase_id'):
2346
# Check validity of the purchase order. We write the order to avoid
2347
# the creation of a new line if one line of the order is not valid
2348
# according to the order category
2350
# 1/ Create a new PO with 'Other' as Order Category
2351
# 2/ Add a new line with a Stockable product
2352
# 3/ Change the Order Category of the PO to 'Service' -> A warning message is displayed
2353
# 4/ Try to create a new line -> The system displays a message to avoid you to create a new line
2354
# while the not valid line is not modified/deleted
2356
# Without the write of the order, the message displayed by the system at 4/ is displayed at the saving
2357
# of the new line that is not very understandable for the user
2359
if context.get('partner_id'):
2360
data.update({'partner_id': context.get('partner_id')})
2361
if context.get('categ'):
2362
data.update({'categ': context.get('categ')})
2363
self.pool.get('purchase.order').write(cr, uid, [context.get('purchase_id')], data, context=context)
2365
return super(purchase_order_line, self).default_get(cr, uid, fields, context=context)
2367
1415
def copy(self, cr, uid, line_id, defaults={}, context=None):
2369
1417
Remove link to merged line
2371
defaults.update({'merged_id': False, 'sync_order_line_db_id': False})
1419
defaults.update({'merged_id': False})
2373
1421
return super(purchase_order_line, self).copy(cr, uid, line_id, defaults, context=context)
2375
def copy_data(self, cr, uid, p_id, default=None, context=None):
2378
# Some verifications
2384
if not 'move_dest_id' in default:
2385
default.update({'move_dest_id': False})
2387
if not 'procurement_id' in default:
2388
default.update({'procurement_id': False})
2390
default.update({'sync_order_line_db_id': False})
2391
return super(purchase_order_line, self).copy_data(cr, uid, p_id, default=default, context=context)
2393
1423
def write(self, cr, uid, ids, vals, context=None):
2395
1425
Update merged line
2400
1430
if isinstance(ids, (int, long)):
2403
# [imported from the 'analytic_distribution_supply']
2404
# Don't save filtering data
2405
self._relatedFields(cr, uid, vals, context)
2408
# Update the name attribute if a product is selected
2409
self._update_name_attr(cr, uid, vals, context=context)
1433
# if ids and not isinstance(ids[0], (int, long)):
1434
# ids = [x.id for x in ids]
2411
1436
for line in self.browse(cr, uid, ids, context=context):
2412
if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok and not context.get('noraise'):
1437
if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok:
2413
1438
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
2415
if vals.get('origin', line.origin):
2417
if vals.get('procurement_id', line.procurement_id.id):
2418
proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id', line.procurement_id.id))
2419
if not proc or not proc.sale_id:
2420
vals.update(self.update_origin_link(cr, uid, vals.get('origin', line.origin), context=context))
2422
if line.order_id and not line.order_id.rfq_ok and (line.order_id.po_from_fo or line.order_id.po_from_ir):
2423
vals['from_fo'] = True
2425
1440
if not context.get('update_merge'):
2426
1441
for line in ids:
2427
vals = self._update_merged_line(cr, uid, line, vals, context=dict(context, skipResequencing=True, noraise=True))
1442
vals = self._update_merged_line(cr, uid, line, vals, context=context)
2429
1444
if 'price_unit' in vals:
2430
1445
vals.update({'old_price_unit': vals.get('price_unit')})
2432
res = super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
2434
# Check the selected product UoM
2435
if not context.get('import_in_progress', False):
2436
for pol_read in self.read(cr, uid, ids, ['product_id', 'product_uom']):
2437
if pol_read.get('product_id'):
2438
product_id = pol_read['product_id'][0]
2439
uom_id = pol_read['product_uom'][0]
2440
self._check_product_uom(cr, uid, product_id, uom_id, context=context)
2444
def update_origin_link(self, cr, uid, origin, context=None):
2446
Return the FO/IR that matches with the origin value
2448
so_obj = self.pool.get('sale.order')
2450
tmp_proc_context = context.get('procurement_request')
2451
context['procurement_request'] = True
2452
so_ids = so_obj.search(cr, uid, [('name', '=', origin), ('state', 'in', ('sourced', 'progress', 'manual'))], context=context)
2453
context['procurement_request'] = tmp_proc_context
2455
return {'link_so_id': so_ids[0]}
2459
def ask_unlink(self, cr, uid, ids, context=None):
2461
Call the unlink method for lines and if the PO becomes empty
2462
ask the user if he wants to cancel the PO
2465
wiz_obj = self.pool.get('purchase.order.line.unlink.wizard')
2467
# Variables initialization
2471
if isinstance(ids, (int, long)):
2475
sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line_id], context=context)
2477
wiz_id = wiz_obj.create(cr, uid, {'line_id': line_id}, context=context)
2478
return {'type': 'ir.actions.act_window',
2479
'res_model': 'purchase.order.line.unlink.wizard',
2480
'view_type': 'form',
2481
'view_mode': 'form',
2486
return self.unlink(cr, uid, ids, context=context)
2488
def cancel_sol(self, cr, uid, ids, context=None):
2490
Re-source the FO line
2492
context = context or {}
2493
sol_obj = self.pool.get('sale.order.line')
2494
uom_obj = self.pool.get('product.uom')
2496
if isinstance(ids, (int, long)):
2500
for line in self.browse(cr, uid, ids, context=context):
2501
sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
2502
for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
2503
diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_qty, sol.product_uom.id)
2504
sol_to_update.setdefault(sol.id, 0.00)
2505
sol_to_update[sol.id] += diff_qty
2506
if line.has_to_be_resourced:
2507
sol_obj.add_resource_line(cr, uid, sol, False, diff_qty, context=context)
2509
for sol in sol_to_update:
2510
sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
2514
def fake_unlink(self, cr, uid, ids, context=None):
2516
Cancel the line and re-source them
2518
proc_obj = self.pool.get('procurement.order')
2523
if isinstance(ids, (int, long)):
2530
for line in self.browse(cr, uid, ids, context=context):
2531
# Set the procurement orders to delete
2532
# Set the list of linked purchase orders
2533
if line.procurement_id:
2534
proc_ids.append(line.procurement_id.id)
2535
if line.order_id.id not in purchase_ids:
2536
purchase_ids.append(line.order_id.id)
2538
self.cancel_sol(cr, uid, [line.id], context=context)
2539
# we want to skip resequencing because unlink is performed on merged purchase order lines
2540
tmp_Resequencing = context.get('skipResequencing', False)
2541
context['skipResequencing'] = True
2542
self._update_merged_line(cr, uid, line.id, False, context=context)
2543
context['skipResequencing'] = tmp_Resequencing
2545
line_to_cancel.append(line.id)
2547
# Cancel the listed procurement orders
2548
for proc_id in proc_ids:
2549
if not self.search(cr, uid, [('procurement_id', '=', proc_id)], context=context):
2550
proc_obj.action_cancel(cr, uid, [proc_id])
2552
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
2553
self.unlink(cr, uid, line_to_cancel, context=context)
1447
return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
2557
1449
def unlink(self, cr, uid, ids, context=None):
2737
1571
res['value'].update({'product_qty': 0.00})
2738
1572
res.update({'warning': {}})
2742
1576
def product_id_on_change(self, cr, uid, ids, pricelist, product, qty, uom,
2743
1577
partner_id, date_order=False, fiscal_position=False, date_planned=False,
2744
1578
name=False, price_unit=False, notes=False, state=False, old_price_unit=False,
2745
1579
nomen_manda_0=False, comment=False, context=None):
1581
suppinfo_obj = self.pool.get('product.supplierinfo')
2747
1582
partner_price = self.pool.get('pricelist.partnerinfo')
2748
product_obj = self.pool.get('product.product')
2753
# If the user modify a line, remove the old quantity for the total quantity
2755
for line_id in self.browse(cr, uid, ids, context=context):
2756
all_qty -= line_id.product_qty
2758
1584
if product and not uom:
2759
uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
2761
if context and context.get('purchase_id') and state == 'draft' and product:
2762
domain = [('product_id', '=', product),
2763
('product_uom', '=', uom),
1585
uom = self.pool.get('product.product').browse(cr, uid, product).uom_po_id.id
1587
if context and context.get('purchase_id') and state == 'draft' and product:
1588
domain = [('product_id', '=', product),
1589
('product_uom', '=', uom),
2764
1590
('order_id', '=', context.get('purchase_id'))]
2765
1591
other_lines = self.search(cr, uid, domain)
2766
1592
for l in self.browse(cr, uid, other_lines):
2767
all_qty += l.product_qty
1593
all_qty += l.product_qty
2769
1595
res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, all_qty, uom,
2770
partner_id, date_order, fiscal_position,
1596
partner_id, date_order, fiscal_position,
2771
1597
date_planned, name, price_unit, notes)
1599
# Remove the warning message if the product has no staged pricelist
1600
# if res.get('warning'):
1601
# supplier_info = self.pool.get('product.supplierinfo').search(cr, uid, [('product_id', '=', product)])
1602
# product_pricelist = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('suppinfo_id', 'in', supplier_info)])
1603
# if not product_pricelist:
1604
# res['warning'] = {}
2773
1605
if res.get('warning', {}).get('title', '') == 'No valid pricelist line found !' or qty == 0.00:
2774
1606
res.update({'warning': {}})
2776
1608
func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
2778
1610
currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
2780
1612
currency_id = func_curr_id
2782
if product and partner_id:
2783
# Test the compatibility of the product with a the partner of the order
2784
res, test = product_obj._on_change_restriction_error(cr, uid, product, field_name='product_id', values=res, vals={'partner_id': partner_id}, context=context)
2788
# Update the old price value
1614
# Update the old price value
2789
1615
res['value'].update({'product_qty': qty})
2790
1616
if product and not res.get('value', {}).get('price_unit', False) and all_qty != 0.00 and qty != 0.00:
2791
1617
# Display a warning message if the quantity is under the minimal qty of the supplier
2799
1625
('valid_from', '=', False),
2800
1626
'|', ('valid_till', '>=', date_order),
2801
1627
('valid_till', '=', False)]
2803
1629
domain_cur = [('currency_id', '=', currency_id)]
2804
1630
domain_cur.extend(domain)
2806
1632
info_prices = partner_price.search(cr, uid, domain_cur, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
2807
1633
if not info_prices:
2808
1634
info_prices = partner_price.search(cr, uid, domain, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
2810
1636
if info_prices:
2811
1637
info_price = partner_price.browse(cr, uid, info_prices[0], context=context)
2812
info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price, round=False, context=context)
1638
info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price)
2813
1639
res['value'].update({'old_price_unit': info_u_price, 'price_unit': info_u_price})
2814
1640
res.update({'warning': {'title': _('Warning'), 'message': _('The product unit price has been set ' \
2815
1641
'for a minimal quantity of %s (the min quantity of the price list), '\
2816
1642
'it might change at the supplier confirmation.') % info_price.min_quantity}})
2817
if info_price.rounding and all_qty%info_price.rounding != 0:
2818
message = _('A rounding value of %s UoM has been set for ' \
2819
'this product, you should than modify ' \
2820
'the quantity ordered to match the supplier criteria.') % info_price.rounding
2821
message = '%s \n %s' % (res.get('warning', {}).get('message', ''), message)
2822
res['warning'].update({'message': message})
2824
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'], round=False, context=context)
1644
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'])
2825
1645
res['value'].update({'old_price_unit': old_price})
2827
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'), round=False, context=context)
1647
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'))
2828
1648
res['value'].update({'old_price_unit': old_price})
2830
1650
# Set the unit price with cost price if the product has no staged pricelist
2831
if product and qty != 0.00:
1651
if product and qty != 0.00:
2832
1652
res['value'].update({'comment': False, 'nomen_manda_0': False, 'nomen_manda_1': False,
2833
'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False,
2834
'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False,
1653
'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False,
1654
'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False,
2835
1655
'nomen_sub_4': False, 'nomen_sub_5': False})
2836
st_uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
2837
1656
st_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
2838
st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price, round=False, context=context)
2839
st_price = self.pool.get('product.uom')._compute_price(cr, uid, st_uom, st_price, uom)
1657
st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price)
2841
1659
if res.get('value', {}).get('price_unit', False) == False and (state and state == 'draft') or not state :
2842
1660
res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
2843
1661
elif state and state != 'draft' and old_price_unit:
2844
1662
res['value'].update({'price_unit': old_price_unit, 'old_price_unit': old_price_unit})
2846
1664
if res['value']['price_unit'] == 0.00:
2847
1665
res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
2849
1667
elif qty == 0.00:
2850
1668
res['value'].update({'price_unit': 0.00, 'old_price_unit': 0.00})
2851
1669
elif not product and not comment and not nomen_manda_0:
2852
1670
res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
2855
if context and context.get('categ') and product:
2856
# Check consistency of product
2857
consistency_message = self.pool.get('product.product').check_consistency(cr, uid, product, context.get('categ'), context=context)
2858
if consistency_message:
2859
res.setdefault('warning', {})
2860
res['warning'].setdefault('title', 'Warning')
2861
res['warning'].setdefault('message', '')
2863
res['warning']['message'] = '%s \n %s' % (res.get('warning', {}).get('message', ''), consistency_message)
2867
def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id,
2868
product_uom, product_qty, pricelist, partner_id, date_order,
2869
change_price_ok, state, old_price_unit,
1674
def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id,
1675
product_uom, product_qty, pricelist, partner_id, date_order,
1676
change_price_ok, state, old_price_unit,
2870
1677
nomen_manda_0=False, comment=False, context=None):
2872
1679
Display a warning message on change price unit if there are other lines with the same product and the same uom
2948
1755
purchase_order_line()
2950
class purchase_order_group(osv.osv_memory):
2951
_name = "purchase.order.group"
2952
_inherit = "purchase.order.group"
2953
_description = "Purchase Order Merge"
2956
'po_value_id': fields.many2one('purchase.order', string='Template PO', help='All values in this PO will be used as default values for the merged PO'),
2957
'unmatched_categ': fields.boolean(string='Unmatched categories'),
2960
def default_get(self, cr, uid, fields, context=None):
2961
res = super(purchase_order_group, self).default_get(cr, uid, fields, context=context)
2962
if context.get('active_model','') == 'purchase.order' and len(context['active_ids']) < 2:
2963
raise osv.except_osv(_('Warning'),
2964
_('Please select multiple order to merge in the list view.'))
2966
res['po_value_id'] = context['active_ids'][-1]
2969
for po in self.pool.get('purchase.order').read(cr, uid, context['active_ids'], ['categ'], context=context):
2970
categories.add(po['categ'])
2972
if len(categories) > 1:
2973
res['unmatched_categ'] = True
2977
def merge_orders(self, cr, uid, ids, context=None):
2978
res = super(purchase_order_group, self).merge_orders(cr, uid, ids, context=context)
2979
res.update({'context': {'search_default_draft': 1, 'search_default_approved': 0,'search_default_create_uid':uid, 'purchase_order': True}})
2981
if 'domain' in res and eval(res['domain'])[0][2]:
2984
raise osv.except_osv(_('Error'), _('No PO merged !'))
2985
return {'type': 'ir.actions.act_window_close'}
2987
purchase_order_group()
2989
class product_product(osv.osv):
2990
_name = 'product.product'
2991
_inherit = 'product.product'
2993
def _product_price(self, cr, uid, ids, field_name, args, context=None):
2994
res = super(product_product, self)._product_price(cr, uid, ids, field_name, args, context=context)
2997
if res[product] == 0.00:
2999
res[product] = self.pool.get('product.product').read(cr, uid, [product], ['standard_price'], context=context)[0]['standard_price']
3005
def _get_purchase_type(self, cr, uid, ids, field_name, args, context=None):
3012
def _src_purchase_type(self, cr, uid, obj, name, args, context=None):
3014
Returns a domain according to the PO type
3018
if arg[0] == 'purchase_type':
3020
raise osv.except_osv(_('Error'), _('Only the \'=\' operator is allowed.'))
3021
# Returns all service products
3022
if arg[2] == 'service':
3023
res.append(('type', '=', 'service_recep'))
3024
elif arg[2] == 'transport':
3025
res.append(('transport_ok', '=', True))
3031
'purchase_type': fields.function(_get_purchase_type, fnct_search=_src_purchase_type, type='boolean', string='Purchase type', method=True, store=False),
3032
'price': fields.function(_product_price, method=True, type='float', string='Pricelist', digits_compute=dp.get_precision('Sale Price')),
3035
def check_consistency(self, cr, uid, product_id, category, context=None):
3037
Check the consistency of product according to category
3039
context = context is None and {} or context
3040
display_message = False
3042
# No check for Other
3043
if category == 'other':
3046
product = self.read(cr, uid, product_id, ['nomen_manda_0', 'type', 'transport_ok'], context=context)
3047
transport_product = product['transport_ok']
3048
product_type = product['type']
3049
main_type = product['nomen_manda_0'][0]
3051
if category == 'medical':
3053
med_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'MED')], context=context)[0]
3055
raise osv.except_osv(_('Error'), _('MED nomenclature Main Type not found'))
3057
if main_type != med_nomen:
3058
display_message = True
3060
if category == 'log':
3062
log_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'LOG')], context=context)[0]
3064
raise osv.except_osv(_('Error'), _('LOG nomenclature Main Type not found'))
3066
if main_type != log_nomen:
3067
display_message = True
3069
if category == 'service' and product_type != 'service_recep':
3070
display_message = True
3072
if category == 'transport' and (product_type != 'service_recep' or not transport_product):
3073
display_message = True
3076
return 'Warning you are about to add a product which does not conform to this PO’s order category, do you wish to proceed ?'
3082
class purchase_order_line_unlink_wizard(osv.osv_memory):
3083
_name = 'purchase.order.line.unlink.wizard'
3086
'line_id': fields.many2one('purchase.order.line', 'Line to delete'),
3089
def just_cancel(self, cr, uid, ids, context=None):
3094
line_obj = self.pool.get('purchase.order.line')
3095
order_wiz_obj = self.pool.get('purchase.order.cancel.wizard')
3096
data_obj = self.pool.get('ir.model.data')
3097
po_obj = self.pool.get('purchase.order')
3103
if isinstance(ids, (int, long)):
3108
for wiz in self.browse(cr, uid, ids, context=context):
3109
po_ids.add(wiz.line_id.order_id.id)
3110
line_ids.append(wiz.line_id.id)
3112
if context.get('has_to_be_resourced'):
3113
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3115
line_obj.fake_unlink(cr, uid, line_ids, context=context)
3117
for po in po_obj.browse(cr, uid, list(po_ids), context=context):
3118
if all(x.state in ('cancel', 'done') for x in po.order_line):
3119
wiz_id = order_wiz_obj.create(cr, uid, {'order_id': po.id}, context=context)
3120
view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3121
context['view_id'] = False
3122
return {'type': 'ir.actions.act_window',
3123
'res_model': 'purchase.order.cancel.wizard',
3124
'view_type': 'form',
3125
'view_mode': 'form',
3126
'view_id': [view_id],
3131
return {'type': 'ir.actions.act_window_close'}
3134
def cancel_and_resource(self, cr, uid, ids, context=None):
3136
Flag the line to be re-sourced and run cancel method
3142
context['has_to_be_resourced'] = True
3144
return self.just_cancel(cr, uid, ids, context=context)
3146
purchase_order_line_unlink_wizard()
3149
class purchase_order_cancel_wizard(osv.osv_memory):
3150
_name = 'purchase.order.cancel.wizard'
3152
_columns = {'order_id': fields.many2one('purchase.order', 'Order to delete'),
3153
'unlink_po': fields.boolean(string='Unlink PO'),}
3155
def fields_view_get(self, cr, uid, view_id=False, view_type='form', context=None, toolbar=False, submenu=False):
3156
return super(purchase_order_cancel_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
3158
def ask_unlink(self, cr, uid, order_id, context=None):
3162
data_obj = self.pool.get('ir.model.data')
3167
view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3168
wiz_id = self.create(cr, uid, {'order_id': order_id}, context=context)
3170
return {'type': 'ir.actions.act_window',
3171
'res_model': 'purchase.order.cancel.wizard',
3173
'view_id': [view_id],
3174
'view_type': 'form',
3175
'view_mode': 'form',
3179
def close_window(self, cr, uid, ids, context=None):
3181
Close the pop-up and reload the PO
3183
return {'type': 'ir.actions.act_window_close'}
3185
def cancel_po(self, cr, uid, ids, context=None):
3187
Cancel the PO and display his form
3189
po_obj = self.pool.get('purchase.order')
3190
line_obj = self.pool.get('purchase.order.line')
3191
wf_service = netsvc.LocalService("workflow")
3196
if isinstance(ids, (int, long)):
3201
for wiz in self.browse(cr, uid, ids, context=context):
3202
order_ids.append(wiz.order_id.id)
3203
if context.get('has_to_be_resourced'):
3204
line_ids.extend([l.id for l in wiz.order_id.order_line])
3206
# Mark lines as 'To be resourced'
3207
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3209
po_obj.write(cr, uid, order_ids, {'canceled_end': True}, context=context)
3210
for order_id in order_ids:
3211
wf_service.trg_validate(uid, 'purchase.order', order_id, 'purchase_cancel', cr)
3213
return {'type': 'ir.actions.act_window_close'}
3215
def cancel_and_resource(self, cr, uid, ids, context=None):
3219
context['has_to_be_resourced'] = True
3221
return self.cancel_po(cr, uid, ids, context=context)
3223
purchase_order_cancel_wizard()
3225
class res_partner(osv.osv):
3226
_inherit = 'res.partner'
3228
def address_multiple_get(self, cr, uid, ids, adr_pref=['default']):
3229
address_obj = self.pool.get('res.partner.address')
3230
address_ids = address_obj.search(cr, uid, [('partner_id', '=', ids)])
3231
address_rec = address_obj.read(cr, uid, address_ids, ['type'])
3233
for addr in address_rec:
3234
res.setdefault(addr['type'], [])
3235
res[addr['type']].append(addr['id'])
3237
default_address = res.get('default', False)
3239
default_address = False
3242
result[a] = res.get(a, default_address)
3249
class res_partner_address(osv.osv):
3250
_inherit = 'res.partner.address'
3252
def _get_dummy(self, cr, uid, ids, field_name, args, context=None):
3259
def _src_address(self, cr, uid, obj, name, args, context=None):
3261
Returns all the destination addresses of a partner or all default
3262
addresses if he hasn't destination addresses
3264
partner_obj = self.pool.get('res.partner')
3265
user_obj = self.pool.get('res.users')
3269
if arg[0] == 'dest_address':
3270
addr_type = 'delivery'
3271
elif arg[0] == 'inv_address':
3272
addr_type = 'invoice'
3277
partner_id = user_obj.browse(cr, uid, uid, context=context).company_id.partner_id.id
3279
partner_id = [partner_id]
3282
if isinstance(partner_id, list):
3283
for partner in partner_id:
3286
addr_ids.extend(partner_obj.address_multiple_get(cr, uid, partner, [addr_type])[addr_type])
3289
addr_ids = partner_obj.address_multiple_get(cr, uid, partner_id, [addr_type])[addr_type]
3291
res.append(('id', 'in', list(i for i in addr_ids if i)))
3296
'dest_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
3297
type='boolean', string='Dest. Address', store=False),
3298
'inv_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
3299
type='boolean', string='Invoice Address', store=False),
3303
res_partner_address()
1757
class account_invoice(osv.osv):
1758
_name = 'account.invoice'
1759
_inherit = 'account.invoice'
1762
'purchase_list': fields.boolean(string='Purchase List ?', help='Check this box if the invoice comes from a purchase list', readonly=True, states={'draft':[('readonly',False)]}),
3305
1767
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: