174
51
res[purchase.id] = invoiced
178
55
# @@@purchase.purchase_order._shipped_rate
179
56
def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
181
sp_obj = self.pool.get('stock.picking')
182
inv_obj = self.pool.get('account.invoice')
183
58
for purchase in self.browse(cursor, user, ids, context=context):
184
if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type in ('internal', 'esc')) or \
59
if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type == 'internal') or \
185
60
purchase.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']):
186
61
res[purchase.id] = purchase.shipped_rate
189
# UTP-808: Deleted invoices amount should be taken in this process. So what we do:
190
# 1/ Take all closed stock picking linked to the purchase
191
# 2/ Search invoices linked to these stock picking
192
# 3/ Take stock picking not linked to an invoice
193
# 4/ Use these non-invoiced closed stock picking to add their amount to the "invoiced" amount
194
64
for invoice in purchase.invoice_ids:
195
65
if invoice.state not in ('draft','cancel'):
196
66
tot += invoice.amount_untaxed
197
stock_pickings = sp_obj.search(cursor, user, [('purchase_id', '=', purchase.id), ('state', '=', 'done')])
199
sp_ids = list(stock_pickings)
200
if isinstance(stock_pickings, (int, long)):
201
stock_pickings = [stock_pickings]
202
for sp in stock_pickings:
203
inv_ids = inv_obj.search(cursor, user, [('picking_id', '=', sp)])
207
for stock_picking in sp_obj.browse(cursor, user, sp_ids):
208
for line in stock_picking.move_lines:
209
tot += line.product_qty * line.price_unit
210
67
if purchase.amount_untaxed:
211
68
res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
213
70
res[purchase.id] = 0.0
217
def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
219
Returns the Unifield configuration value
222
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
225
res[order] = setup.allocation_setup
229
def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
231
for order in self.browse(cr, uid, ids, context=context):
234
res[order.id] = False
237
def _po_from_x(self, cr, uid, ids, field_names, args, context=None):
238
"""fields.function multi for 'po_from_ir' and 'po_from_fo' fields."""
240
pol_obj = self.pool.get('purchase.order.line')
241
sol_obj = self.pool.get('sale.order.line')
242
for po_data in self.read(cr, uid, ids, ['order_line'], context=context):
243
res[po_data['id']] = {'po_from_ir': False, 'po_from_fo': False}
244
pol_ids = po_data.get('order_line')
246
pol_datas = pol_obj.read(
247
cr, uid, pol_ids, ['procurement_id'], context=context)
248
proc_ids = [pol['procurement_id'][0]
249
for pol in pol_datas if pol.get('procurement_id')]
252
sol_ids = sol_obj.search(
254
[('procurement_id', 'in', proc_ids)],
256
res[po_data['id']]['po_from_ir'] = bool(sol_ids)
258
sol_ids = sol_obj.search(
260
[('procurement_id', 'in', proc_ids),
261
('order_id.procurement_request', '=', False)],
263
res[po_data['id']]['po_from_fo'] = bool(sol_ids)
266
def _get_dest_partner_names(self, cr, uid, ids, field_name, args, context=None):
268
for po_r in self.read(cr, uid, ids, ['dest_partner_ids'], context=context):
270
if po_r['dest_partner_ids']:
271
name_tuples = self.pool.get('res.partner').name_get(cr, uid, po_r['dest_partner_ids'], context=context)
273
names_list = [nt[1] for nt in name_tuples]
274
names = "; ".join(names_list)
275
res[po_r['id']] = names
278
def _get_project_ref(self, cr, uid, ids, field_name, args, context=None):
280
Get the name of the POs at project side
282
if isinstance(ids, (int, long)):
288
'fnct_project_ref': '',
289
'sourced_references': '',
292
so_ids = self.get_so_ids_from_po_ids(cr, uid, po, context=context)
293
for so in self.pool.get('sale.order').browse(cr, uid, so_ids, context=context):
294
if so.client_order_ref:
295
if res[po]['fnct_project_ref']:
296
res[po]['fnct_project_ref'] += ' - '
297
res[po]['fnct_project_ref'] += so.client_order_ref
299
if res[po]['sourced_references']:
300
res[po]['sourced_references'] += ','
301
res[po]['sourced_references'] += so.name
305
def _get_vat_ok(self, cr, uid, ids, field_name, args, context=None):
307
Return True if the system configuration VAT management is set to True
309
vat_ok = self.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok
316
def _get_requested_date_in_past(self, cr, uid, ids, field_name, args, context=None):
317
if isinstance(ids, (int, long)):
321
for po in self.read(cr, uid, ids, ['delivery_requested_date', 'rfq_ok'], context=context):
322
res[po['id']] = po['delivery_requested_date'] and not po['rfq_ok'] and po['delivery_requested_date'] < time.strftime('%Y-%m-%d') or False
327
'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
328
('donation_st', 'Standard donation'), ('loan', 'Loan'),
75
'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
76
('donation_st', 'Standard donation'), ('loan', 'Loan'),
329
77
('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
330
('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'sourced':[('readonly',True)], 'split':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
78
('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
331
79
'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
332
80
'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
333
81
'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
334
# we increase the size of the 'details' field from 30 to 86
335
'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)]}),
336
'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
82
'details': fields.char(size=30, string='Details', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
83
'invoiced': fields.function(_invoiced, method=True, string='Invoiced & Paid', type='boolean', help="It indicates that an invoice has been paid"),
337
84
'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
338
'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
85
'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
339
86
'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
340
87
'date_order':fields.date(string='Creation Date', readonly=True, required=True,
341
88
states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
342
'name': fields.char('Order Reference', size=64, required=True, select=True, readonly=True,
89
'name': fields.char('Order Reference', size=64, required=True, select=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)]},
343
90
help="unique number of the purchase order,computed automatically when the purchase order is created"),
344
91
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
345
92
'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'rfq_sent':[('readonly',False)], 'confirmed': [('readonly',False)]}),
346
'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)]"),
93
'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)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
347
94
'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
348
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)]"),
95
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)]"),
349
96
'dest_partner_id': fields.many2one('res.partner', string='Destination partner', domain=[('partner_type', '=', 'internal')]),
350
'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
97
'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
351
98
help="The address where the invoice will be sent."),
352
99
'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
353
100
help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
356
103
"Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
358
105
'merged_line_ids': fields.one2many('purchase.order.merged.line', 'order_id', string='Merged line'),
359
'date_confirm': fields.date(string='Confirmation date'),
360
'allocation_setup': fields.function(_get_allocation_setup, type='selection',
361
selection=[('allocated', 'Allocated'),
362
('unallocated', 'Unallocated'),
363
('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
364
'unallocation_ok': fields.boolean(string='Unallocated PO'),
365
# we increase the size of the partner_ref field from 64 to 128
366
'partner_ref': fields.char('Supplier Reference', size=128),
367
'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
368
'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
369
'active': fields.boolean('Active', readonly=True),
370
'po_from_ir': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from IR ?', multi='po_from_x'),
371
'po_from_fo': fields.function(_po_from_x, method=True, type='boolean', string='Is PO from FO ?', multi='po_from_x'),
372
'canceled_end': fields.boolean(string='Canceled End', readonly=True),
373
'is_a_counterpart': fields.boolean('Counterpart?', help="This field is only for indicating that the order is a counterpart"),
374
'po_updated_by_sync': fields.boolean('PO updated by sync', readonly=False),
375
'origin': fields.text('Source Document',
376
help="Reference of the document that generated this purchase order request."),
377
# UF-2267: Store also the parent PO as reference in the sourced PO
378
'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'),
379
'project_ref': fields.char(size=256, string='Project Ref.'),
380
'message_esc': fields.text(string='ESC Message'),
381
'fnct_project_ref': fields.function(_get_project_ref, method=True, string='Project Ref.',
382
type='char', size=256, store=False, multi='so_info'),
383
'dest_partner_ids': fields.many2many('res.partner', 'res_partner_purchase_order_rel', 'purchase_order_id', 'partner_id', 'Customers'), # uf-2223
384
'dest_partner_names': fields.function(_get_dest_partner_names, type='char', size=256, string='Customers', method=True), # uf-2223
385
'split_po': fields.boolean('Created by split PO', readonly=True),
386
'sourced_references': fields.function(
389
string='Sourced references',
394
'vat_ok': fields.function(_get_vat_ok, method=True, type='boolean', string='VAT OK', store=False, readonly=True),
395
'requested_date_in_past': fields.function(
396
_get_requested_date_in_past,
398
string='Requested date in past',
405
109
'order_type': lambda *a: 'regular',
406
110
'priority': lambda *a: 'normal',
407
111
'categ': lambda *a: 'other',
408
112
'loan_duration': 2,
409
113
'from_yml_test': lambda *a: False,
410
'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'],
114
'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'],
411
115
'invoice_method': lambda *a: 'picking',
412
'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'],
413
'no_line': lambda *a: True,
415
'name': lambda *a: False,
416
'is_a_counterpart': False,
417
'parent_order_name': False,
418
'canceled_end': False,
420
'vat_ok': lambda obj, cr, uid, context: obj.pool.get('unifield.setup.configuration').get_config(cr, uid).vat_ok,
423
def _check_po_from_fo(self, cr, uid, ids, context=None):
427
for po in self.browse(cr, uid, ids, context=context):
428
if po.partner_id.partner_type == 'internal' and po.po_from_fo:
433
(_check_po_from_fo, 'You cannot choose an internal supplier for this purchase order', []),
436
def _check_service(self, cr, uid, ids, vals, context=None):
438
Avoid the saving of a PO with non service products on Service PO
440
# UTP-871 : Remove check of service
443
if isinstance(ids, (int, long)):
447
if context.get('import_in_progress'):
450
for order in self.browse(cr, uid, ids, context=context):
451
for line in order.order_line:
452
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):
453
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))
455
elif vals.get('categ', order.categ) == 'service' and line.product_id and line.product_id.type not in ('service', 'service_recep'):
456
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))
461
def purchase_cancel(self, cr, uid, ids, context=None):
463
Call the wizard to ask if you want to re-source the line
465
line_obj = self.pool.get('purchase.order.line')
466
wiz_obj = self.pool.get('purchase.order.cancel.wizard')
467
exp_sol_obj = self.pool.get('expected.sale.order.line')
468
so_obj = self.pool.get('sale.order')
469
data_obj = self.pool.get('ir.model.data')
470
wf_service = netsvc.LocalService("workflow")
475
if isinstance(ids, (int, long)):
478
if context.get('rfq_ok', False):
479
view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'rfq_cancel_wizard_form_view')[1]
481
view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'purchase_order_cancel_wizard_form_view')[1]
483
so_to_cancel_ids = set()
484
for po in self.browse(cr, uid, ids, context=context):
485
for l in po.order_line:
486
if line_obj.get_sol_ids_from_pol_ids(cr, uid, [l.id], context=context):
487
wiz_id = wiz_obj.create(cr, uid, {
489
'last_lines': wiz_obj._get_last_lines(cr, uid, po.id, context=context),
491
return {'type': 'ir.actions.act_window',
492
'res_model': 'purchase.order.cancel.wizard',
496
'view_id': [view_id],
500
exp_sol_ids = exp_sol_obj.search(cr, uid, [('po_id', '=', po.id)], context=context)
501
for exp in exp_sol_obj.browse(cr, uid, exp_sol_ids, context=context):
502
if not exp.order_id.order_line:
503
so_to_cancel_ids.add(exp.order_id.id)
505
wf_service.trg_validate(uid, 'purchase.order', po.id, 'purchase_cancel', cr)
507
# Ask user to choose what must be done on the FO/IR
513
return so_obj.open_cancel_wizard(cr, uid, set(so_to_cancel_ids), context=context)
518
def unlink(self, cr, uid, ids, context=None):
520
No unlink for PO linked to a FO
522
if self.get_so_ids_from_po_ids(cr, uid, ids, context=context):
523
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.'))
525
return super(purchase_order, self).unlink(cr, uid, ids, context=context)
527
def _check_restriction_line(self, cr, uid, ids, context=None):
529
Check restriction on products
531
if isinstance(ids, (int, long)):
534
line_obj = self.pool.get('purchase.order.line')
537
for order in self.browse(cr, uid, ids, context=context):
538
res = res and line_obj._check_restriction_line(cr, uid, [x.id for x in order.order_line], context=context)
543
def default_get(self, cr, uid, fields, context=None):
545
Fill the unallocated_ok field according to Unifield setup
547
res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
549
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
550
res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
551
if setup.allocation_setup == 'unallocated':
552
res.update({'unallocation_ok': True})
554
res.update({'name': False})
559
118
def _check_user_company(self, cr, uid, company_id, context=None):
561
120
Remove the possibility to make a PO to user's company
608
151
partner_obj = self.pool.get('res.partner')
610
# the domain on the onchange was replace by a several fields.function that you can retrieve in the
611
# file msf_custom_settings/view/purchase_view.xml: domain="[('supplier', '=', True), ('id', '!=', company_id), ('check_partner_po', '=', order_type), ('check_partner_rfq', '=', tender_id)]"
612
# d = {'partner_id': []}
153
d = {'partner_id': []}
614
155
local_market = None
616
157
# Search the local market partner id
617
158
data_obj = self.pool.get('ir.model.data')
618
159
data_id = data_obj.search(cr, uid, [('module', '=', 'order_types'), ('model', '=', 'res.partner'), ('name', '=', 'res_partner_local_market')] )
620
161
local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
622
if order_type == 'loan':
623
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
625
if not setup.field_orders_ok:
626
return {'value': {'order_type': 'regular'},
627
'warning': {'title': 'Error',
628
'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
630
if order_type in ['donation_exp', 'donation_st', 'loan']:
163
if order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']:
631
164
v['invoice_method'] = 'manual'
632
elif order_type in ['direct']:
165
elif order_type in ['direct', 'purchase_list']:
633
166
v['invoice_method'] = 'order'
634
elif order_type in ['in_kind', 'purchase_list']:
635
v['invoice_method'] = 'picking'
637
v['invoice_method'] = 'picking'
639
company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id
641
if order_type == 'direct' and dest_partner_id and dest_partner_id != company_id:
642
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
643
v.update({'dest_address_id': cp_address_id})
644
elif order_type == 'direct':
645
v.update({'dest_address_id': False, 'dest_partner_id': False})
647
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, company_id, ['delivery'])['delivery']
648
v.update({'dest_address_id': cp_address_id, 'dest_partner_id': company_id})
167
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
169
v['invoice_method'] = 'picking'
650
171
if partner_id and partner_id != local_market:
651
172
partner = partner_obj.browse(cr, uid, partner_id)
652
if partner.partner_type in ('internal', 'esc') and order_type in ('regular', 'direct'):
173
if partner.partner_type == 'internal' and order_type == 'regular':
653
174
v['invoice_method'] = 'manual'
654
175
elif partner.partner_type not in ('external', 'esc') and order_type == 'direct':
655
v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
176
v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False})
177
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
656
178
w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
657
179
'title': 'An error has occured !'})
658
180
elif partner_id and partner_id == local_market and order_type != 'purchase_list':
659
181
v['partner_id'] = None
182
v['dest_address_id'] = None
660
183
v['partner_address_id'] = None
661
184
v['pricelist_id'] = None
663
186
if order_type == 'purchase_list':
665
188
partner = self.pool.get('res.partner').browse(cr, uid, local_market)
666
189
v['partner_id'] = partner.id
667
190
if partner.address:
191
v['dest_address_id'] = partner.address[0].id
668
192
v['partner_address_id'] = partner.address[0].id
669
193
if partner.property_product_pricelist_purchase:
670
194
v['pricelist_id'] = partner.property_product_pricelist_purchase.id
671
elif order_type == 'direct':
672
v['cross_docking_ok'] = False
674
return {'value': v, 'warning': w}
196
return {'value': v, 'domain': d, 'warning': w}
676
198
def onchange_partner_id(self, cr, uid, ids, part, *a, **b):
678
200
Fills the Requested and Confirmed delivery dates
680
202
if isinstance(ids, (int, long)):
683
205
res = super(purchase_order, self).onchange_partner_id(cr, uid, ids, part, *a, **b)
686
208
partner_obj = self.pool.get('res.partner')
687
product_obj = self.pool.get('product.product')
688
209
partner = partner_obj.browse(cr, uid, part)
690
# Check the restrction of product in lines
692
product_obj = self.pool.get('product.product')
693
for order in self.browse(cr, uid, ids):
694
for line in order.order_line:
696
res, test = product_obj._on_change_restriction_error(cr, uid, line.product_id.id, field_name='partner_id', values=res, vals={'partner_id': part})
698
res.setdefault('value', {}).update({'partner_address_id': False})
700
if partner.partner_type in ('internal', 'esc'):
210
if partner.partner_type == 'internal':
701
211
res['value']['invoice_method'] = 'manual'
702
elif ids and partner.partner_type == 'intermission':
704
intermission = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
705
'analytic_account_project_intermission')[1]
708
cr.execute('''select po.id from purchase_order po
709
left join purchase_order_line pol on pol.order_id = po.id
710
left join cost_center_distribution_line cl1 on cl1.distribution_id = po.analytic_distribution_id
711
left join cost_center_distribution_line cl2 on cl2.distribution_id = pol.analytic_distribution_id
712
where po.id in %s and (cl1.analytic_id!=%s or cl2.analytic_id!=%s)''', (tuple(ids), intermission, intermission))
714
res.setdefault('warning', {})
715
msg = _('You set an intermission partner, at validation Cost Centers will be changed to intermission.')
716
if res.get('warning', {}).get('message'):
717
res['warning']['message'] += msg
719
res['warning'] = {'title': _('Warning'), 'message': msg}
722
# Be careful during integration, the onchange_warehouse_id method is also defined on UF-965
723
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, order_type, dest_address_id):
725
Change the destination address to the destination address of the company if False
727
res = super(purchase_order, self).onchange_warehouse_id(cr, uid, ids, warehouse_id)
729
if not res.get('value', {}).get('dest_address_id') and order_type!='direct':
730
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.partner_id.id, ['delivery'])['delivery']
732
res['value'].update({'dest_address_id': cp_address_id})
734
res.update({'value': {'dest_address_id': cp_address_id}})
735
if order_type == 'direct' or dest_address_id:
736
if 'dest_address_id' in res.get('value', {}):
737
res['value'].pop('dest_address_id')
741
215
def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
743
217
Fill automatically the destination address according to the destination partner
750
225
if not dest_partner_id:
751
226
v.update({'dest_address_id': False})
227
d.update({'dest_address_id': []})
229
d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
231
delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
232
v.update({'dest_address_id': delivery_addr['delivery']})
234
return {'value': v, 'domain': d}
236
def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
238
Change the logged message
244
return _("Purchase order '%s' is validated.") % (po.name,)
753
delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
754
v.update({'dest_address_id': delivery_addr['delivery']})
757
def change_currency(self, cr, uid, ids, context=None):
759
Launches the wizard to change the currency and update lines
764
if isinstance(ids, (int, long)):
767
for order in self.browse(cr, uid, ids, context=context):
768
data = {'order_id': order.id,
769
'partner_id': order.partner_id.id,
770
'partner_type': order.partner_id.partner_type,
771
'new_pricelist_id': order.pricelist_id.id,
772
'currency_rate': 1.00,
773
'old_pricelist_id': order.pricelist_id.id}
774
wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
775
return {'type': 'ir.actions.act_window',
776
'res_model': 'purchase.order.change.currency',
784
def order_line_change(self, cr, uid, ids, order_line):
785
res = {'no_line': True}
788
res = {'no_line': False}
790
return {'value': res}
792
def _get_destination_ok(self, cr, uid, lines, context):
795
is_inkind = line.order_id and line.order_id.order_type == 'in_kind' or False
796
dest_ok = line.account_4_distribution and line.account_4_distribution.destination_ids or False
799
raise osv.except_osv(_('Error'), _('No destination found. An In-kind Donation account is probably missing for this line: %s.') % (line.name or ''))
800
raise osv.except_osv(_('Error'), _('No destination found for this line: %s.') % (line.name or '',))
803
def check_analytic_distribution(self, cr, uid, ids, context=None):
805
Check analytic distribution validity for given PO.
806
Also check that partner have a donation account (is PO is in_kind)
809
ad_obj = self.pool.get('analytic.distribution')
810
ccdl_obj = self.pool.get('cost.center.distribution.line')
811
pol_obj = self.pool.get('purchase.order.line')
816
if isinstance(ids, (int, long)):
819
# Analytic distribution verification
820
for po in self.browse(cr, uid, ids, context=context):
822
intermission_cc = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'analytic_distribution',
823
'analytic_account_project_intermission')[1]
827
if po.order_type == 'in_kind' and not po.partner_id.donation_payable_account:
828
if not po.partner_id.donation_payable_account:
829
raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
831
if po.partner_id and po.partner_id.partner_type == 'intermission':
832
if not intermission_cc:
833
raise osv.except_osv(_('Error'), _('No Intermission Cost Center found!'))
835
for pol in po.order_line:
836
distrib = pol.analytic_distribution_id or po.analytic_distribution_id or False
837
# Raise an error if no analytic distribution found
839
# UFTP-336: For the case of a new line added from Coordo, it's a push flow, no need to check the AD! VERY SPECIAL CASE
840
if not po.order_type in ('loan', 'donation_st', 'donation_exp', 'in_kind') and not po.push_fo:
841
raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
843
# UF-2031: If no distrib accepted (for loan, donation), then do not process the distrib
845
elif pol.analytic_distribution_state != 'valid':
846
id_ad = ad_obj.create(cr, uid, {})
847
ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_center_lines or po.analytic_distribution_id.cost_center_lines
848
bro_dests = self._get_destination_ok(cr, uid, [pol], context=context)
849
for line in ad_lines:
850
# fetch compatible destinations then use on of them:
851
# - destination if compatible
852
# - else default destination of given account
853
if line.destination_id in bro_dests:
854
bro_dest_ok = line.destination_id
856
bro_dest_ok = pol.account_4_distribution.default_destination_id
857
# Copy cost center line to the new distribution
858
ccdl_obj.copy(cr, uid, line.id, {
859
'distribution_id': id_ad,
860
'destination_id': bro_dest_ok.id,
861
'partner_type': pol.order_id.partner_id.partner_type,
864
pol_obj.write(cr, uid, [pol.id], {'analytic_distribution_id': id_ad})
866
ad_lines = pol.analytic_distribution_id and pol.analytic_distribution_id.cost_center_lines or po.analytic_distribution_id.cost_center_lines
867
for line in ad_lines:
868
if not line.partner_type:
869
ccdl_obj.write(cr, uid, [line.id], {
870
'partner_type': pol.order_id.partner_id.partner_type,
875
def wkf_picking_done(self, cr, uid, ids, context=None):
877
Change the shipped boolean and the state of the PO
879
for order in self.browse(cr, uid, ids, context=context):
880
if order.order_type == 'direct':
881
self.write(cr, uid, order.id, {'state': 'approved'}, context=context)
883
self.write(cr, uid, order.id, {'shipped':1,'state':'approved'}, context=context)
887
def confirm_button(self, cr, uid, ids, context=None):
889
check the supplier partner type (partner_type)
891
confirmation is needed for internal, inter-mission and inter-section
893
('internal', 'Internal'), ('section', 'Inter-section'), ('intermission', 'Intermission')
896
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)?")
899
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)?"
900
clazz = 'purchase.order'
901
func = '_purchase_approve'
905
for obj in self.browse(cr, uid, ids, context=context):
906
if obj.partner_id.partner_type in ('internal', 'section', 'intermission'):
908
wiz_obj = self.pool.get('wizard')
909
# open the selected wizard
910
res = wiz_obj.open_wizard(cr, uid, ids, name=name, model=model, step=step, context=dict(context, question=question,
911
callback={'clazz': clazz,
917
# otherwise call function directly
918
return self.purchase_approve(cr, uid, ids, context=context)
920
def _purchase_approve(self, cr, uid, ids, context=None):
922
interface for call from wizard
924
if called from wizard without opening a new dic -> return close
925
if called from wizard with new dic -> open new wizard
927
if called from button directly, this interface is not called
929
res = self.purchase_approve(cr, uid, ids, context=context)
930
if not isinstance(res, dict):
931
return {'type': 'ir.actions.act_window_close'}
934
def purchase_approve(self, cr, uid, ids, context=None):
936
If the PO is a DPO, check the state of the stock moves
939
sale_line_obj = self.pool.get('sale.order.line')
940
stock_move_obj = self.pool.get('stock.move')
941
wiz_obj = self.pool.get('purchase.order.confirm.wizard')
943
if isinstance(ids, (int, long)):
946
wf_service = netsvc.LocalService("workflow")
947
move_obj = self.pool.get('stock.move')
949
for order in self.browse(cr, uid, ids, context=context):
950
if not order.delivery_confirmed_date:
951
raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
953
if order.order_type == 'direct':
957
for line in order.order_line:
958
if line.procurement_id: todo.append(line.procurement_id.id)
961
todo2 = sale_line_obj.search(cr, uid, [('procurement_id', 'in', todo)], context=context)
964
sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
966
for move in move_obj.browse(cr, uid, sm_ids, context=context):
967
backmove_ids = stock_move_obj.search(cr, uid, [('backmove_id', '=', move.id)])
968
if move.state == 'done':
969
error_moves.append(move)
971
for bmove in move_obj.browse(cr, uid, backmove_ids):
972
error_moves.append(bmove)
975
errors = '''You are trying to confirm a Direct Purchase Order.
976
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected
977
stock moves which are already processed : '''
978
for m in error_moves:
979
errors = '%s \n %s' % (errors, '''
980
* 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))
982
errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
984
wiz_id = wiz_obj.create(cr, uid, {'order_id': order.id,
986
return {'type': 'ir.actions.act_window',
987
'res_model': 'purchase.order.confirm.wizard',
993
# If no errors, validate the DPO
994
wf_service.trg_validate(uid, 'purchase.order', order.id, 'purchase_confirmed_wait', cr)
998
def get_so_ids_from_po_ids(self, cr, uid, ids, context=None, sol_ids=[]):
1000
receive the list of purchase order ids
1002
return the list of sale order ids corresponding (through procurement process)
1004
# Some verifications
1007
if isinstance(ids, (int, long)):
1011
sol_obj = self.pool.get('sale.order.line')
1015
# get the sale order lines
1017
sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
1019
# list of dictionaries for each sale order line
1020
datas = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
1021
# we retrieve the list of sale order ids
1023
if data['order_id'] and data['order_id'][0] not in so_ids:
1024
so_ids.append(data['order_id'][0])
1026
for po in self.browse(cr, uid, ids, context=context):
1027
for line in po.order_line:
1028
if line.procurement_id and line.procurement_id.sale_id and line.procurement_id.sale_id.id not in so_ids:
1029
so_ids.append(line.procurement_id.sale_id.id)
1033
def get_sol_ids_from_po_ids(self, cr, uid, ids, context=None):
1035
receive the list of purchase order ids
1037
return the list of sale order line ids corresponding (through procurement process)
1039
# Some verifications
1042
if isinstance(ids, (int, long)):
1046
sol_obj = self.pool.get('sale.order.line')
1047
# procurement ids list
1049
# sale order lines list
1052
for po in self.browse(cr, uid, ids, context=context):
1053
for line in po.order_line:
1054
if line.procurement_id:
1055
proc_ids.append(line.procurement_id.id)
1056
# get the corresponding sale order line list
1058
sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
1061
# @@@override purchase->purchase.py>purchase_order>wkf_confirm_order
1062
def wkf_confirm_order(self, cr, uid, ids, context=None):
1064
Update the confirmation date of the PO at confirmation.
1065
Check analytic distribution.
1068
po_line_obj = self.pool.get('purchase.order.line')
1073
if isinstance(ids, (int, long)):
1078
for po in self.browse(cr, uid, ids, context=context):
1080
if po.order_type == 'regular':
1081
cr.execute('SELECT line_number FROM purchase_order_line WHERE (price_unit*product_qty < 0.01 OR price_unit = 0.00) AND order_id = %s', (po.id,))
1082
line_errors = cr.dictfetchall()
1083
for l_id in line_errors:
1084
if l_id not in line_error:
1085
line_error.append(l_id['line_number'])
1087
if len(line_error) > 0:
1088
errors = ' / '.join(str(x) for x in line_error)
1089
raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price or 0.00 Subtotal. Lines in exception : %s') % errors)
1091
# Check if the pricelist of the order is good according to currency of the partner
1092
pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', po.partner_id.partner_type)], context=context)
1093
if po.pricelist_id.id not in pricelist_ids:
1094
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.'))
1096
if not po.split_po and not po.order_line:
1097
raise osv.except_osv(_('Error !'), _('You can not validate a purchase order without Purchase Order Lines.'))
1099
if po.order_type == 'purchase_list' and po.amount_total == 0: # UFTP-69
1100
raise osv.except_osv(_('Error'), _('You can not validate a purchase list with a total amount of 0.'))
1102
for line in po.order_line:
1103
if line.state=='draft':
1104
todo.append(line.id)
1106
message = _("Purchase order '%s' is validated.") % (po.name,)
1107
self.log(cr, uid, po.id, message)
1108
# hook for corresponding Fo update
1109
self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
1111
po_line_obj.action_confirm(cr, uid, todo, context)
1113
self.write(cr, uid, ids, {'state' : 'confirmed',
1115
'date_confirm': strftime('%Y-%m-%d')}, context=context)
1117
self.check_analytic_distribution(cr, uid, ids, context=context)
1121
def common_code_from_wkf_approve_order(self, cr, uid, ids, context=None):
1123
delivery confirmed date at po level is mandatory
1124
update corresponding date at line level if needed.
1125
Check analytic distribution
1126
Check that no line have a 0 price unit.
1129
po_line_obj = self.pool.get('purchase.order.line')
1134
if isinstance(ids, (int, long)):
1137
# Check analytic distribution
1138
self.check_analytic_distribution(cr, uid, ids, context=context)
1139
for po in self.browse(cr, uid, ids, context=context):
1140
# prepare some values
1141
is_regular = po.order_type == 'regular' # True if order_type is regular, else False
1143
# msf_order_date checks
1144
if po.state == 'approved' and not po.delivery_confirmed_date:
1145
raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
1146
# for all lines, if the confirmed date is not filled, we copy the header value
1148
cr.execute('SELECT line_number FROM purchase_order_line WHERE (price_unit*product_qty < 0.01 OR price_unit = 0.00) AND order_id = %s', (po.id,))
1149
line_errors = cr.dictfetchall()
1150
for l_id in line_errors:
1151
if l_id not in line_error:
1152
line_error.append(l_id['line_number'])
1154
if len(line_error) > 0:
1155
errors = ' / '.join(str(x) for x in line_error)
1156
raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price or 0.00 Subtotal. Lines in exception : %s') % errors)
1158
lines_to_update = po_line_obj.search(
1160
[('order_id', '=', po.id), ('confirmed_delivery_date', '=', False)],
1163
po_line_obj.write(cr, uid, lines_to_update, {'confirmed_delivery_date': po.delivery_confirmed_date}, context=context)
1164
# MOVE code for COMMITMENT into wkf_approve_order
1167
def create_extra_lines_on_fo(self, cr, uid, ids, context=None):
1169
Creates FO/IR lines according to PO extra lines
1172
sol_obj = self.pool.get('sale.order.line')
1173
so_obj = self.pool.get('sale.order')
1174
ad_obj = self.pool.get('analytic.distribution')
1175
proc_obj = self.pool.get('procurement.order')
1176
pick_obj = self.pool.get('stock.picking')
1177
move_obj = self.pool.get('stock.move')
1182
if isinstance(ids, (int, long)):
1187
for order in self.browse(cr, uid, ids, context=context):
1188
for l in order.order_line:
1189
link_so_id = l.link_so_id and l.link_so_id.state in ('sourced', 'progress', 'manual')
1190
if link_so_id and (not l.procurement_id or not l.procurement_id.sale_id):
1196
if l.analytic_distribution_id:
1197
new_distrib = ad_obj.copy(cr, uid, l.analytic_distribution_id.id, {}, context=context)
1198
elif not l.analytic_distribution_id and l.order_id and l.order_id.analytic_distribution_id:
1199
new_distrib = ad_obj.copy(cr, uid, l.order_id.analytic_distribution_id.id, {}, context=context)
1200
# Creates the FO lines
1201
tmp_sale_context = context.get('sale_id')
1202
# create new line in FOXXXX-Y
1203
context['sale_id'] = l.link_so_id.id
1204
vals = {'order_id': l.link_so_id.id,
1205
'product_id': l.product_id.id,
1206
'product_uom': l.product_uom.id,
1207
'product_uom_qty': l.product_qty,
1208
'price_unit': l.price_unit,
1209
'procurement_id': l.procurement_id and l.procurement_id.id or False,
1210
'type': 'make_to_order',
1211
'supplier': l.order_id.partner_id.id,
1212
'analytic_distribution_id': new_distrib,
1213
'created_by_po': not l.order_id.rfq_ok and l.order_id.id or False,
1214
'created_by_po_line': not l.order_id.rfq_ok and l.id or False,
1215
'created_by_rfq': l.order_id.rfq_ok and l.order_id.id or False,
1216
'created_by_rfq_line': l.order_id.rfq_ok and l.id or False,
1217
'po_cft': l.order_id.rfq_ok and 'rfq' or 'po',
1218
'sync_sourced_origin': l.instance_sync_order_ref and l.instance_sync_order_ref.name or False,
1219
#'is_line_split': l.is_line_split,
1220
'name': '[%s] %s' % (l.product_id.default_code, l.product_id.name)}
1222
new_line_id = sol_obj.create(cr, uid, vals, context=context)
1224
# Put the sale_id in the procurement order
1225
if l.procurement_id:
1226
proc_obj.write(cr, uid, [l.procurement_id.id], {
1227
'sale_id': l.link_so_id.id,
1228
'purchase_id': l.order_id.id,
1230
# Create new line in FOXXXX (original FO)
1231
if l.link_so_id.original_so_id_sale_order:
1232
context['sale_id'] = l.link_so_id.original_so_id_sale_order.id
1233
vals.update({'order_id': l.link_so_id.original_so_id_sale_order.id,
1235
sol_obj.create(cr, uid, vals, context=context)
1236
context['sale_id'] = tmp_sale_context
1238
# If the order is an Internal request with External location, create a new
1239
# stock move on the picking ticket (if not closed)
1240
# Get move data and create the move
1241
if l.link_so_id.procurement_request and l.link_so_id.location_requestor_id.usage == 'customer' and l.product_id.type == 'product':
1242
# Get OUT linked to IR
1243
pick_to_confirm = None
1244
out_ids = pick_obj.search(cr, uid, [
1245
('sale_id', '=', l.link_so_id.id),
1246
('type', '=', 'out'),
1247
('state', 'in', ['draft', 'confirmed', 'assigned']),
1250
picking_data = so_obj._get_picking_data(cr, uid, l.link_so_id)
1251
out_ids = [pick_obj.create(cr, uid, picking_data, context=context)]
1252
pick_to_confirm = out_ids
1254
ir_line = sol_obj.browse(cr, uid, new_line_id, context=context)
1255
move_data = so_obj._get_move_data(cr, uid, l.link_so_id, ir_line, out_ids[0], context=context)
1256
move_obj.create(cr, uid, move_data, context=context)
1259
pick_obj.action_confirm(cr, uid, pick_to_confirm, context=context)
1261
sol_ids.add(l.link_so_id.id)
1264
so_obj.action_ship_proc_create(cr, uid, list(sol_ids), context=context)
1268
def wkf_confirm_wait_order(self, cr, uid, ids, context=None):
1271
1/ if all purchase line could take an analytic distribution
1272
2/ if a commitment voucher should be created after PO approbation
1274
_> originally in purchase.py from analytic_distribution_supply
1276
Checks if the Delivery Confirmed Date has been filled
1278
_> originally in order_dates.py from msf_order_date
1280
# Some verifications
1283
if isinstance(ids, (int, long)):
1287
sol_obj = self.pool.get('sale.order.line')
1288
exp_sol_obj = self.pool.get('expected.sale.order.line')
1289
so_obj = self.pool.get('sale.order')
1291
# Create extra lines on the linked FO/IR
1292
self.create_extra_lines_on_fo(cr, uid, ids, context=context)
1294
# code from wkf_approve_order
1295
self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
1296
# set the state of purchase order to confirmed_wait
1297
self.write(cr, uid, ids, {'state': 'confirmed_wait'}, context=context)
1298
sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
1300
# corresponding sale order
1301
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context, sol_ids=sol_ids)
1302
# UF-2509: so_ids is a list, not an int
1303
exp_sol_ids = exp_sol_obj.search(cr, uid, [('order_id', 'in', so_ids)], context=context)
1304
# from so, list corresponding po
1305
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1306
for exp_sol in exp_sol_obj.browse(cr, uid, exp_sol_ids, context=context):
1307
# UFTP-335: Added a check in if to avoid False value being taken
1308
if exp_sol.po_id and exp_sol.po_id.id not in all_po_ids:
1309
all_po_ids.append(exp_sol.po_id.id)
1310
list_po_name = ', '.join([linked_po.name for linked_po in self.browse(cr, uid, all_po_ids, context) if linked_po.id != ids[0]])
1311
self.log(cr, uid, ids[0], _("The order %s is in confirmed (waiting) state and will be confirmed once the related orders [%s] would have been confirmed"
1312
) % (self.read(cr, uid, ids, ['name'])[0]['name'], list_po_name))
1313
# sale order lines with modified state
1315
sol_obj.write(cr, uid, sol_ids, {'state': 'confirmed'}, context=context)
1317
# !!BEWARE!! we must update the So lines before any writing to So objects
1318
for po in self.browse(cr, uid, ids, context=context):
1319
# hook for corresponding Fo update
1320
context['wait_order'] = True
1321
self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po, so_ids=so_ids)
1322
del context['wait_order']
1326
def compute_confirmed_delivery_date(self, cr, uid, ids, confirmed, prep_lt, ship_lt, est_transport_lead_time, db_date_format, context=None):
1328
compute the confirmed date
1330
confirmed must be string
1331
return string corresponding to database format
1333
assert type(confirmed) == str
1334
confirmed = datetime.strptime(confirmed, db_date_format)
1335
confirmed = confirmed + relativedelta(days=prep_lt or 0)
1336
confirmed = confirmed + relativedelta(days=ship_lt or 0)
1337
confirmed = confirmed + relativedelta(days=est_transport_lead_time or 0)
1338
confirmed = confirmed.strftime(db_date_format)
1342
def _hook_confirm_order_update_corresponding_so(self, cr, uid, ids, context=None, *args, **kwargs):
1344
Add a hook to update correspondingn so
1346
# Some verifications
1349
if isinstance(ids, (int, long)):
1354
so_ids= kwargs.get('so_ids')
1355
pol_obj = self.pool.get('purchase.order.line')
1356
so_obj = self.pool.get('sale.order')
1357
sol_obj = self.pool.get('sale.order.line')
1358
move_obj = self.pool.get('stock.move')
1359
proc_obj = self.pool.get('procurement.order')
1360
pick_obj = self.pool.get('stock.picking')
1361
uom_obj = self.pool.get('product.uom')
1362
ad_obj = self.pool.get('analytic.distribution')
1363
date_tools = self.pool.get('date.tools')
1364
fields_tools = self.pool.get('fields.tools')
1365
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
1366
wf_service = netsvc.LocalService("workflow")
1368
# update corresponding fo if exist
1370
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1371
ctx = context.copy()
1372
ctx['no_store_function'] = ['sale.order.line']
1377
ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
1378
prep_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='preparation_lead_time', context=context)
1380
for line in po.order_line:
1381
# get the corresponding so line
1382
sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
1384
store_to_call += sol_ids
1386
sol = sol_obj.browse(cr, uid, sol_ids[0], context=context)
1388
# do not update Internal Requests with internal requestor location
1389
if so and so.procurement_request and so.location_requestor_id.usage != 'customer':
1392
line_confirmed = False
1393
# compute confirmed date for line
1394
if line.confirmed_delivery_date:
1395
line_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, line.confirmed_delivery_date,
1396
prep_lt, ship_lt, so.est_transport_lead_time,
1397
db_date_format, context=context)
1399
# we update the corresponding sale order line
1401
# compute the price_unit value - we need to specify the date
1402
date_context = {'date': po.date_order}
1404
# convert from currency of pol to currency of sol
1405
price_unit_converted = self.pool.get('res.currency').compute(cr, uid, line.currency_id.id,
1406
sol.currency_id.id, line.price_unit or 0.0,
1407
round=False, context=date_context)
1409
if so.order_type == 'regular' and price_unit_converted < 0.00001:
1410
price_unit_converted = 0.00001
1412
line_qty = line.product_qty
1413
if line.procurement_id:
1414
other_po_lines = pol_obj.search(cr, uid, [
1415
('procurement_id', '=', line.procurement_id.id),
1416
('id', '!=', line.id),
1417
'|', ('order_id.id', '=', line.order_id.id), ('order_id.state', 'in', ['sourced', 'approved']),
1419
for opl in pol_obj.browse(cr, uid, other_po_lines, context=context):
1420
if opl.product_uom.id != line.product_uom.id:
1421
line_qty += uom_obj._compute_qty(cr, uid, opl.product_uom.id, opl.product_qty, line.product_uom.id)
1423
line_qty += opl.product_qty
1425
fields_dic = {'product_id': line.product_id and line.product_id.id or False,
1427
'default_name': line.default_name,
1428
'default_code': line.default_code,
1429
'product_uom_qty': line_qty,
1430
'product_uom': line.product_uom and line.product_uom.id or False,
1431
'product_uos_qty': line_qty,
1432
'product_uos': line.product_uom and line.product_uom.id or False,
1433
'price_unit': price_unit_converted,
1434
'nomenclature_description': line.nomenclature_description,
1435
'nomenclature_code': line.nomenclature_code,
1436
'comment': line.comment,
1437
'nomen_manda_0': line.nomen_manda_0 and line.nomen_manda_0.id or False,
1438
'nomen_manda_1': line.nomen_manda_1 and line.nomen_manda_1.id or False,
1439
'nomen_manda_2': line.nomen_manda_2 and line.nomen_manda_2.id or False,
1440
'nomen_manda_3': line.nomen_manda_3 and line.nomen_manda_3.id or False,
1441
'nomen_sub_0': line.nomen_sub_0 and line.nomen_sub_0.id or False,
1442
'nomen_sub_1': line.nomen_sub_1 and line.nomen_sub_1.id or False,
1443
'nomen_sub_2': line.nomen_sub_2 and line.nomen_sub_2.id or False,
1444
'nomen_sub_3': line.nomen_sub_3 and line.nomen_sub_3.id or False,
1445
'nomen_sub_4': line.nomen_sub_4 and line.nomen_sub_4.id or False,
1446
'nomen_sub_5': line.nomen_sub_5 and line.nomen_sub_5.id or False,
1447
'confirmed_delivery_date': line_confirmed,
1448
#'is_line_split': line.is_line_split,
1451
UFTP-336: Update the analytic distribution at FO line when
1452
PO is confirmed if lines are created at tender
1453
or RfQ because there is no AD on FO line.
1455
if sol.created_by_tender or sol.created_by_rfq:
1457
if line.analytic_distribution_id:
1458
new_distrib = ad_obj.copy(cr, uid, line.analytic_distribution_id.id, {}, context=context)
1459
elif not line.analytic_distribution_id and line.order_id and line.order_id.analytic_distribution_id:
1460
new_distrib = ad_obj.copy(cr, uid, line.order_id.analytic_distribution_id.id, {}, context=context)
1462
fields_dic['analytic_distribution_id'] = new_distrib
1465
sol_obj.write(cr, uid, sol_ids, fields_dic, context=ctx)
1467
if so.procurement_request and so.location_requestor_id.usage == 'customer' \
1468
and line.procurement_id.move_id \
1469
and not line.procurement_id.move_id.processed_stock_move:
1470
out_move_id = line.procurement_id.move_id
1471
# If there is a non-stockable or service product, remove the OUT
1472
# stock move and update the stock move on the procurement
1473
if context.get('wait_order') and line.product_id.type in ('service', 'service_recep', 'consu') and out_move_id.picking_id:
1474
out_pick_id = out_move_id.picking_id.id
1475
proc_obj.write(cr, uid, [line.procurement_id.id], {
1476
'move_id': line.move_dest_id.id,
1478
move_obj.write(cr, uid, [out_move_id.id], {'state': 'draft'})
1480
picks_to_check.setdefault(out_pick_id, [])
1481
picks_to_check[out_pick_id].append(out_move_id.id)
1483
move_obj.unlink(cr, uid, [out_move_id.id], context=context)
1489
if out_move_id.picking_id and out_move_id.picking_id.backorder_id:
1490
bo_moves = move_obj.search(cr, uid, [
1491
('picking_id', '=', out_move_id.picking_id.backorder_id.id),
1492
('sale_line_id', '=', out_move_id.sale_line_id.id),
1493
('state', '=', 'done'),
1496
boms = move_obj.browse(cr, uid, bo_moves, context=context)
1499
if bom.product_uom.id != out_move_id.product_uom.id:
1500
minus_qty += uom_obj._compute_qty(cr, uid, bom.product_uom.id, bom.product_qty, out_move_id.product_uom.id)
1502
minus_qty += bom.product_qty
1503
if bom.picking_id and bom.picking_id.backorder_id:
1504
bo_moves.extend(move_obj.search(cr, uid, [
1505
('picking_id', '=', bom.picking_id.backorder_id.id),
1506
('sale_line_id', '=', bom.sale_line_id.id),
1507
('state', '=', 'done'),
1508
], context=context))
1510
if out_move_id.product_uom.id != line.product_uom.id:
1511
minus_qty = uom_obj._compute_qty(cr, uid, out_move_id.product_uom.id, minus_qty, line.product_uom.id)
1513
if out_move_id.state == 'assigned':
1514
move_obj.cancel_assign(cr, uid, [out_move_id.id])
1515
elif out_move_id.state in ('cancel', 'done'):
1520
'product_uom': line.product_uom and line.product_uom.id or False,
1521
'product_uos': line.product_uom and line.product_uom.id or False,
1522
'product_qty': line_qty - minus_qty,
1523
'product_uos_qty': line_qty - minus_qty,
1526
move_dic['product_id'] = line.product_id.id
1527
if line.product_uom:
1529
'product_uom': line.product_uom.id,
1530
'product_uos': line.product_uom.id,
1532
move_obj.write(cr, uid, [out_move_id.id], move_dic, context=context)
1535
sol_obj._call_store_function(cr, uid, store_to_call, keys=None, bypass=False, context=context)
1536
# compute so dates -- only if we get a confirmed value, because rts is mandatory on So side
1537
# update after lines update, as so write triggers So workflow, and we dont want the Out document
1538
# to be created with old So datas
1539
if po.delivery_confirmed_date:
1540
for so in so_obj.browse(cr, uid, so_ids, context=context):
1541
# Fo rts = Po confirmed date + prep_lt
1542
delivery_confirmed_date = datetime.strptime(po.delivery_confirmed_date, db_date_format)
1543
so_rts = delivery_confirmed_date + relativedelta(days=prep_lt or 0)
1544
so_rts = so_rts.strftime(db_date_format)
1546
# Fo confirmed date = confirmed date + prep_lt + ship_lt + transport_lt
1547
so_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, po.delivery_confirmed_date,
1548
prep_lt, ship_lt, so.est_transport_lead_time,
1549
db_date_format, context=context)
1551
so_obj.write(cr, uid, [so.id], {'delivery_confirmed_date': so_confirmed,
1552
'ready_to_ship_date': so_rts}, context=context)
1553
wf_service.trg_write(uid, 'sale.order', so.id, cr)
1555
for out_pick_id, out_move_ids in picks_to_check.iteritems():
1556
full_out = pick_obj.read(cr, uid, out_pick_id, ['move_lines'])['move_lines']
1557
for om_id in out_move_ids:
1558
if om_id in full_out:
1559
full_out.remove(om_id)
1561
if out_pick_id and not full_out:
1562
pick_obj.write(cr, uid, [out_pick_id], {'state': 'draft'}, context=context)
1563
move_obj.unlink(cr, uid, out_move_ids)
1564
pick_obj.unlink(cr, uid, out_pick_id)
1566
move_obj.unlink(cr, uid, out_move_ids)
1570
def check_if_product(self, cr, uid, ids, context=None):
1572
Check if all line have a product before confirming the Purchase Order
1574
if isinstance(ids, (int, long)):
1577
for po in self.browse(cr, uid, ids, context=context):
1579
for line in po.order_line:
1580
if not line.product_id:
1581
raise osv.except_osv(_('Error !'), _('You should have a product on all Purchase Order lines to be able to confirm the Purchase Order.') )
1584
def sourcing_document_state(self, cr, uid, ids, context=None):
1586
Returns all documents that are in the sourcing for a given PO
1591
if isinstance(ids, (int, long)):
1594
sol_obj = self.pool.get('sale.order.line')
1595
so_obj = self.pool.get('sale.order')
1597
# corresponding sale order
1598
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1599
# from so, list corresponding po
1600
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1602
# from listed po, list corresponding so
1603
all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1605
all_sol_not_confirmed_ids = []
1606
# if we have sol_ids, we are treating a po which is make_to_order from sale order
1608
all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
1609
('type', '=', 'make_to_order'),
1610
('product_id', '!=', False),
1611
('procurement_id.state', '!=', 'cancel'),
1612
('state', 'not in', ['confirmed', 'done'])], context=context)
1614
return so_ids, all_po_ids, all_so_ids, all_sol_not_confirmed_ids
1617
def all_po_confirmed(self, cr, uid, ids, context=None):
1619
condition for the po to leave the act_confirmed_wait state
1621
if the po is from scratch (no procurement), or from replenishment mechanism (procurement but no sale order line)
1622
the method will return True and therefore the po workflow is not blocked
1624
only 'make_to_order' sale order lines are checked, we dont care on state of 'make_to_stock' sale order line
1625
_> anyway, thanks to Fo split, make_to_stock and make_to_order so lines are separated in different sale orders
1627
# Some verifications
1630
if isinstance(ids, (int, long)):
1634
exp_sol_obj = self.pool.get('expected.sale.order.line')
1635
sol_obj = self.pool.get('sale.order.line')
1636
so_obj = self.pool.get('sale.order')
1638
# corresponding sale order
1639
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1640
# from so, list corresponding po
1641
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1643
# from listed po, list corresponding so
1644
all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1645
# if we have sol_ids, we are treating a po which is make_to_order from sale order
1647
# we retrieve the list of ids of all sale order line if type 'make_to_order' with state != 'confirmed'
1648
# with product_id (if no product id, no procurement, no po, so should not be taken into account)
1649
# in case of grouped po, multiple Fo depend on this po, all Po of these Fo need to be completed
1650
# and all Fo will be confirmed together. Because IN of grouped Po need corresponding OUT document of all Fo
1651
# internal request are automatically 'confirmed'
1652
# not take done into account, because IR could be done as they are confirmed before the Po are all done
1653
# see video in uf-1050 for detail
1654
all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
1655
('type', '=', 'make_to_order'),
1656
('product_id', '!=', False),
1657
('procurement_id.state', '!=', 'cancel'),
1658
('state', 'not in', ['confirmed', 'done'])], context=context)
1660
all_exp_sol_not_confirmed_ids = exp_sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids)], context=context)
1662
# if any lines exist, we return False
1663
if all_sol_not_confirmed_ids or all_exp_sol_not_confirmed_ids:
1668
def wkf_confirm_trigger(self, cr, uid, ids, context=None):
1670
trigger corresponding so then po
1672
# Some verifications
1675
if isinstance(ids, (int, long)):
1679
so_obj = self.pool.get('sale.order')
1680
wf_service = netsvc.LocalService("workflow")
1682
# corresponding sale order
1683
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
1684
# from so, list corresponding po first level
1685
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
1686
# from listed po, list corresponding so
1687
all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
1688
# from all so, list all corresponding po second level
1689
all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
1691
not_confirmed_po = self.search(cr, uid, [
1692
('id', 'not in', all_po_for_all_so_ids),
1693
('state', '=', 'confirmed_wait'),
1696
# we trigger all the corresponding sale order -> test_lines is called on these so
1697
for so_id in all_so_ids:
1698
wf_service.trg_write(uid, 'sale.order', so_id, cr)
1700
# we trigger pos of all sale orders -> all_po_confirm is called on these po
1701
for po_id in all_po_for_all_so_ids:
1702
wf_service.trg_write(uid, 'purchase.order', po_id, cr)
1704
for po_id in not_confirmed_po:
1705
wf_service.trg_write(uid, 'purchase.order', po_id, cr)
246
return super(purchase_order, self)._hook_confirm_order_message(cr, uid, context, args, kwargs)
1709
248
def wkf_approve_order(self, cr, uid, ids, context=None):
1711
250
Checks if the invoice should be create from the purchase order
2576
770
line = self.browse(cr, uid, line_id, context=context)
2577
771
# Remove the qty from the merged line
2578
772
if line.merged_id:
2579
merged_id = line.merged_id.id
2580
change_price_ok = line.change_price_ok
2582
c.update({'change_price_ok': change_price_ok})
2583
noraise_ctx = context.copy()
2584
noraise_ctx.update({'noraise': True})
2585
773
# Need removing the merged_id link before update the merged line because the merged line
2586
774
# will be removed if it hasn't attached PO line
2587
self.write(cr, uid, [line.id], {'merged_id': False}, context=noraise_ctx)
2588
res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
775
self.write(cr, uid, [line.id], {'merged_id': False}, context=context)
776
res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, -line.product_qty, line.price_unit, context=context)
2592
def _check_restriction_line(self, cr, uid, ids, context=None):
2594
Check if there is restriction on lines
2596
if isinstance(ids, (int, long)):
2602
for line in self.browse(cr, uid, ids, context=context):
2603
if line.order_id and line.order_id.partner_id and line.order_id.state != 'done' and line.product_id:
2604
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):
2609
def _relatedFields(self, cr, uid, vals, context=None):
2611
related fields for create and write
2613
# recreate description because in readonly
2614
if ('product_id' in vals) and (vals['product_id']):
2615
# no nomenclature description
2616
vals.update({'nomenclature_description':False})
2617
# update the name (comment) of order line
2618
# the 'name' is no more the get_name from product, but instead
2619
# the name of product
2620
productObj = self.pool.get('product.product').browse(cr, uid, vals['product_id'], context=context)
2621
vals.update({'name':productObj.name})
2622
vals.update({'default_code':productObj.default_code})
2623
vals.update({'default_name':productObj.name})
2624
# erase the nomenclature - readonly
2625
self.pool.get('product.product')._resetNomenclatureFields(vals)
2626
elif ('product_id' in vals) and (not vals['product_id']):
2627
sale = self.pool.get('sale.order.line')
2628
sale._setNomenclatureInfo(cr, uid, vals, context)
2629
# erase default code
2630
vals.update({'default_code':False})
2631
vals.update({'default_name':False})
2633
if 'comment' in vals:
2634
vals.update({'name':vals['comment']})
2635
# clear nomenclature filter values
2636
#self.pool.get('product.product')._resetNomenclatureFields(vals)
2638
def _update_name_attr(self, cr, uid, vals, context=None):
2639
"""Update the name attribute in `vals` if a product is selected."""
2642
prod_obj = self.pool.get('product.product')
2643
if vals.get('product_id'):
2644
product = prod_obj.browse(cr, uid, vals['product_id'], context=context)
2645
vals['name'] = product.name
2646
elif vals.get('comment'):
2647
vals['name'] = vals.get('comment', False)
2649
def _check_product_uom(self, cr, uid, product_id, uom_id, context=None):
2650
"""Check the product UoM."""
2653
uom_tools_obj = self.pool.get('uom.tools')
2654
if not uom_tools_obj.check_uom(cr, uid, product_id, uom_id, context=context):
2655
raise osv.except_osv(
2657
_('You have to select a product UOM in the same '
2658
'category than the purchase UOM of the product !'))
2660
780
def create(self, cr, uid, vals, context=None):
2662
782
Create or update a merged line
2667
po_obj = self.pool.get('purchase.order')
2668
seq_pool = self.pool.get('ir.sequence')
2669
so_obj = self.pool.get('sale.order')
2670
sol_obj = self.pool.get('sale.order.line')
787
vals.update({'old_price_unit': vals.get('price_unit', False)})
789
order_id = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context)
790
if order_id.from_yml_test:
791
vals.update({'change_price_manually': True})
792
if not vals.get('product_qty', False):
793
vals['product_qty'] = 1.00
795
# If we are on a RfQ, use the last entered unit price and update other lines with this price
797
vals.update({'change_price_manually': True})
799
if vals.get('product_qty', 0.00) == 0.00:
800
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
802
if vals.get('price_unit', 0.00) == 0.00:
803
raise osv.except_osv(_('Error'), _('You cannot save a line with no unit price !'))
2672
805
order_id = vals.get('order_id')
2673
806
product_id = vals.get('product_id')
2674
807
product_uom = vals.get('product_uom')
2675
order = po_obj.browse(cr, uid, order_id, context=context)
2677
if order.from_yml_test:
2678
vals.update({'change_price_manually': True})
2679
if not vals.get('product_qty', False):
2680
vals['product_qty'] = 1.00
2681
# [imported and adapted from 'analytic_distribution_supply']
2682
if not vals.get('price_unit', False):
2683
vals['price_unit'] = 1.00
2686
# Update the name attribute if a product is selected
2687
self._update_name_attr(cr, uid, vals, context=context)
2689
# If we are on a RfQ, use the last entered unit price and update other lines with this price
2691
vals.update({'change_price_manually': True})
2693
if order.po_from_fo or order.po_from_ir:
2694
vals['from_fo'] = True
2695
if vals.get('product_qty', 0.00) == 0.00 and not context.get('noraise'):
2696
raise osv.except_osv(
2698
_('You can not have an order line with a negative or zero quantity')
808
order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
2701
809
other_lines = self.search(cr, uid, [('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
2702
810
stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
2704
if vals.get('origin'):
2706
if vals.get('procurement_id'):
2707
proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id'))
2708
if not proc or not proc.sale_id:
2709
vals.update(self.update_origin_link(cr, uid, vals.get('origin'), context=context))
2711
812
if (other_lines and stages and order.state != 'confirmed'):
2712
813
context.update({'change_price_ok': False})
2714
if not context.get('offline_synchronization'):
2715
vals = self._update_merged_line(cr, uid, False, vals, context=dict(context, skipResequencing=True))
2717
vals.update({'old_price_unit': vals.get('price_unit', False)})
2719
# [imported from 'order_nomenclature']
2720
# Don't save filtering data
2721
self._relatedFields(cr, uid, vals, context)
2724
# [imported from 'order_line_number']
2725
# Add the corresponding line number
2726
# I leave this line from QT related to purchase.order.merged.line for compatibility and safety reasons
2727
# merged lines, set the line_number to 0 when calling create function
2728
# the following line should *logically* be removed safely
2729
# copy method should work as well, as merged line do *not* need to keep original line number with copy function (QT confirmed)
2730
if self._name != 'purchase.order.merged.line':
2732
# gather the line number from the sale order sequence if not specified in vals
2733
# either line_number is not specified or set to False from copy, we need a new value
2734
if not vals.get('line_number', False):
2735
# new number needed - gather the line number from the sequence
2736
sequence_id = order.sequence_id.id
2737
line = seq_pool.get_id(cr, uid, sequence_id, code_or_id='id', context=context)
2738
vals.update({'line_number': line})
2741
# Check the selected product UoM
2742
if not context.get('import_in_progress', False):
2743
if vals.get('product_id') and vals.get('product_uom'):
2744
self._check_product_uom(
2745
cr, uid, vals['product_id'], vals['product_uom'], context=context)
2747
# utp-518:we write the comment from the sale.order.line on the PO line through the procurement (only for the create!!)
2748
po_procurement_id = vals.get('procurement_id', False)
2749
if po_procurement_id:
2750
sale_id = sol_obj.search(cr, uid, [('procurement_id', '=', po_procurement_id)], context=context)
2752
comment_so = sol_obj.read(cr, uid, sale_id, ['comment'], context=context)[0]['comment']
2753
vals.update(comment=comment_so)
2755
# add the database Id to the sync_order_line_db_id
2756
po_line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context)
2757
if not vals.get('sync_order_line_db_id', False): #'sync_order_line_db_id' not in vals or vals:
2759
super(purchase_order_line, self).write(cr, uid, [po_line_id], {'sync_order_line_db_id': name + "_" + str(po_line_id),}, context=context)
2761
if self._name != 'purchase.order.merged.line' and vals.get('origin') and not vals.get('procurement_id'):
2762
so_ids = so_obj.search(cr, uid, [('name', '=', vals.get('origin'))], context=context)
2763
for so_id in so_ids:
2764
self.pool.get('expected.sale.order.line').create(cr, uid, {
2766
'po_line_id': po_line_id,
2771
def default_get(self, cr, uid, fields, context=None):
2775
if context.get('purchase_id'):
2776
# Check validity of the purchase order. We write the order to avoid
2777
# the creation of a new line if one line of the order is not valid
2778
# according to the order category
2780
# 1/ Create a new PO with 'Other' as Order Category
2781
# 2/ Add a new line with a Stockable product
2782
# 3/ Change the Order Category of the PO to 'Service' -> A warning message is displayed
2783
# 4/ Try to create a new line -> The system displays a message to avoid you to create a new line
2784
# while the not valid line is not modified/deleted
2786
# Without the write of the order, the message displayed by the system at 4/ is displayed at the saving
2787
# of the new line that is not very understandable for the user
2789
if context.get('partner_id'):
2790
data.update({'partner_id': context.get('partner_id')})
2791
if context.get('categ'):
2792
data.update({'categ': context.get('categ')})
2793
self.pool.get('purchase.order').write(cr, uid, [context.get('purchase_id')], data, context=context)
2795
return super(purchase_order_line, self).default_get(cr, uid, fields, context=context)
2797
def copy(self, cr, uid, line_id, defaults={}, context=None):
2799
Remove link to merged line
2801
defaults.update({'merged_id': False, 'sync_order_line_db_id': False})
2803
return super(purchase_order_line, self).copy(cr, uid, line_id, defaults, context=context)
2805
def copy_data(self, cr, uid, p_id, default=None, context=None):
2808
# Some verifications
2814
if not 'move_dest_id' in default:
2815
default.update({'move_dest_id': False})
2817
if not 'procurement_id' in default:
2818
default.update({'procurement_id': False})
2820
default.update({'sync_order_line_db_id': False})
2821
return super(purchase_order_line, self).copy_data(cr, uid, p_id, default=default, context=context)
815
vals = self._update_merged_line(cr, uid, False, vals, context=context)
817
return super(purchase_order_line, self).create(cr, uid, vals, context=context)
2823
819
def write(self, cr, uid, ids, vals, context=None):
2825
821
Update merged line
2827
so_obj = self.pool.get('sale.order')
2832
826
if isinstance(ids, (int, long)):
2837
# [imported from the 'analytic_distribution_supply']
2838
# Don't save filtering data
2839
self._relatedFields(cr, uid, vals, context)
2842
# Update the name attribute if a product is selected
2843
self._update_name_attr(cr, uid, vals, context=context)
2845
829
if 'price_unit' in vals:
2846
830
vals.update({'old_price_unit': vals.get('price_unit')})
2848
if ('state' in vals and vals.get('state') != 'draft') or ('procurement_id' in vals and vals.get('procurement_id')):
2849
exp_sol_ids = self.pool.get('expected.sale.order.line').search(cr, uid, [('po_line_id', 'in', ids)], context=context)
2850
self.pool.get('expected.sale.order.line').unlink(cr, uid, exp_sol_ids, context=context)
2852
for line in self.browse(cr, uid, ids, context=context):
2853
new_vals = vals.copy()
2855
if vals.get('product_qty', line.product_qty) <= 0.0 and \
2856
not line.order_id.rfq_ok and \
2857
'noraise' not in context and line.state != 'cancel':
2858
raise osv.except_osv(
2860
_('You can not have an order line with a negative or zero quantity')
2863
if vals.get('origin', line.origin):
2865
if vals.get('procurement_id', line.procurement_id.id):
2866
proc = self.pool.get('procurement.order').browse(cr, uid, vals.get('procurement_id', line.procurement_id.id))
2867
if not proc or not proc.sale_id:
2868
link_so_dict = self.update_origin_link(cr, uid, vals.get('origin', line.origin), context=context)
2869
new_vals.update(link_so_dict)
2871
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):
2872
new_vals['from_fo'] = True
2874
if not context.get('update_merge'):
2875
new_vals.update(self._update_merged_line(cr, uid, line.id, vals, context=dict(context, skipResequencing=True, noraise=True)))
2877
res = super(purchase_order_line, self).write(cr, uid, [line.id], new_vals, context=context)
2879
if self._name != 'purchase.order.merged.line' and vals.get('origin') and not vals.get('procurement_id', line.procurement_id):
2880
so_ids = so_obj.search(cr, uid, [('name', '=', vals.get('origin'))], context=context)
2881
for so_id in so_ids:
2882
self.pool.get('expected.sale.order.line').create(cr, uid, {
2884
'po_line_id': line.id,
2887
# Check the selected product UoM
2888
if not context.get('import_in_progress', False):
2889
for pol_read in self.read(cr, uid, ids, ['product_id', 'product_uom']):
2890
if pol_read.get('product_id'):
2891
product_id = pol_read['product_id'][0]
2892
uom_id = pol_read['product_uom'][0]
2893
self._check_product_uom(cr, uid, product_id, uom_id, context=context)
2897
def update_origin_link(self, cr, uid, origin, context=None):
2899
Return the FO/IR that matches with the origin value
2901
so_obj = self.pool.get('sale.order')
2903
tmp_proc_context = context.get('procurement_request')
2904
context['procurement_request'] = True
2905
so_ids = so_obj.search(cr, uid, [('name', '=', origin), ('state', 'in', ('sourced', 'progress', 'manual'))], context=context)
2906
context['procurement_request'] = tmp_proc_context
2908
return {'link_so_id': so_ids[0]}
2912
def ask_unlink(self, cr, uid, ids, context=None):
2914
Call the unlink method for lines and if the PO becomes empty
2915
ask the user if he wants to cancel the PO
2918
wiz_obj = self.pool.get('purchase.order.line.unlink.wizard')
2919
proc_obj = self.pool.get('procurement.order')
2920
data_obj = self.pool.get('ir.model.data')
2921
wkf_act_obj = self.pool.get('workflow.activity')
2922
opl_obj = self.pool.get('stock.warehouse.orderpoint.line')
2924
# Variables initialization
2928
if isinstance(ids, (int, long)):
2931
# Check if the line is not already removed
2932
ids = self.search(cr, uid, [('id', 'in', ids)], context=context)
2934
raise osv.except_osv(
2936
_('The line has been already deleted - Please refresh the page'),
2939
if context.get('rfq_ok', False):
2940
view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'rfq_line_unlink_wizard_form_view')[1]
2942
view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'purchase_order_line_unlink_wizard_form_view')[1]
2944
for line in self.browse(cr, uid, ids, context=context):
2945
sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
2946
exp_sol_ids = self.get_exp_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
2947
if (sol_ids or exp_sol_ids) and not context.get('from_del_wizard'):
2948
wiz_id = wiz_obj.create(cr, uid, {'line_id': line.id, 'only_exp': (not sol_ids and exp_sol_ids) and True or False}, context=context)
2949
if sol_ids or wiz_obj.read(cr, uid, wiz_id, ['last_line'], context=context)['last_line']:
2950
return {'type': 'ir.actions.act_window',
2951
'res_model': 'purchase.order.line.unlink.wizard',
2952
'view_type': 'form',
2953
'view_mode': 'form',
2954
'view_id': [view_id],
2959
# In case of a PO line is created to source a FO/IR but the corresponding
2960
# FO/IR line will be created when the PO will be confirmed
2961
if not line.procurement_id and line.origin:
2962
wiz_id = wiz_obj.create(cr, uid, {'line_id': line.id}, context=context)
2963
return wiz_obj.just_cancel(cr, uid, [wiz_id], context=context)
2965
# In case of a PO line is not created to source a FO/IR but from a
2966
# replenishment rule, cancel the stock move and the procurement order
2967
if line.move_dest_id:
2968
self.pool.get('stock.move').action_cancel(cr, uid, [line.move_dest_id.id], context=context)
2969
proc_ids = proc_obj.search(cr, uid, [('move_id', '=', line.move_dest_id.id)], context=context)
2971
# Delete link between proc. order and min/max rule lines
2972
opl_ids = opl_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
2973
opl_obj.write(cr, uid, opl_ids, {'procurement_id': False}, context=context)
2974
wf_service = netsvc.LocalService("workflow")
2975
for proc_id in proc_ids:
2976
wf_service.trg_delete(uid, 'procurement.order', proc_id, cr)
2977
wkf_id = data_obj.get_object_reference(cr, uid, 'procurement', 'act_cancel')[1]
2978
activity = wkf_act_obj.browse(cr, uid, wkf_id, context=context)
2979
_eval_expr(cr, [uid, 'procurement.order', proc_id], False, activity.action)
2981
context['from_del_wizard'] = False
2982
return self.unlink(cr, uid, ids, context=context)
2984
def cancel_sol(self, cr, uid, ids, context=None):
2986
Re-source the FO line
2988
context = context or {}
2989
sol_obj = self.pool.get('sale.order.line')
2990
exp_sol_obj = self.pool.get('expected.sale.order.line')
2991
so_obj = self.pool.get('sale.order')
2992
uom_obj = self.pool.get('product.uom')
2994
if isinstance(ids, (int, long)):
2998
sol_not_to_delete_ids = []
2999
ir_to_potentialy_cancel_ids = []
3000
sol_of_po_line_resourced_ids = []
3002
so_to_cancel_ids = []
3003
for line in self.browse(cr, uid, ids, context=context):
3004
sol_ids = self.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
3006
if not sol_ids and line.origin:
3007
origin_ids = so_obj.search(cr, uid, [('name', '=', line.origin)], context=context)
3008
for origin in so_obj.read(cr, uid, origin_ids, ['order_line'], context=context):
3009
exp_sol_ids = exp_sol_obj.search(cr, uid, [('order_id', '=', origin['id']), ('po_line_id', '!=', line.id)], context=context)
3010
if not origin['order_line'] and not exp_sol_ids:
3011
so_to_cancel_ids.extend(origin_ids)
3013
line_qty = line.product_qty
3014
if 'pol_qty' in context and line.id in context['pol_qty']:
3015
line_qty = context['pol_qty'].get(line.id, 0.00)
3017
for sol in sol_obj.browse(cr, uid, sol_ids, context=context):
3018
diff_qty = uom_obj._compute_qty(cr, uid, line.product_uom.id, line_qty, sol.product_uom.id)
3019
sol_to_update.setdefault(sol.id, 0.00)
3020
sol_to_update[sol.id] += diff_qty
3021
if line.has_to_be_resourced:
3022
sol_obj.add_resource_line(cr, uid, sol, False, diff_qty, context=context)
3023
sol_of_po_line_resourced_ids.append(sol.id)
3024
if sol.order_id.procurement_request:
3025
# UFTP-82: do not delete IR line, cancel it
3026
sol_not_to_delete_ids.append(sol.id)
3027
if sol.order_id.id not in ir_to_potentialy_cancel_ids:
3028
ir_to_potentialy_cancel_ids.append(sol.order_id.id)
3030
context['pol_ids'] = ids
3031
# In case of cancelation and resourcing from IN cancelation
3032
for sol in sol_to_update:
3033
context['update_or_cancel_line_not_delete'] = sol in sol_not_to_delete_ids
3034
if context.get('update_or_cancel_line_not_delete', False) or not context.get('from_in_cancel', False):
3035
so_to_cancel_id = sol_obj.update_or_cancel_line(cr, uid, sol, sol_to_update[sol], context=context)
3037
so_to_cancel_ids.append(so_to_cancel_id)
3039
del context['pol_ids']
3041
if context.get('update_or_cancel_line_not_delete', False):
3042
del context['update_or_cancel_line_not_delete']
3044
# UFTP-82: IR and its PO is cancelled
3045
# IR cancel all lines that have to be cancelled
3046
# and cancel IR if all its lines cancelled
3047
if ir_to_potentialy_cancel_ids:
3048
for ir in self.pool.get('sale.order').browse(cr, uid, ir_to_potentialy_cancel_ids, context=context):
3050
# we change his state to 'cancel' if at least one line cancelled
3051
# we change his state to 'done' if all lines cancelled and resourced
3053
ir_new_state = 'cancel'
3054
lines_to_cancel_ids = []
3055
all_lines_resourced = True
3056
one_line_not_cancelled = False
3058
# check if at least one line is cancelled
3059
# or all lines cancel and resourced
3060
for irl in ir.order_line:
3061
line_cancelled = False
3062
if ir.is_ir_from_po_cancel and \
3063
(irl.state == 'cancel' or irl.state == 'exception'):
3064
# note PO sourced from IR, IR cancelled line can be in 'exception' as a 'cancelled' one
3065
line_cancelled = True
3066
if irl.id not in sol_of_po_line_resourced_ids:
3067
all_lines_resourced = False # one cancelled line not resourced
3068
if irl.state == 'exception':
3069
lines_to_cancel_ids.append(irl.id) # to be set to cancel
3070
if not line_cancelled:
3071
ir_new_state = False # no cancelled line left, then no change
3072
if ir_new_state and all_lines_resourced:
3073
# 'state change' flaged and all line resourced, state to done
3074
ir_new_state = 'done'
3076
if lines_to_cancel_ids:
3077
self.pool.get('sale.order.line').write(cr, uid, lines_to_cancel_ids,
3078
{'state': ir_new_state if ir_new_state else 'cancel'},
3081
self.pool.get('sale.order').write(cr, uid, ir.id,
3082
{'state': ir_new_state}, context=context)
3084
return so_to_cancel_ids
3086
def fake_unlink(self, cr, uid, ids, context=None):
3088
Add an entry to cancel (and resource if needed) the line when the
3089
PO will be confirmed
3091
proc_obj = self.pool.get('procurement.order')
3096
if isinstance(ids, (int, long)):
3105
for line in self.browse(cr, uid, ids, context=context):
3106
# Set the procurement orders to delete
3107
# Set the list of linked purchase orders
3108
if line.procurement_id:
3109
proc_ids.append(line.procurement_id.id)
3110
if line.order_id.id not in purchase_ids:
3111
purchase_ids.append(line.order_id.id)
3113
if not self.pool.get('sale.order.line.cancel').search(cr, uid, [
3114
('sync_order_line_db_id', '=', line.sync_order_line_db_id),
3115
], context=context):
3116
so_to_cancel = self.cancel_sol(cr, uid, [line.id], context=context)
3118
# we want to skip resequencing because unlink is performed on merged purchase order lines
3119
tmp_Resequencing = context.get('skipResequencing', False)
3120
context['skipResequencing'] = True
3121
self._update_merged_line(cr, uid, line.id, False, context=context)
3122
context['skipResequencing'] = tmp_Resequencing
3124
line_to_cancel.append(line.id)
3126
# Cancel the listed procurement orders
3127
for proc_id in proc_ids:
3128
if not self.search(cr, uid, [
3129
('order_id.state', '!=', 'split'),
3130
('id', 'not in', ids),
3131
('procurement_id', '=', proc_id)], context=context):
3132
proc_obj.action_cancel(cr, uid, [proc_id])
3134
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
3135
self.unlink(cr, uid, line_to_cancel, context=context)
832
for line in self.browse(cr, uid, ids, context=context):
833
if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok:
834
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
836
if vals.get('price_unit', line.price_unit) == 0.00 and not line.order_id.rfq_ok:
837
raise osv.except_osv(_('Error'), _('You cannot save a line with no unit price !'))
839
if not context.get('update_merge'):
841
vals = self._update_merged_line(cr, uid, line, vals, context=context)
843
return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
3139
845
def unlink(self, cr, uid, ids, context=None):
3141
847
Update the merged line
3143
po_obj = self.pool.get('purchase.order')
3144
wf_service = netsvc.LocalService("workflow")
3149
852
if isinstance(ids, (int, long)):
3153
for line in self.read(cr, uid, ids, ['id', 'order_id'], context=context):
3154
# we want to skip resequencing because unlink is performed on merged purchase order lines
3155
tmp_skip_resourcing = context.get('skipResourcing', False)
3156
context['skipResourcing'] = True
3157
self._update_merged_line(cr, uid, line['id'], False, context=context)
3158
context['skipResourcing'] = tmp_skip_resourcing
3159
if line['order_id'][0] not in order_ids:
3160
order_ids.append(line['order_id'][0])
3162
if context.get('from_del_wizard'):
3163
return self.ask_unlink(cr, uid, ids, context=context)
3165
res = super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
3167
po_obj.wkf_confirm_trigger(cr, uid, order_ids, context=context)
856
self._update_merged_line(cr, uid, line_id, False, context=context)
858
return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
3171
860
def _get_fake_state(self, cr, uid, ids, field_name, args, context=None):
3172
861
if isinstance(ids, (int, long)):
3370
931
res = super(purchase_order_line, self).product_uom_change(cr, uid, ids, pricelist, product, qty, uom,
3371
932
partner_id, date_order, fiscal_position, date_planned,
3372
933
name, price_unit, notes)
3375
934
res['value'].update({'product_qty': 0.00})
3376
935
res.update({'warning': {}})
3380
939
def product_id_on_change(self, cr, uid, ids, pricelist, product, qty, uom,
3381
940
partner_id, date_order=False, fiscal_position=False, date_planned=False,
3382
name=False, price_unit=False, notes=False, state=False, old_price_unit=False,
3383
nomen_manda_0=False, comment=False, context=None):
3385
partner_price = self.pool.get('pricelist.partnerinfo')
3386
product_obj = self.pool.get('product.product')
3391
# If the user modify a line, remove the old quantity for the total quantity
3393
for line_id in self.browse(cr, uid, ids, context=context):
3394
all_qty -= line_id.product_qty
3396
if product and not uom:
3397
uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
3399
if context and context.get('purchase_id') and state == 'draft' and product:
3400
domain = [('product_id', '=', product),
3401
('product_uom', '=', uom),
3402
('order_id', '=', context.get('purchase_id'))]
3403
other_lines = self.search(cr, uid, domain)
3404
for l in self.browse(cr, uid, other_lines):
3405
all_qty += l.product_qty
3407
res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, all_qty, uom,
3408
partner_id, date_order, fiscal_position,
941
name=False, price_unit=False, notes=False, state=False, old_price_unit=False):
942
res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, qty, uom,
943
partner_id, date_order, fiscal_position,
3409
944
date_planned, name, price_unit, notes)
946
# Remove the warning message if the product has no staged pricelist
947
# if res.get('warning'):
948
# supplier_info = self.pool.get('product.supplierinfo').search(cr, uid, [('product_id', '=', product)])
949
# product_pricelist = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('suppinfo_id', 'in', supplier_info)])
950
# if not product_pricelist:
951
# res['warning'] = {}
3411
952
if res.get('warning', {}).get('title', '') == 'No valid pricelist line found !' or qty == 0.00:
3412
953
res.update({'warning': {}})
3414
func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
3416
currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
3418
currency_id = func_curr_id
3420
if product and partner_id:
3421
# Test the compatibility of the product with a the partner of the order
3422
res, test = product_obj._on_change_restriction_error(cr, uid, product, field_name='product_id', values=res, vals={'partner_id': partner_id}, context=context)
3426
# Update the old price value
955
# Update the old price value
3427
956
res['value'].update({'product_qty': qty})
3428
if product and not res.get('value', {}).get('price_unit', False) and all_qty != 0.00 and qty != 0.00:
957
if not res.get('value', {}).get('price_unit', False) and qty != 0.00:
3429
958
# Display a warning message if the quantity is under the minimal qty of the supplier
3430
currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
3431
tmpl_id = self.pool.get('product.product').read(cr, uid, product, ['product_tmpl_id'])['product_tmpl_id'][0]
3433
domain = [('uom_id', '=', uom),
3434
('partner_id', '=', partner_id),
3435
('product_id', '=', tmpl_id),
3436
'|', ('valid_from', '<=', date_order),
3437
('valid_from', '=', False),
3438
'|', ('valid_till', '>=', date_order),
3439
('valid_till', '=', False)]
3441
domain_cur = [('currency_id', '=', currency_id)]
3442
domain_cur.extend(domain)
3444
info_prices = partner_price.search(cr, uid, domain_cur, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
3446
info_prices = partner_price.search(cr, uid, domain, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
3449
info_price = partner_price.browse(cr, uid, info_prices[0], context=context)
3450
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)
3451
res['value'].update({'old_price_unit': info_u_price, 'price_unit': info_u_price})
3452
res.update({'warning': {'title': _('Warning'), 'message': _('The product unit price has been set ' \
3453
'for a minimal quantity of %s (the min quantity of the price list), '\
3454
'it might change at the supplier confirmation.') % info_price.min_quantity}})
3455
if info_price.rounding and all_qty%info_price.rounding != 0:
3456
message = _('A rounding value of %s UoM has been set for ' \
3457
'this product, you should than modify ' \
3458
'the quantity ordered to match the supplier criteria.') % info_price.rounding
3459
message = '%s \n %s' % (res.get('warning', {}).get('message', ''), message)
3460
res['warning'].update({'message': message})
959
suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', partner_id),
960
('product_id', '=', product)])
962
currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
963
pricelist_ids = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('currency_id', '=', currency_id),
964
('suppinfo_id', 'in', suppinfo_ids),
965
('uom_id', '=', uom),
966
'|', ('valid_till', '=', False),
967
('valid_till', '>=', date_order)], order='min_quantity')
969
pricelist = self.pool.get('pricelist.partnerinfo').browse(cr, uid, pricelist_ids[0])
970
res['value'].update({'old_price_unit': pricelist.price, 'price_unit': pricelist.price})
971
res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier has a minimal ' \
972
'quantity set to %s, you cannot purchase less.') % pricelist.min_quantity}})
974
res['value'].update({'old_price_unit': res['value']['price_unit']})
3462
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'], round=False, context=context)
3463
res['value'].update({'old_price_unit': old_price})
976
res['value'].update({'old_price_unit': res['value']['price_unit']})
3465
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)
3466
res['value'].update({'old_price_unit': old_price})
978
res['value'].update({'old_price_unit': res.get('value').get('price_unit')})
3468
980
# Set the unit price with cost price if the product has no staged pricelist
3469
if product and qty != 0.00:
3470
res['value'].update({'comment': False, 'nomen_manda_0': False, 'nomen_manda_1': False,
3471
'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False,
3472
'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False,
3473
'nomen_sub_4': False, 'nomen_sub_5': False})
3474
st_uom = self.pool.get('product.product').browse(cr, uid, product).uom_id.id
981
if product and qty != 0.00:
3475
982
st_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
3476
st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price, round=False, context=context)
3477
st_price = self.pool.get('product.uom')._compute_price(cr, uid, st_uom, st_price, uom)
3479
984
if res.get('value', {}).get('price_unit', False) == False and (state and state == 'draft') or not state :
3480
985
res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
3481
986
elif state and state != 'draft' and old_price_unit:
3482
987
res['value'].update({'price_unit': old_price_unit, 'old_price_unit': old_price_unit})
3484
if res['value']['price_unit'] == 0.00:
3485
res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
3487
988
elif qty == 0.00:
3488
989
res['value'].update({'price_unit': 0.00, 'old_price_unit': 0.00})
3489
elif not product and not comment and not nomen_manda_0:
3490
res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
3493
if context and context.get('categ') and product:
3494
# Check consistency of product
3495
consistency_message = self.pool.get('product.product').check_consistency(cr, uid, product, context.get('categ'), context=context)
3496
if consistency_message:
3497
res.setdefault('warning', {})
3498
res['warning'].setdefault('title', 'Warning')
3499
res['warning'].setdefault('message', '')
3501
res['warning']['message'] = '%s \n %s' % (res.get('warning', {}).get('message', ''), consistency_message)
991
res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
3505
def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id,
3506
product_uom, product_qty, pricelist, partner_id, date_order,
3507
change_price_ok, state, old_price_unit,
3508
nomen_manda_0=False, comment=False, context=None):
995
def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id, product_uom, product_qty, pricelist, partner_id, date_order, change_price_ok, state, old_price_unit, context=None):
3510
997
Display a warning message on change price unit if there are other lines with the same product and the same uom
3615
1047
purchase_order_line()
3617
class purchase_order_group(osv.osv_memory):
3618
_name = "purchase.order.group"
3619
_inherit = "purchase.order.group"
3620
_description = "Purchase Order Merge"
3623
'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'),
3624
'unmatched_categ': fields.boolean(string='Unmatched categories'),
3627
def default_get(self, cr, uid, fields, context=None):
3628
res = super(purchase_order_group, self).default_get(cr, uid, fields, context=context)
3629
if context.get('active_model','') == 'purchase.order' and len(context['active_ids']) < 2:
3630
raise osv.except_osv(_('Warning'),
3631
_('Please select multiple order to merge in the list view.'))
3633
res['po_value_id'] = context['active_ids'][-1]
3636
for po in self.pool.get('purchase.order').read(cr, uid, context['active_ids'], ['categ'], context=context):
3637
categories.add(po['categ'])
3639
if len(categories) > 1:
3640
res['unmatched_categ'] = True
3644
def merge_orders(self, cr, uid, ids, context=None):
3645
res = super(purchase_order_group, self).merge_orders(cr, uid, ids, context=context)
3646
res.update({'context': {'search_default_draft': 1, 'search_default_approved': 0,'search_default_create_uid':uid, 'purchase_order': True}})
3648
if 'domain' in res and eval(res['domain'])[0][2]:
3651
raise osv.except_osv(_('Error'), _('No PO merged !'))
3652
return {'type': 'ir.actions.act_window_close'}
3654
purchase_order_group()
3656
class product_product(osv.osv):
3657
_name = 'product.product'
3658
_inherit = 'product.product'
3660
def _product_price(self, cr, uid, ids, field_name, args, context=None):
3661
res = super(product_product, self)._product_price(cr, uid, ids, field_name, args, context=context)
3664
if res[product] == 0.00:
3666
res[product] = self.pool.get('product.product').read(cr, uid, [product], ['standard_price'], context=context)[0]['standard_price']
3672
def _get_purchase_type(self, cr, uid, ids, field_name, args, context=None):
3679
def _src_purchase_type(self, cr, uid, obj, name, args, context=None):
3681
Returns a domain according to the PO type
3685
if arg[0] == 'purchase_type':
3687
raise osv.except_osv(_('Error'), _('Only the \'=\' operator is allowed.'))
3688
# Returns all service products
3689
if arg[2] == 'service':
3690
res.append(('type', '=', 'service_recep'))
3691
elif arg[2] == 'transport':
3692
res.append(('transport_ok', '=', True))
3698
'purchase_type': fields.function(_get_purchase_type, fnct_search=_src_purchase_type, type='boolean', string='Purchase type', method=True, store=False),
3699
'price': fields.function(_product_price, method=True, type='float', string='Pricelist', digits_compute=dp.get_precision('Sale Price')),
3702
def check_consistency(self, cr, uid, product_id, category, context=None):
3704
Check the consistency of product according to category
3706
context = context is None and {} or context
3707
display_message = False
3709
# No check for Other
3710
if category == 'other':
3713
product = self.read(cr, uid, product_id, ['nomen_manda_0', 'type', 'transport_ok'], context=context)
3714
transport_product = product['transport_ok']
3715
product_type = product['type']
3716
main_type = product['nomen_manda_0'][0]
3718
if category == 'medical':
3720
med_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'MED')], context=context)[0]
3722
raise osv.except_osv(_('Error'), _('MED nomenclature Main Type not found'))
3724
if main_type != med_nomen:
3725
display_message = True
3727
if category == 'log':
3729
log_nomen = self.pool.get('product.nomenclature').search(cr, uid, [('level', '=', 0), ('name', '=', 'LOG')], context=context)[0]
3731
raise osv.except_osv(_('Error'), _('LOG nomenclature Main Type not found'))
3733
if main_type != log_nomen:
3734
display_message = True
3736
if category == 'service' and product_type != 'service_recep':
3737
display_message = True
3739
if category == 'transport' and (product_type != 'service_recep' or not transport_product):
3740
display_message = True
3743
return 'Warning you are about to add a product which does not conform to this PO’s order category, do you wish to proceed ?'
3750
class purchase_order_line_unlink_wizard(osv.osv_memory):
3751
_name = 'purchase.order.line.unlink.wizard'
3753
def _get_last_line(self, cr, uid, ids, field_name, args, context=None):
3755
Return True if the line is the last line to confirm before confirm
3758
po_obj = self.pool.get('purchase.order')
3759
pol_obj = self.pool.get('purchase.order.line')
3760
sol_obj = self.pool.get('sale.order.line')
3761
exp_sol_obj = self.pool.get('expected.sale.order.line')
3762
so_obj = self.pool.get('sale.order')
3767
if isinstance(ids, (int, long)):
3771
for wiz in self.browse(cr, uid, ids, context=context):
3774
# Add a check to avoid error in server log
3775
if not pol_obj.search(cr, uid, [('id', '=', wiz.line_id.id)], context=context):
3778
exp_sol_ids = pol_obj.get_exp_sol_ids_from_pol_ids(cr, uid, [wiz.line_id.id], context=context, po_line=wiz.line_id.id)
3780
if wiz.line_id.procurement_id:
3781
order_id = wiz.line_id.order_id.id
3782
po_so_ids, po_ids, so_ids, sol_nc_ids = po_obj.sourcing_document_state(cr, uid, [order_id], context=context)
3783
sol_ids = sol_obj.search(cr, uid, [('procurement_id', '=', wiz.line_id.procurement_id.id)], context=context)
3785
if order_id in po_ids:
3786
po_ids.remove(order_id)
3787
for sol_id in sol_ids:
3788
if sol_id in sol_nc_ids:
3789
sol_nc_ids.remove(sol_id)
3791
if po_ids and not sol_nc_ids and not exp_sol_ids:
3793
elif wiz.line_id.origin and not exp_sol_ids:
3799
'line_id': fields.many2one('purchase.order.line', 'Line to delete'),
3800
'last_line': fields.function(
3804
string='Last line to confirm',
3808
'only_exp': fields.boolean(
3809
string='Remains only expected FO/IR lines',
3814
def just_cancel(self, cr, uid, ids, context=None):
3819
line_obj = self.pool.get('purchase.order.line')
3820
order_wiz_obj = self.pool.get('purchase.order.cancel.wizard')
3821
data_obj = self.pool.get('ir.model.data')
3822
po_obj = self.pool.get('purchase.order')
3823
so_obj = self.pool.get('sale.order')
3829
if isinstance(ids, (int, long)):
3834
for wiz in self.browse(cr, uid, ids, context=context):
3835
po_ids.add(wiz.line_id.order_id.id)
3836
line_ids.append(wiz.line_id.id)
3838
if context.get('has_to_be_resourced'):
3839
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3841
so_to_cancel_ids = line_obj.fake_unlink(cr, uid, line_ids, context=context)
3843
if not so_to_cancel_ids:
3844
return po_obj.check_empty_po(cr, uid, list(po_ids), context=context)
3848
'po_ids': list(po_ids),
3850
return so_obj.open_cancel_wizard(cr, uid, so_to_cancel_ids, context=context)
3852
return {'type': 'ir.actions.act_window_close'}
3855
def cancel_and_resource(self, cr, uid, ids, context=None):
3857
Flag the line to be re-sourced and run cancel method
3863
context['has_to_be_resourced'] = True
3865
return self.just_cancel(cr, uid, ids, context=context)
3867
purchase_order_line_unlink_wizard()
3870
class purchase_order_cancel_wizard(osv.osv_memory):
3871
_name = 'purchase.order.cancel.wizard'
3874
'order_id': fields.many2one(
3876
string='Order to delete',
3878
'unlink_po': fields.boolean(
3881
'last_lines': fields.boolean(
3882
string='Remove last lines of the FO',
3886
def _get_last_lines(self, cr, uid, order_id, context=None):
3888
Returns True if the deletion of the PO will delete the last lines
3891
exp_sol_obj = self.pool.get('expected.sale.order.line')
3892
po_obj = self.pool.get('purchase.order')
3894
po_so_ids, po_ids, so_ids, sol_nc_ids = po_obj.sourcing_document_state(cr, uid, [order_id], context=context)
3895
if order_id in po_ids:
3896
po_ids.remove(order_id)
3898
exp_sol_ids = exp_sol_obj.search(cr, uid, [('order_id', 'in', po_so_ids), ('po_id', '!=', order_id)], context=context)
3900
if not exp_sol_ids and not po_ids:
3905
def fields_view_get(self, cr, uid, view_id=False, view_type='form', context=None, toolbar=False, submenu=False):
3906
return super(purchase_order_cancel_wizard, self).fields_view_get(cr, uid, view_id, view_type, context, toolbar, submenu)
3908
def ask_unlink(self, cr, uid, order_id, context=None):
3912
data_obj = self.pool.get('ir.model.data')
3917
if self._name == 'rfq.cancel.wizard':
3918
view_id = data_obj.get_object_reference(cr, uid, 'tender_flow', 'ask_rfq_cancel_wizard_form_view')[1]
3920
view_id = data_obj.get_object_reference(cr, uid, 'purchase_override', 'ask_po_cancel_wizard_form_view')[1]
3921
wiz_id = self.create(cr, uid, {'order_id': order_id}, context=context)
3923
return {'type': 'ir.actions.act_window',
3924
'res_model': 'purchase.order.cancel.wizard',
3926
'view_id': [view_id],
3927
'view_type': 'form',
3928
'view_mode': 'form',
3932
def close_window(self, cr, uid, ids, context=None):
3934
Close the pop-up and reload the PO
3936
return {'type': 'ir.actions.act_window_close'}
3938
def cancel_po(self, cr, uid, ids, context=None):
3940
Cancel the PO and display his form
3942
po_obj = self.pool.get('purchase.order')
3943
so_obj = self.pool.get('sale.order')
3944
line_obj = self.pool.get('purchase.order.line')
3945
wf_service = netsvc.LocalService("workflow")
3951
if isinstance(ids, (int, long)):
3957
for wiz in self.browse(cr, uid, ids, context=context):
3958
order_ids.append(wiz.order_id.id)
3959
if wiz.last_lines and wiz.order_id.id not in order_to_check:
3960
order_to_check.append(wiz.order_id.id)
3961
if context.get('has_to_be_resourced'):
3962
line_ids.extend([l.id for l in wiz.order_id.order_line])
3964
po_so_ids, po_ids, so_ids, sol_nc_ids = po_obj.sourcing_document_state(cr, uid, order_to_check, context=context)
3966
# Mark lines as 'To be resourced'
3967
line_obj.write(cr, uid, line_ids, {'has_to_be_resourced': True}, context=context)
3969
po_obj.write(cr, uid, order_ids, {'canceled_end': True}, context=context)
3970
for order_id in order_ids:
3971
wf_service.trg_validate(uid, 'purchase.order', order_id, 'purchase_cancel', cr)
3974
order_to_cancel = []
3975
for so_id in po_so_ids:
3976
if so_obj._get_ready_to_cancel(cr, uid, so_id, context=context)[so_id]:
3977
order_to_cancel.append(so_id)
3982
'po_ids': list(order_to_check),
3984
return so_obj.open_cancel_wizard(cr, uid, po_so_ids, context=context)
3986
return {'type': 'ir.actions.act_window_close'}
3988
def cancel_and_resource(self, cr, uid, ids, context=None):
3992
context['has_to_be_resourced'] = True
3994
return self.cancel_po(cr, uid, ids, context=context)
3996
purchase_order_cancel_wizard()
3999
class res_partner(osv.osv):
4000
_inherit = 'res.partner'
4002
def address_multiple_get(self, cr, uid, ids, adr_pref=['default']):
4003
address_obj = self.pool.get('res.partner.address')
4004
address_ids = address_obj.search(cr, uid, [('partner_id', '=', ids)])
4005
address_rec = address_obj.read(cr, uid, address_ids, ['type'])
4007
for addr in address_rec:
4008
res.setdefault(addr['type'], [])
4009
res[addr['type']].append(addr['id'])
4011
default_address = res.get('default', False)
4013
default_address = False
4016
result[a] = res.get(a, default_address)
4023
class res_partner_address(osv.osv):
4024
_inherit = 'res.partner.address'
4026
def _get_dummy(self, cr, uid, ids, field_name, args, context=None):
4033
def _src_address(self, cr, uid, obj, name, args, context=None):
4035
Returns all the destination addresses of a partner or all default
4036
addresses if he hasn't destination addresses
4038
partner_obj = self.pool.get('res.partner')
4039
user_obj = self.pool.get('res.users')
4043
if arg[0] == 'dest_address':
4044
addr_type = 'delivery'
4045
elif arg[0] == 'inv_address':
4046
addr_type = 'invoice'
4051
partner_id = user_obj.browse(cr, uid, uid, context=context).company_id.partner_id.id
4053
partner_id = [partner_id]
4056
if isinstance(partner_id, list):
4057
for partner in partner_id:
4060
addr_ids.extend(partner_obj.address_multiple_get(cr, uid, partner, [addr_type])[addr_type])
4063
addr_ids = partner_obj.address_multiple_get(cr, uid, partner_id, [addr_type])[addr_type]
4065
res.append(('id', 'in', list(i for i in addr_ids if i)))
4070
'dest_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
4071
type='boolean', string='Dest. Address', store=False),
4072
'inv_address': fields.function(_get_dummy, fnct_search=_src_address, method=True,
4073
type='boolean', string='Invoice Address', store=False),
4077
res_partner_address()
1049
class account_invoice(osv.osv):
1050
_name = 'account.invoice'
1051
_inherit = 'account.invoice'
1054
'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)]}),
4079
1059
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: