1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2011 TeMPO Consulting, MSF
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Affero General Public License as
9
# published by the Free Software Foundation, either version 3 of the
10
# License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Affero General Public License for more details.
17
# You should have received a copy of the GNU Affero General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
20
##############################################################################
22
from osv import osv, fields
23
from order_types import ORDER_PRIORITY, ORDER_CATEGORY
24
from tools.translate import _
26
from mx.DateTime import *
29
from workflow.wkf_expr import _eval_expr
32
from dateutil.relativedelta import relativedelta
33
from datetime import datetime
35
from purchase_override import PURCHASE_ORDER_STATE_SELECTION
37
class purchase_order_confirm_wizard(osv.osv):
38
_name = 'purchase.order.confirm.wizard'
41
'order_id': fields.many2one('purchase.order', string='Purchase Order', readonly=True),
42
'errors': fields.text(string='Error message', readonly=True),
45
def validate_order(self, cr, uid, ids, context=None):
46
wf_service = netsvc.LocalService("workflow")
47
for wiz in self.browse(cr, uid, ids, context=context):
48
wf_service.trg_validate(uid, 'purchase.order', wiz.order_id.id, 'purchase_confirmed_wait', cr)
49
return {'type': 'ir.actions.act_window_close'}
51
purchase_order_confirm_wizard()
53
class purchase_order(osv.osv):
54
_name = 'purchase.order'
55
_inherit = 'purchase.order'
57
def copy(self, cr, uid, id, default=None, context=None):
59
Remove loan_id field on new purchase.order
63
default.update({'loan_id': False, 'merged_line_ids': False, 'origin': False})
64
return super(purchase_order, self).copy(cr, uid, id, default, context=context)
66
# @@@purchase.purchase_order._invoiced
67
def _invoiced(self, cursor, user, ids, name, arg, context=None):
69
for purchase in self.browse(cursor, user, ids, context=context):
71
if purchase.invoiced_rate == 100.00:
73
res[purchase.id] = invoiced
77
# @@@purchase.purchase_order._shipped_rate
78
def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
80
for purchase in self.browse(cursor, user, ids, context=context):
81
if ((purchase.order_type == 'regular' and purchase.partner_id.partner_type == 'internal') or \
82
purchase.order_type in ['donation_exp', 'donation_st', 'loan', 'in_kind']):
83
res[purchase.id] = purchase.shipped_rate
86
for invoice in purchase.invoice_ids:
87
if invoice.state not in ('draft','cancel'):
88
tot += invoice.amount_untaxed
89
if purchase.amount_untaxed:
90
res[purchase.id] = min(100.0, tot * 100.0 / (purchase.amount_untaxed))
92
res[purchase.id] = 0.0
96
def _get_allocation_setup(self, cr, uid, ids, field_name, args, context=None):
98
Returns the Unifield configuration value
101
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
104
res[order] = setup.allocation_setup
108
def _get_no_line(self, cr, uid, ids, field_name, args, context=None):
111
for order in self.browse(cr, uid, ids, context=context):
113
for line in order.order_line:
114
res[order.id] = False
116
# better: if order.order_line: res[order.id] = False
121
'order_type': fields.selection([('regular', 'Regular'), ('donation_exp', 'Donation before expiry'),
122
('donation_st', 'Standard donation'), ('loan', 'Loan'),
123
('in_kind', 'In Kind Donation'), ('purchase_list', 'Purchase List'),
124
('direct', 'Direct Purchase Order')], string='Order Type', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
125
'loan_id': fields.many2one('sale.order', string='Linked loan', readonly=True),
126
'priority': fields.selection(ORDER_PRIORITY, string='Priority', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
127
'categ': fields.selection(ORDER_CATEGORY, string='Order category', required=True, states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
128
'details': fields.char(size=30, string='Details', states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}),
129
'invoiced': fields.function(_invoiced, method=True, string='Invoiced', type='boolean', help="It indicates that an invoice has been generated"),
130
'invoiced_rate': fields.function(_invoiced_rate, method=True, string='Invoiced', type='float'),
131
'loan_duration': fields.integer(string='Loan duration', help='Loan duration in months', states={'confirmed':[('readonly',True)],'approved':[('readonly',True)],'done':[('readonly',True)]}),
132
'from_yml_test': fields.boolean('Only used to pass addons unit test', readonly=True, help='Never set this field to true !'),
133
'date_order':fields.date(string='Creation Date', readonly=True, required=True,
134
states={'draft':[('readonly',False)],}, select=True, help="Date on which this document has been created."),
135
'name': fields.char('Order Reference', size=64, required=True, select=True, states={'cancel':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'confirmed':[('readonly',True)], 'approved':[('readonly',True)], 'done':[('readonly',True)]},
136
help="unique number of the purchase order,computed automatically when the purchase order is created"),
137
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order", readonly=True),
138
'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', readonly=True, states={'draft':[('readonly',False)], 'rfq_sent':[('readonly',False)], 'confirmed': [('readonly',False)]}),
139
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'rfq_sent':[('readonly',True)], 'rfq_done':[('readonly',True)], 'rfq_updated':[('readonly',True)], 'confirmed':[('readonly',True)], 'confirmed_wait':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)],'cancel':[('readonly',True)]}, change_default=True, domain="[('id', '!=', company_id)]"),
140
'partner_address_id':fields.many2one('res.partner.address', 'Address', required=True,
141
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)]"),
142
'dest_partner_id': fields.many2one('res.partner', string='Destination partner', domain=[('partner_type', '=', 'internal')]),
143
'invoice_address_id': fields.many2one('res.partner.address', string='Invoicing address', required=True,
144
help="The address where the invoice will be sent."),
145
'invoice_method': fields.selection([('manual','Manual'),('order','From Order'),('picking','From Picking')], 'Invoicing Control', required=True, readonly=True,
146
help="From Order: a draft invoice will be pre-generated based on the purchase order. The accountant " \
147
"will just have to validate this invoice for control.\n" \
148
"From Picking: a draft invoice will be pre-generated based on validated receptions.\n" \
149
"Manual: allows you to generate suppliers invoices by chosing in the uninvoiced lines of all manual purchase orders."
151
'merged_line_ids': fields.one2many('purchase.order.merged.line', 'order_id', string='Merged line'),
152
'date_confirm': fields.date(string='Confirmation date'),
153
'allocation_setup': fields.function(_get_allocation_setup, type='selection',
154
selection=[('allocated', 'Allocated'),
155
('unallocated', 'Unallocated'),
156
('mixed', 'Mixed')], string='Allocated setup', method=True, store=False),
157
'unallocation_ok': fields.boolean(string='Unallocated PO'),
158
'partner_ref': fields.char('Supplier Reference', size=64),
159
'product_id': fields.related('order_line', 'product_id', type='many2one', relation='product.product', string='Product'),
160
'no_line': fields.function(_get_no_line, method=True, type='boolean', string='No line'),
164
'order_type': lambda *a: 'regular',
165
'priority': lambda *a: 'normal',
166
'categ': lambda *a: 'other',
168
'from_yml_test': lambda *a: False,
169
'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'],
170
'invoice_method': lambda *a: 'picking',
171
'dest_address_id': lambda obj, cr, uid, ctx: obj.pool.get('res.partner').address_get(cr, uid, obj.pool.get('res.users').browse(cr, uid, uid, ctx).company_id.id, ['delivery'])['delivery'],
172
'no_line': lambda *a: True,
175
def default_get(self, cr, uid, fields, context=None):
177
Fill the unallocated_ok field according to Unifield setup
179
res = super(purchase_order, self).default_get(cr, uid, fields, context=context)
180
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
182
res.update({'unallocation_ok': False, 'allocation_setup': setup.allocation_setup})
183
if setup.allocation_setup == 'unallocated':
184
res.update({'unallocation_ok': True})
188
def _check_user_company(self, cr, uid, company_id, context=None):
190
Remove the possibility to make a PO to user's company
192
user_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
193
if company_id == user_company_id:
194
raise osv.except_osv(_('Error'), _('You cannot made a purchase order to your own company !'))
198
def write(self, cr, uid, ids, vals, context=None):
200
Check if the partner is correct
202
if 'partner_id' in vals:
203
self._check_user_company(cr, uid, vals['partner_id'], context=context)
205
if vals.get('order_type'):
206
if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
207
vals.update({'invoice_method': 'manual'})
208
elif vals.get('order_type') in ['direct', 'purchase_list']:
209
vals.update({'invoice_method': 'order'})
211
vals.update({'invoice_method': 'picking'})
213
return super(purchase_order, self).write(cr, uid, ids, vals, context=context)
215
def onchange_internal_type(self, cr, uid, ids, order_type, partner_id, dest_partner_id=False, warehouse_id=False):
217
Changes the invoice method of the purchase order according to
218
the choosen order type
219
Changes the partner to local market if the type is Purchase List
221
partner_obj = self.pool.get('res.partner')
223
d = {'partner_id': []}
227
# Search the local market partner id
228
data_obj = self.pool.get('ir.model.data')
229
data_id = data_obj.search(cr, uid, [('module', '=', 'order_types'), ('model', '=', 'res.partner'), ('name', '=', 'res_partner_local_market')] )
231
local_market = data_obj.read(cr, uid, data_id, ['res_id'])[0]['res_id']
233
if order_type == 'loan':
234
setup = self.pool.get('unifield.setup.configuration').get_config(cr, uid)
236
if not setup.field_orders_ok:
237
return {'value': {'order_type': 'regular'},
238
'warning': {'title': 'Error',
239
'message': 'The Field orders feature is not activated on your system, so, you cannot create a Loan Purchase Order !'}}
241
if order_type in ['donation_exp', 'donation_st', 'loan']:
242
v['invoice_method'] = 'manual'
243
elif order_type in ['direct', 'purchase_list']:
244
v['invoice_method'] = 'order'
245
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
246
elif order_type in ['in_kind']:
247
v['invoice_method'] = 'picking'
248
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
250
v['invoice_method'] = 'picking'
252
if order_type == 'direct' and dest_partner_id:
253
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])['delivery']
254
v.update({'dest_address_id': cp_address_id})
255
d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
256
elif order_type == 'direct':
257
v.update({'dest_address_id': False})
258
d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
260
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.id, ['delivery'])['delivery']
261
v.update({'dest_address_id': cp_address_id})
262
d.update({'dest_address_id': [('partner_id', '=', self.pool.get('res.users').browse(cr, uid, uid).company_id.id)]})
264
if partner_id and partner_id != local_market:
265
partner = partner_obj.browse(cr, uid, partner_id)
266
if partner.partner_type == 'internal' and order_type == 'regular':
267
v['invoice_method'] = 'manual'
268
elif partner.partner_type not in ('external', 'esc') and order_type == 'direct':
269
v.update({'partner_address_id': False, 'partner_id': False, 'pricelist_id': False,})
270
d['partner_id'] = [('partner_type', 'in', ['esc', 'external'])]
271
w.update({'message': 'You cannot have a Direct Purchase Order with a partner which is not external or an ESC',
272
'title': 'An error has occured !'})
273
elif partner_id and partner_id == local_market and order_type != 'purchase_list':
274
v['partner_id'] = None
275
v['partner_address_id'] = None
276
v['pricelist_id'] = None
278
if order_type == 'purchase_list':
280
partner = self.pool.get('res.partner').browse(cr, uid, local_market)
281
v['partner_id'] = partner.id
283
v['partner_address_id'] = partner.address[0].id
284
if partner.property_product_pricelist_purchase:
285
v['pricelist_id'] = partner.property_product_pricelist_purchase.id
286
elif order_type == 'direct':
287
v['cross_docking_ok'] = False
289
return {'value': v, 'domain': d, 'warning': w}
291
def onchange_partner_id(self, cr, uid, ids, part, *a, **b):
293
Fills the Requested and Confirmed delivery dates
295
if isinstance(ids, (int, long)):
298
res = super(purchase_order, self).onchange_partner_id(cr, uid, ids, part, *a, **b)
301
partner_obj = self.pool.get('res.partner')
302
partner = partner_obj.browse(cr, uid, part)
303
if partner.partner_type == 'internal':
304
res['value']['invoice_method'] = 'manual'
308
# Be careful during integration, the onchange_warehouse_id method is also defined on UF-965
309
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, order_type, dest_address_id):
311
Change the destination address to the destination address of the company if False
313
res = super(purchase_order, self).onchange_warehouse_id(cr, uid, ids, warehouse_id)
315
if not res.get('value', {}).get('dest_address_id') and order_type!='direct':
316
cp_address_id = self.pool.get('res.partner').address_get(cr, uid, self.pool.get('res.users').browse(cr, uid, uid).company_id.id, ['delivery'])['delivery']
318
res['value'].update({'dest_address_id': cp_address_id})
320
res.update({'value': {'dest_address_id': cp_address_id}})
321
if order_type == 'direct' or dest_address_id:
322
if 'dest_address_id' in res.get('value', {}):
323
res['value'].pop('dest_address_id')
327
def on_change_dest_partner_id(self, cr, uid, ids, dest_partner_id, context=None):
329
Fill automatically the destination address according to the destination partner
337
company_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.id
339
if not dest_partner_id:
340
v.update({'dest_address_id': False})
341
d.update({'dest_address_id': [('partner_id', '=', company_id)]})
343
d.update({'dest_address_id': [('partner_id', '=', dest_partner_id)]})
345
delivery_addr = self.pool.get('res.partner').address_get(cr, uid, dest_partner_id, ['delivery'])
346
v.update({'dest_address_id': delivery_addr['delivery']})
348
return {'value': v, 'domain': d}
350
def change_currency(self, cr, uid, ids, context=None):
352
Launches the wizard to change the currency and update lines
357
if isinstance(ids, (int, long)):
360
for order in self.browse(cr, uid, ids, context=context):
361
data = {'order_id': order.id,
362
'partner_id': order.partner_id.id,
363
'partner_type': order.partner_id.partner_type,
364
'new_pricelist_id': order.pricelist_id.id,
365
'currency_rate': 1.00,
366
'old_pricelist_id': order.pricelist_id.id}
367
wiz = self.pool.get('purchase.order.change.currency').create(cr, uid, data, context=context)
368
return {'type': 'ir.actions.act_window',
369
'res_model': 'purchase.order.change.currency',
377
def order_line_change(self, cr, uid, ids, order_line):
378
res = {'no_line': True}
381
res = {'no_line': False}
383
return {'value': res}
385
def _hook_confirm_order_message(self, cr, uid, context=None, *args, **kwargs):
387
Change the logged message
393
return _("Purchase order '%s' is validated.") % (po.name,)
395
return super(purchase_order, self)._hook_confirm_order_message(cr, uid, context, args, kwargs)
397
def check_analytic_distribution(self, cr, uid, ids, context=None):
399
Check analytic distribution validity for given PO.
400
Also check that partner have a donation account (is PO is in_kind)
402
if isinstance(ids, (int, long)):
404
# Analytic distribution verification
405
for po in self.browse(cr, uid, ids, context=context):
406
if po.order_type and po.order_type == 'in_kind':
407
if not po.partner_id.donation_payable_account:
408
raise osv.except_osv(_('Error'), _('No donation account on this partner: %s') % (po.partner_id.name or '',))
409
for pol in po.order_line:
410
# Forget check if we come from YAML tests
413
distrib_id = (pol.analytic_distribution_id and pol.analytic_distribution_id.id) or (po.analytic_distribution_id and po.analytic_distribution_id.id) or False
414
# Raise an error if no analytic distribution found
416
raise osv.except_osv(_('Warning'), _('Analytic allocation is mandatory for this line: %s!') % (pol.name or '',))
417
if pol.analytic_distribution_state != 'valid':
418
raise osv.except_osv(_('Warning'), _("Analytic distribution is not valid for '%s'!") % (pol.name or '',))
421
def wkf_confirm_order(self, cr, uid, ids, context=None):
423
Update the confirmation date of the PO at confirmation.
424
Check analytic distribution.
426
for order in self.browse(cr, uid, ids, context=context):
427
pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [('in_search', '=', order.partner_id.partner_type)], context=context)
428
if order.pricelist_id.id not in pricelist_ids:
429
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.'))
430
res = super(purchase_order, self).wkf_confirm_order(cr, uid, ids, context=context)
431
self.write(cr, uid, ids, {'date_confirm': time.strftime('%Y-%m-%d')}, context=context)
432
# CODE MOVED TO self.check_analytic_distribution()
433
self.check_analytic_distribution(cr, uid, ids, context=context)
436
def wkf_picking_done(self, cr, uid, ids, context=None):
438
Change the shipped boolean and the state of the PO
440
for order in self.browse(cr, uid, ids, context=context):
441
if order.order_type == 'direct':
442
self.write(cr, uid, order.id, {'state': 'approved'}, context=context)
444
self.write(cr, uid, order.id, {'shipped':1,'state':'approved'}, context=context)
448
def purchase_approve(self, cr, uid, ids, context=None):
450
If the PO is a DPO, check the state of the stock moves
452
if isinstance(ids, (int, long)):
455
wf_service = netsvc.LocalService("workflow")
456
move_obj = self.pool.get('stock.move')
458
for order in self.browse(cr, uid, ids, context=context):
459
if not order.delivery_confirmed_date:
460
raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
465
if order.order_type == 'direct':
466
for line in order.order_line:
467
if line.procurement_id: todo.append(line.procurement_id.id)
470
todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
473
sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
475
for move in move_obj.browse(cr, uid, sm_ids, context=context):
476
backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
477
if move.state == 'done':
478
error_moves.append(move)
480
for bmove in move_obj.browse(cr, uid, backmove_ids):
481
error_moves.append(bmove)
484
errors = '''You are trying to confirm a Direct Purchase Order.
485
At Direct Purchase Order confirmation, the system tries to change the state of concerning OUT moves but for this DPO, the system has detected
486
stock moves which are already processed : '''
487
for m in error_moves:
488
errors = '%s \n %s' % (errors, '''
489
* 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))
491
errors = '%s \n %s' % (errors, 'This warning is only for informational purpose. The stock moves already processed will not be modified by this confirmation.')
493
wiz_id = self.pool.get('purchase.order.confirm.wizard').create(cr, uid, {'order_id': order.id,
495
return {'type': 'ir.actions.act_window',
496
'res_model': 'purchase.order.confirm.wizard',
502
# If no errors, validate the DPO
503
wf_service.trg_validate(uid, 'purchase.order', order.id, 'purchase_confirmed_wait', cr)
507
def get_so_ids_from_po_ids(self, cr, uid, ids, context=None):
509
receive the list of purchase order ids
511
return the list of sale order ids corresponding (through procurement process)
516
if isinstance(ids, (int, long)):
520
sol_obj = self.pool.get('sale.order.line')
524
# get the sale order lines
525
sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
527
# list of dictionaries for each sale order line
528
datas = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
529
# we retrieve the list of sale order ids
531
if data['order_id'] and data['order_id'][0] not in so_ids:
532
so_ids.append(data['order_id'][0])
535
def get_sol_ids_from_po_ids(self, cr, uid, ids, context=None):
537
receive the list of purchase order ids
539
return the list of sale order line ids corresponding (through procurement process)
544
if isinstance(ids, (int, long)):
548
sol_obj = self.pool.get('sale.order.line')
549
# procurement ids list
551
# sale order lines list
554
for po in self.browse(cr, uid, ids, context=context):
555
for line in po.order_line:
556
if line.procurement_id:
557
proc_ids.append(line.procurement_id.id)
558
# get the corresponding sale order line list
560
sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
563
def common_code_from_wkf_approve_order(self, cr, uid, ids, context=None):
565
delivery confirmed date at po level is mandatory
566
update corresponding date at line level if needed.
567
Check analytic distribution
570
ana_obj = self.pool.get('analytic.distribution')
572
# Check analytic distribution
573
self.check_analytic_distribution(cr, uid, ids, context=context)
574
for po in self.browse(cr, uid, ids, context=context):
575
# CODE MOVED TO self.check_analytic_distribution()
576
# msf_order_date checks
577
if not po.delivery_confirmed_date:
578
raise osv.except_osv(_('Error'), _('Delivery Confirmed Date is a mandatory field.'))
579
# for all lines, if the confirmed date is not filled, we copy the header value
580
for line in po.order_line:
581
if not line.confirmed_delivery_date:
582
line.write({'confirmed_delivery_date': po.delivery_confirmed_date,}, context=context)
583
# MOVE code for COMMITMENT into wkf_approve_order
586
def wkf_confirm_wait_order(self, cr, uid, ids, context=None):
589
1/ if all purchase line could take an analytic distribution
590
2/ if a commitment voucher should be created after PO approbation
592
_> originally in purchase.py from analytic_distribution_supply
594
Checks if the Delivery Confirmed Date has been filled
596
_> originally in order_dates.py from msf_order_date
601
if isinstance(ids, (int, long)):
605
sol_obj = self.pool.get('sale.order.line')
607
# code from wkf_approve_order
608
self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
609
# set the state of purchase order to confirmed_wait
610
self.write(cr, uid, ids, {'state': 'confirmed_wait'}, context=context)
611
# sale order lines with modified state
612
sol_ids = self.get_sol_ids_from_po_ids(cr, uid, ids, context=context)
614
sol_obj.write(cr, uid, sol_ids, {'state': 'confirmed'}, context=context)
616
# !!BEWARE!! we must update the So lines before any writing to So objects
617
for po in self.browse(cr, uid, ids, context=context):
618
# hook for corresponding Fo update
619
self._hook_confirm_order_update_corresponding_so(cr, uid, ids, context=context, po=po)
623
def compute_confirmed_delivery_date(self, cr, uid, ids, confirmed, prep_lt, ship_lt, est_transport_lead_time, db_date_format, context=None):
625
compute the confirmed date
627
confirmed must be string
628
return string corresponding to database format
630
assert type(confirmed) == str
631
confirmed = datetime.strptime(confirmed, db_date_format)
632
confirmed = confirmed + relativedelta(days=prep_lt or 0)
633
confirmed = confirmed + relativedelta(days=ship_lt or 0)
634
confirmed = confirmed + relativedelta(days=est_transport_lead_time or 0)
635
confirmed = confirmed.strftime(db_date_format)
639
def _hook_confirm_order_update_corresponding_so(self, cr, uid, ids, context=None, *args, **kwargs):
641
Add a hook to update correspondingn so
646
if isinstance(ids, (int, long)):
651
pol_obj = self.pool.get('purchase.order.line')
652
so_obj = self.pool.get('sale.order')
653
sol_obj = self.pool.get('sale.order.line')
654
date_tools = self.pool.get('date.tools')
655
fields_tools = self.pool.get('fields.tools')
656
db_date_format = date_tools.get_db_date_format(cr, uid, context=context)
658
# update corresponding fo if exist
659
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
662
ship_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='shipment_lead_time', context=context)
663
prep_lt = fields_tools.get_field_from_company(cr, uid, object=self._name, field='preparation_lead_time', context=context)
665
for line in po.order_line:
666
# get the corresponding so line
667
sol_ids = pol_obj.get_sol_ids_from_pol_ids(cr, uid, [line.id], context=context)
670
data = sol_obj.read(cr, uid, sol_ids, ['order_id'], context=context)
671
order_id = data[0]['order_id'][0]
672
# get est_transport_lead_time of corresponding so
673
data = so_obj.read(cr, uid, order_id, ['est_transport_lead_time'], context=context)
674
est_transport_lead_time = data['est_transport_lead_time']
676
line_confirmed = False
677
# compute confirmed date for line
678
if line.confirmed_delivery_date:
679
line_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, line.confirmed_delivery_date,
680
prep_lt, ship_lt, est_transport_lead_time,
681
db_date_format, context=context)
682
# we update the corresponding sale order line
683
sol = sol_obj.browse(cr, uid, sol_ids[0], context=context)
684
# do not update Internal Requests
685
if sol.order_id.procurement_request:
688
# compute the price_unit value - we need to specify the date
689
date_context = {'date': po.date_order}
690
# convert from currency of pol to currency of sol
691
price_unit_converted = self.pool.get('res.currency').compute(cr, uid, line.currency_id.id,
692
sol.currency_id.id, line.price_unit or 0.0,
693
round=True, context=date_context)
694
fields_dic = {'product_id': line.product_id and line.product_id.id or False,
696
'default_name': line.default_name,
697
'default_code': line.default_code,
698
'product_uom_qty': line.product_qty,
699
'product_uom': line.product_uom and line.product_uom.id or False,
700
'product_uos_qty': line.product_qty,
701
'product_uos': line.product_uom and line.product_uom.id or False,
702
'price_unit': price_unit_converted,
703
'nomenclature_description': line.nomenclature_description,
704
'nomenclature_code': line.nomenclature_code,
705
'comment': line.comment,
706
'nomen_manda_0': line.nomen_manda_0 and line.nomen_manda_0.id or False,
707
'nomen_manda_1': line.nomen_manda_1 and line.nomen_manda_1.id or False,
708
'nomen_manda_2': line.nomen_manda_2 and line.nomen_manda_2.id or False,
709
'nomen_manda_3': line.nomen_manda_3 and line.nomen_manda_3.id or False,
710
'nomen_sub_0': line.nomen_sub_0 and line.nomen_sub_0.id or False,
711
'nomen_sub_1': line.nomen_sub_1 and line.nomen_sub_1.id or False,
712
'nomen_sub_2': line.nomen_sub_2 and line.nomen_sub_2.id or False,
713
'nomen_sub_3': line.nomen_sub_3 and line.nomen_sub_3.id or False,
714
'nomen_sub_4': line.nomen_sub_4 and line.nomen_sub_4.id or False,
715
'nomen_sub_5': line.nomen_sub_5 and line.nomen_sub_5.id or False,
716
'confirmed_delivery_date': line_confirmed,
719
sol_obj.write(cr, uid, sol_ids, fields_dic, context=context)
721
# compute so dates -- only if we get a confirmed value, because rts is mandatory on So side
722
# update after lines update, as so write triggers So workflow, and we dont want the Out document
723
# to be created with old So datas
724
if po.delivery_confirmed_date:
725
for so in so_obj.browse(cr, uid, so_ids, context=context):
726
# Fo rts = Po confirmed date + prep_lt
727
delivery_confirmed_date = datetime.strptime(po.delivery_confirmed_date, db_date_format)
728
so_rts = delivery_confirmed_date + relativedelta(days=prep_lt or 0)
729
so_rts = so_rts.strftime(db_date_format)
731
# Fo confirmed date = confirmed date + prep_lt + ship_lt + transport_lt
732
so_confirmed = self.compute_confirmed_delivery_date(cr, uid, ids, po.delivery_confirmed_date,
733
prep_lt, ship_lt, so.est_transport_lead_time,
734
db_date_format, context=context)
736
so_obj.write(cr, uid, [so.id], {'delivery_confirmed_date': so_confirmed,
737
'ready_to_ship_date': so_rts}, context=context)
741
def all_po_confirmed(self, cr, uid, ids, context=None):
743
condition for the po to leave the act_confirmed_wait state
745
if the po is from scratch (no procurement), or from replenishment mechanism (procurement but no sale order line)
746
the method will return True and therefore the po workflow is not blocked
748
only 'make_to_order' sale order lines are checked, we dont care on state of 'make_to_stock' sale order line
749
_> anyway, thanks to Fo split, make_to_stock and make_to_order so lines are separated in different sale orders
754
if isinstance(ids, (int, long)):
758
sol_obj = self.pool.get('sale.order.line')
759
so_obj = self.pool.get('sale.order')
761
# corresponding sale order
762
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
763
# from so, list corresponding po
764
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
765
# from listed po, list corresponding so
766
all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
767
# if we have sol_ids, we are treating a po which is make_to_order from sale order
769
# we retrieve the list of ids of all sale order line if type 'make_to_order' with state != 'confirmed'
770
# with product_id (if no product id, no procurement, no po, so should not be taken into account)
771
# in case of grouped po, multiple Fo depend on this po, all Po of these Fo need to be completed
772
# and all Fo will be confirmed together. Because IN of grouped Po need corresponding OUT document of all Fo
773
# internal request are automatically 'confirmed'
774
# not take done into account, because IR could be done as they are confirmed before the Po are all done
775
# see video in uf-1050 for detail
776
all_sol_not_confirmed_ids = sol_obj.search(cr, uid, [('order_id', 'in', all_so_ids),
777
('type', '=', 'make_to_order'),
778
('product_id', '!=', False),
779
('state', 'not in', ['confirmed', 'done'])], context=context)
780
# if any lines exist, we return False
781
if all_sol_not_confirmed_ids:
786
def wkf_confirm_trigger(self, cr, uid, ids, context=None):
788
trigger corresponding so then po
793
if isinstance(ids, (int, long)):
797
sol_obj = self.pool.get('sale.order.line')
798
so_obj = self.pool.get('sale.order')
799
proc_obj = self.pool.get('procurement.order')
800
wf_service = netsvc.LocalService("workflow")
802
# corresponding sale order
803
so_ids = self.get_so_ids_from_po_ids(cr, uid, ids, context=context)
804
# from so, list corresponding po first level
805
all_po_ids = so_obj.get_po_ids_from_so_ids(cr, uid, so_ids, context=context)
806
# from listed po, list corresponding so
807
all_so_ids = self.get_so_ids_from_po_ids(cr, uid, all_po_ids, context=context)
808
# from all so, list all corresponding po second level
809
all_po_for_all_so_ids = so_obj.get_po_ids_from_so_ids(cr, uid, all_so_ids, context=context)
811
# we trigger all the corresponding sale order -> test_lines is called on these so
812
for so_id in all_so_ids:
813
wf_service.trg_write(uid, 'sale.order', so_id, cr)
815
# we trigger pos of all sale orders -> all_po_confirm is called on these po
816
for po_id in all_po_for_all_so_ids:
817
wf_service.trg_write(uid, 'purchase.order', po_id, cr)
821
def wkf_approve_order(self, cr, uid, ids, context=None):
823
Checks if the invoice should be create from the purchase order
825
If the PO is a DPO, set all related OUT stock move to 'done' state
827
line_obj = self.pool.get('purchase.order.line')
828
move_obj = self.pool.get('stock.move')
829
wf_service = netsvc.LocalService("workflow")
831
if isinstance(ids, (int, long)):
834
# duplicated code with wkf_confirm_wait_order because of backward compatibility issue with yml tests for dates,
835
# which doesnt execute wkf_confirm_wait_order (null value in column "date_expected" violates not-null constraint for stock.move otherwise)
836
# msf_order_date checks
837
self.common_code_from_wkf_approve_order(cr, uid, ids, context=context)
839
for order in self.browse(cr, uid, ids):
840
# Create commitments for each PO only if po is "from picking"
841
if order.invoice_method in ['picking', 'order'] and not order.from_yml_test and order.order_type != 'in_kind' and order.partner_id.partner_type != 'intermission':
842
self.action_create_commitment(cr, uid, [order.id], order.partner_id and order.partner_id.partner_type, context=context)
843
# Don't accept the confirmation of regular PO with 0.00 unit price lines
844
if order.order_type == 'regular':
846
for line in order.order_line:
847
if line.price_unit == 0.00:
848
line_error.append(line.line_number)
850
if len(line_error) > 0:
851
errors = ' / '.join(str(x) for x in line_error)
852
raise osv.except_osv(_('Error !'), _('You cannot have a purchase order line with a 0.00 Unit Price. Lines in exception : %s') % errors)
857
if order.partner_id.partner_type == 'internal' and order.order_type == 'regular' or \
858
order.order_type in ['donation_exp', 'donation_st', 'loan']:
859
self.write(cr, uid, [order.id], {'invoice_method': 'manual'})
860
line_obj.write(cr, uid, [x.id for x in order.order_line], {'invoiced': 1})
862
message = _("Purchase order '%s' is confirmed.") % (order.name,)
863
self.log(cr, uid, order.id, message)
865
if order.order_type == 'direct':
866
self.write(cr, uid, [order.id], {'invoice_method': 'order'}, context=context)
867
for line in order.order_line:
868
if line.procurement_id: todo.append(line.procurement_id.id)
871
todo2 = self.pool.get('sale.order.line').search(cr, uid, [('procurement_id', 'in', todo)], context=context)
874
sm_ids = move_obj.search(cr, uid, [('sale_line_id', 'in', todo2)], context=context)
875
self.pool.get('stock.move').action_confirm(cr, uid, sm_ids, context=context)
876
stock_location_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'stock_location_stock')[1]
877
for move in move_obj.browse(cr, uid, sm_ids, context=context):
878
# Search if this move has been processed
879
backmove_ids = self.pool.get('stock.move').search(cr, uid, [('backmove_id', '=', move.id)])
880
if move.state != 'done' and not backmove_ids and not move.backmove_id:
881
move_obj.write(cr, uid, sm_ids, {'dpo_id': order.id, 'state': 'done',
882
'location_id': stock_location_id,
883
'location_dest_id': stock_location_id,
884
'date': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
885
wf_service.trg_trigger(uid, 'stock.move', move.id, cr)
887
all_move_closed = True
888
# Check if the picking should be updated
889
if move.picking_id.subtype == 'picking':
890
for m in move.picking_id.move_lines:
891
if m.id not in sm_ids and m.state != 'done':
892
all_move_closed = False
893
# If all stock moves of the picking is done, trigger the workflow
895
todo3.append(move.picking_id.id)
898
for pick_id in todo3:
899
wf_service.trg_validate(uid, 'stock.picking', pick_id, 'button_confirm', cr)
900
wf_service.trg_write(uid, 'stock.picking', pick_id, cr)
902
return super(purchase_order, self).wkf_approve_order(cr, uid, ids, context=context)
904
def action_sale_order_create(self, cr, uid, ids, context=None):
906
Create a sale order as counterpart for the loan.
908
if isinstance(ids, (int, long)):
913
sale_obj = self.pool.get('sale.order')
914
sale_line_obj = self.pool.get('sale.order.line')
915
sale_shop = self.pool.get('sale.shop')
916
partner_obj = self.pool.get('res.partner')
918
for order in self.browse(cr, uid, ids):
919
loan_duration = Parser.DateFromString(order.minimum_planned_date) + RelativeDateTime(months=+order.loan_duration)
920
# from yml test is updated according to order value
921
values = {'shop_id': sale_shop.search(cr, uid, [])[0],
922
'partner_id': order.partner_id.id,
923
'partner_order_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['contact'])['contact'],
924
'partner_invoice_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['invoice'])['invoice'],
925
'partner_shipping_id': partner_obj.address_get(cr, uid, [order.partner_id.id], ['delivery'])['delivery'],
926
'pricelist_id': order.partner_id.property_product_pricelist.id,
928
'loan_duration': order.loan_duration,
929
'origin': order.name,
930
'order_type': 'loan',
931
'delivery_requested_date': loan_duration.strftime('%Y-%m-%d'),
932
'categ': order.categ,
933
'priority': order.priority,
934
'from_yml_test': order.from_yml_test,
936
order_id = sale_obj.create(cr, uid, values, context=context)
937
for line in order.order_line:
938
sale_line_obj.create(cr, uid, {'product_id': line.product_id and line.product_id.id or False,
939
'product_uom': line.product_uom.id,
940
'order_id': order_id,
941
'price_unit': line.price_unit,
942
'product_uom_qty': line.product_qty,
943
'date_planned': loan_duration.strftime('%Y-%m-%d'),
946
'type': line.product_id.procure_method})
947
self.write(cr, uid, [order.id], {'loan_id': order_id})
949
sale = sale_obj.browse(cr, uid, order_id)
951
message = _("Loan counterpart '%s' has been created.") % (sale.name,)
953
sale_obj.log(cr, uid, order_id, message)
957
def has_stockable_product(self,cr, uid, ids, *args):
959
Override the has_stockable_product to return False
960
when the order_type of the order is 'direct'
962
# TODO: See with Synchro team which object the system will should create
963
# to have an Incoming Movement in the destination instance
964
for order in self.browse(cr, uid, ids):
965
if order.order_type != 'direct':
966
return super(purchase_order, self).has_stockable_product(cr, uid, ids, args)
970
def action_invoice_create(self, cr, uid, ids, *args):
972
Override this method to check the purchase_list box on invoice
973
when the invoice comes from a purchase list.
974
Change journal to an inkind journal if we comes from a In-kind Donation PO
976
invoice_id = super(purchase_order, self).action_invoice_create(cr, uid, ids, args)
977
invoice_obj = self.pool.get('account.invoice')
978
inkind_journal_ids = self.pool.get('account.journal').search(cr, uid, [("type", "=", "inkind")])
980
for order in self.browse(cr, uid, ids):
981
if order.order_type == 'purchase_list':
982
invoice_obj.write(cr, uid, [invoice_id], {'purchase_list': 1})
983
elif order.order_type == 'in_kind':
984
if not inkind_journal_ids:
985
raise osv.except_osv(_('Error'), _('No In-kind Donation journal found!'))
986
invoice_obj.write(cr, uid, [invoice_id], {'journal_id': inkind_journal_ids[0], 'is_inkind_donation': True})
990
def _hook_action_picking_create_modify_out_source_loc_check(self, cr, uid, ids, context=None, *args, **kwargs):
992
Please copy this to your module's method also.
993
This hook belongs to the action_picking_create method from purchase>purchase.py>purchase_order class
995
- allow to choose whether or not the source location of the corresponding outgoing stock move should
996
match the destination location of incoming stock move
998
order_line = kwargs['order_line']
999
# by default, we change the destination stock move if the destination stock move exists
1000
return order_line.move_dest_id
1002
def _hook_action_picking_create_stock_picking(self, cr, uid, ids, context=None, *args, **kwargs):
1004
modify data for stock move creation
1006
move_values = kwargs['move_values']
1009
# @@@override@purchase.purchase.order.action_picking_create
1010
def action_picking_create(self,cr, uid, ids, context=None, *args):
1012
for order in self.browse(cr, uid, ids):
1013
loc_id = order.partner_id.property_stock_supplier.id
1015
reason_type_id = False
1016
if order.invoice_method=='picking':
1017
istate = '2binvoiced'
1019
pick_name = self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in')
1022
'origin': order.name+((order.origin and (':'+order.origin)) or ''),
1024
'partner_id2': order.partner_id.id,
1025
'address_id': order.partner_address_id.id or False,
1026
'invoice_state': istate,
1027
'purchase_id': order.id,
1028
'company_id': order.company_id.id,
1032
if order.order_type in ('regular', 'purchase_list', 'direct'):
1033
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_external_supply')[1]
1034
if order.order_type == 'loan':
1035
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_loan')[1]
1036
if order.order_type == 'donation_st':
1037
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation')[1]
1038
if order.order_type == 'donation_exp':
1039
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_donation_expiry')[1]
1040
if order.order_type == 'in_kind':
1041
reason_type_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'reason_types_moves', 'reason_type_in_kind_donation')[1]
1044
picking_values.update({'reason_type_id': reason_type_id})
1046
picking_id = self.pool.get('stock.picking').create(cr, uid, picking_values, context=context)
1048
for order_line in order.order_line:
1049
if not order_line.product_id:
1051
if order_line.product_id.product_tmpl_id.type in ('product', 'consu', 'service_recep',):
1052
dest = order.location_id.id
1053
# service with reception are directed to Service Location
1054
if order_line.product_id.product_tmpl_id.type == 'service_recep':
1055
service_loc = self.pool.get('stock.location').search(cr, uid, [('service_location', '=', True)], context=context)
1057
dest = service_loc[0]
1060
'name': order.name + ': ' +(order_line.name or ''),
1061
'product_id': order_line.product_id.id,
1062
'product_qty': order_line.product_qty,
1063
'product_uos_qty': order_line.product_qty,
1064
'product_uom': order_line.product_uom.id,
1065
'product_uos': order_line.product_uom.id,
1066
'date': order_line.date_planned,
1067
'date_expected': order_line.date_planned,
1068
'location_id': loc_id,
1069
'location_dest_id': dest,
1070
'picking_id': picking_id,
1071
'move_dest_id': order_line.move_dest_id.id,
1073
'purchase_line_id': order_line.id,
1074
'company_id': order.company_id.id,
1075
'price_currency_id': order.pricelist_id.currency_id.id,
1076
'price_unit': order_line.price_unit
1078
# hook for stock move values modification
1079
move_values = self._hook_action_picking_create_stock_picking(cr, uid, ids, context=context, move_values=move_values, order_line=order_line,)
1082
move_values.update({'reason_type_id': reason_type_id})
1084
move = self.pool.get('stock.move').create(cr, uid, move_values, context=context)
1085
if self._hook_action_picking_create_modify_out_source_loc_check(cr, uid, ids, context=context, order_line=order_line, move_id=move):
1086
self.pool.get('stock.move').write(cr, uid, [order_line.move_dest_id.id], {'location_id':order.location_id.id})
1087
todo_moves.append(move)
1088
self.pool.get('stock.move').action_confirm(cr, uid, todo_moves)
1089
self.pool.get('stock.move').force_assign(cr, uid, todo_moves)
1090
wf_service = netsvc.LocalService("workflow")
1091
wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
1095
def create(self, cr, uid, vals, context=None):
1097
Filled in 'from_yml_test' to True if we come from tests
1101
if context.get('update_mode') in ['init', 'update'] and 'from_yml_test' not in vals:
1102
logging.getLogger('init').info('PO: set from yml test to True')
1103
vals['from_yml_test'] = True
1105
if vals.get('order_type'):
1106
if vals.get('order_type') in ['donation_exp', 'donation_st', 'loan']:
1107
vals.update({'invoice_method': 'manual'})
1108
elif vals.get('order_type') in ['direct', 'purchase_list']:
1109
vals.update({'invoice_method': 'order'})
1111
vals.update({'invoice_method': 'picking'})
1113
if 'partner_id' in vals:
1114
self._check_user_company(cr, uid, vals['partner_id'], context=context)
1116
return super(purchase_order, self).create(cr, uid, vals, context=context)
1118
def wkf_action_cancel_po(self, cr, uid, ids, context=None):
1120
Cancel activity in workflow.
1122
# Some verifications
1125
if isinstance(ids, (int, long)):
1127
return self.write(cr, uid, ids, {'state':'cancel'}, context=context)
1129
def action_done(self, cr, uid, ids, context=None):
1131
Done activity in workflow.
1133
# Some verifications
1136
if isinstance(ids, (int, long)):
1138
for order in self.browse(cr, uid, ids, context=context):
1139
vals = {'state': 'done'}
1140
if order.order_type == 'direct':
1141
vals.update({'shipped': 1})
1142
self.write(cr, uid, order.id, vals, context=context)
1145
def set_manually_done(self, cr, uid, ids, all_doc=True, context=None):
1147
Set the PO to done state
1149
wf_service = netsvc.LocalService("workflow")
1153
if isinstance(ids, (int, long)):
1157
for order in self.browse(cr, uid, ids, context=context):
1158
for line in order.order_line:
1159
order_lines.append(line.id)
1162
for pick in order.picking_ids:
1163
if pick.state not in ('cancel', 'done'):
1164
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'manually_done', cr)
1166
# Done loan counterpart
1167
if order.loan_id and order.loan_id.state not in ('cancel', 'done') and not context.get('loan_id', False) == order.id:
1168
loan_context = context.copy()
1169
loan_context.update({'loan_id': order.id})
1170
self.pool.get('sale.order').set_manually_done(cr, uid, order.loan_id.id, all_doc=all_doc, context=loan_context)
1173
invoice_error_ids = []
1174
for invoice in order.invoice_ids:
1175
if invoice.state == 'draft':
1176
wf_service.trg_validate(uid, 'account.invoice', invoice.id, 'invoice_cancel', cr)
1177
elif invoice.state not in ('cancel', 'done'):
1178
invoice_error_ids.append(invoice.id)
1180
if invoice_error_ids:
1181
invoices_ref = ' / '.join(x.number for x in self.pool.get('account.invoice').browse(cr, uid, invoice_error_ids, context=context))
1182
raise osv.except_osv(_('Error'), _('The state of the following invoices cannot be updated automatically. Please cancel them manually or discuss with the accounting team to solve the problem.' \
1183
'Invoices references : %s') % invoices_ref)
1186
move_ids = self.pool.get('stock.move').search(cr, uid, [('purchase_line_id', 'in', order_lines), ('state', 'not in', ('cancel', 'done'))], context=context)
1187
self.pool.get('stock.move').set_manually_done(cr, uid, move_ids, all_doc=all_doc, context=context)
1189
# Cancel all procurement ordes which have generated one of these PO
1190
proc_ids = self.pool.get('procurement.order').search(cr, uid, [('purchase_id', 'in', ids)], context=context)
1191
for proc in self.pool.get('procurement.order').browse(cr, uid, proc_ids, context=context):
1192
self.pool.get('stock.move').write(cr, uid, [proc.move_id.id], {'state': 'cancel'}, context=context)
1193
wf_service.trg_validate(uid, 'procurement.order', proc.id, 'subflow.cancel', cr)
1196
# Detach the PO from his workflow and set the state to done
1197
for order_id in self.browse(cr, uid, ids, context=context):
1198
if order_id.rfq_ok and order_id.state == 'draft':
1199
wf_service.trg_validate(uid, 'purchase.order', order_id.id, 'purchase_cancel', cr)
1200
elif order_id.tender_id:
1201
raise osv.except_osv(_('Error'), _('You cannot \'Close\' a Request for Quotation attached to a tender. Please make the tender %s to \'Closed\' before !') % order_id.tender_id.name)
1203
wf_service.trg_delete(uid, 'purchase.order', order_id.id, cr)
1204
# Search the method called when the workflow enter in last activity
1205
wkf_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'act_done')[1]
1206
activity = self.pool.get('workflow.activity').browse(cr, uid, wkf_id, context=context)
1207
res = _eval_expr(cr, [uid, 'purchase.order', order_id.id], False, activity.action)
1214
class purchase_order_merged_line(osv.osv):
1216
A purchase order merged line is a special PO line.
1217
These lines give the total quantity of all normal PO lines
1218
which have the same product and the same quantity.
1219
When a new normal PO line is created, the system will check
1220
if this new line can be attached to other PO lines. If yes,
1221
the unit price of all normal PO lines with the same product and
1222
the same UoM will be computed from supplier catalogue and updated on lines.
1224
_name = 'purchase.order.merged.line'
1225
_inherit = 'purchase.order.line'
1226
_description = 'Purchase Order Merged Lines'
1227
_table = 'purchase_order_merged_line'
1229
def _get_name(self, cr, uid, ids, field_name, args, context=None):
1231
for line in self.browse(cr, uid, ids, context=context):
1232
if line.order_line_ids:
1233
res[line.id] = line.product_id and line.product_id.name or line.order_line_ids[0].comment
1237
'order_line_ids': fields.one2many('purchase.order.line', 'merged_id', string='Purchase Lines'),
1238
'date_planned': fields.date(string='Delivery Requested Date', required=False, select=True,
1239
help='Header level dates has to be populated by default with the possibility of manual updates'),
1240
'name': fields.function(_get_name, method=True, type='char', string='Name', store=False),
1243
def create(self, cr, uid, vals, context=None):
1245
Set the line number to 0
1247
if self._name == 'purchase.order.merged.line':
1248
vals.update({'line_number': 0})
1249
return super(purchase_order_merged_line, self).create(cr, uid, vals, context=context)
1251
def write(self, cr, uid, ids, vals, context=None):
1253
Update unit price of PO lines attached to the merged line
1257
new_context = context.copy()
1258
new_context.update({'update_merge': True})
1259
# If the unit price is changing, update the price unit of all normal PO lines
1260
# associated to this merged PO line
1261
if 'price_unit' in vals:
1262
for merged_line in self.browse(cr, uid, ids, context=context):
1263
for po_line in merged_line.order_line_ids:
1264
self.pool.get('purchase.order.line').write(cr, uid, [po_line.id], {'price_unit': vals['price_unit'],
1265
'old_price_unit': vals['price_unit']}, context=new_context)
1267
return super(purchase_order_merged_line, self).write(cr, uid, ids, vals, context=context)
1269
def _update(self, cr, uid, id, po_line_id, product_qty, price=0.00, context=None, no_update=False):
1271
Update the quantity and the unit price according to the new qty
1273
line = self.browse(cr, uid, id, context=context)
1274
change_price_ok = True
1276
change_price_ok = context.get('change_price_ok', True)
1278
po_line = self.pool.get('purchase.order.line').browse(cr, uid, po_line_id, context=context)
1279
change_price_ok = po_line.change_price_ok
1280
if 'change_price_ok' in context:
1281
change_price_ok = context.get('change_price_ok')
1283
# If no PO line attached to this merged line, remove the merged line
1284
if not line.order_line_ids:
1285
self.unlink(cr, uid, [id], context=context)
1289
new_qty = line.product_qty + product_qty
1291
if (po_line_id and not change_price_ok and not po_line.order_id.rfq_ok) or (not po_line_id and not change_price_ok):
1292
# Get the catalogue unit price according to the total qty
1293
new_price = self.pool.get('product.pricelist').price_get(cr, uid,
1294
[line.order_id.pricelist_id.id],
1297
line.order_id.partner_id.id,
1298
{'uom': line.product_uom.id,
1299
'date': line.order_id.date_order})[line.order_id.pricelist_id.id]
1301
# Update the quantity of the merged line
1302
values = {'product_qty': new_qty}
1303
# If a catalogue unit price exist and the unit price is not manually changed
1305
values.update({'price_unit': new_price})
1307
# Keep the unit price given by the user
1308
values.update({'price_unit': price})
1311
# Update the unit price and the quantity of the merged line
1313
self.write(cr, uid, [id], values, context=context)
1315
return id, new_price or False
1318
purchase_order_merged_line()
1321
class purchase_order_line(osv.osv):
1322
_name = 'purchase.order.line'
1323
_inherit = 'purchase.order.line'
1325
def link_merged_line(self, cr, uid, vals, product_id, order_id, product_qty, uom_id, price_unit=0.00, context=None):
1327
Check if a merged line exist. If not, create a new one and attach them to the Po line
1329
line_obj = self.pool.get('purchase.order.merged.line')
1331
domain = [('product_id', '=', product_id), ('order_id', '=', order_id), ('product_uom', '=', uom_id)]
1332
# Search if a merged line already exist for the same product, the same order and the same UoM
1333
merged_ids = line_obj.search(cr, uid, domain, context=context)
1337
new_vals = vals.copy()
1340
new_vals['order_id'] = order_id
1341
if not new_vals.get('price_unit', False):
1342
new_vals['price_unit'] = price_unit
1343
# Create a new merged line which is the same than the normal PO line except for price unit
1344
vals['merged_id'] = line_obj.create(cr, uid, new_vals, context=context)
1347
order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
1348
stages = self._get_stages_price(cr, uid, product_id, uom_id, order, context=context)
1349
if order.state != 'confirmed' and stages and not order.rfq_ok:
1350
c.update({'change_price_ok': False})
1351
# Update the associated merged line
1352
res_merged = line_obj._update(cr, uid, merged_ids[0], False, product_qty, price_unit, context=c, no_update=False)
1353
vals['merged_id'] = res_merged[0]
1355
vals['price_unit'] = res_merged[1]
1359
def _update_merged_line(self, cr, uid, line_id, vals=None, context=None):
1361
Update the merged line
1363
merged_line_obj = self.pool.get('purchase.order.merged.line')
1367
tmp_vals = vals.copy()
1369
# If it's an update of a line
1370
if vals and line_id:
1371
line = self.browse(cr, uid, line_id, context=context)
1373
# Set default values if not pass in values
1374
if not 'product_uom' in vals:
1375
tmp_vals.update({'product_uom': line.product_uom.id})
1376
if not 'product_qty' in vals:
1377
tmp_vals.update({'product_qty': line.product_qty})
1379
# If the user changed the product or the UoM or both on the PO line
1380
if ('product_id' in vals and line.product_id.id != vals['product_id']) or ('product_uom' in vals and line.product_uom.id != vals['product_uom']):
1381
# Need removing the merged_id link before update the merged line because the merged line
1382
# will be removed if it hasn't attached PO line
1383
merged_id = line.merged_id.id
1384
change_price_ok = line.change_price_ok
1386
c.update({'change_price_ok': change_price_ok})
1387
self.write(cr, uid, line_id, {'merged_id': False}, context=context)
1388
res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
1390
# Create or update an existing merged line with the new product
1391
vals = self.link_merged_line(cr, uid, tmp_vals, tmp_vals.get('product_id', line.product_id.id), line.order_id.id, tmp_vals.get('product_qty', line.product_qty), tmp_vals.get('product_uom', line.product_uom.id), tmp_vals.get('price_unit', line.price_unit), context=context)
1393
# If the quantity is changed
1394
elif 'product_qty' in vals and line.product_qty != vals['product_qty']:
1395
res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, vals['product_qty']-line.product_qty, line.price_unit, context=context)
1396
# Update the unit price
1397
if res_merged and res_merged[1]:
1398
vals.update({'price_unit': res_merged[1]})
1400
# If the price unit is changed and the product and the UoM is not modified
1401
if 'price_unit' in tmp_vals and (line.price_unit != tmp_vals['price_unit'] or vals['price_unit'] != tmp_vals['price_unit']) and not (line.product_id.id != vals.get('product_id', False) or line.product_uom.id != vals.get('product_uom', False)):
1402
# Give 0.00 to quantity because the _update should recompute the price unit with the same quantity
1403
res_merged = merged_line_obj._update(cr, uid, line.merged_id.id, line.id, 0.00, tmp_vals['price_unit'], context=context)
1404
# Update the unit price
1405
if res_merged and res_merged[1]:
1406
vals.update({'price_unit': res_merged[1]})
1407
# If it's a new line
1410
vals = self.link_merged_line(cr, uid, vals, vals.get('product_id'), vals['order_id'], vals['product_qty'], vals['product_uom'], vals['price_unit'], context=c)
1411
# If the line is removed
1413
line = self.browse(cr, uid, line_id, context=context)
1414
# Remove the qty from the merged line
1416
merged_id = line.merged_id.id
1417
change_price_ok = line.change_price_ok
1419
c.update({'change_price_ok': change_price_ok})
1420
# Need removing the merged_id link before update the merged line because the merged line
1421
# will be removed if it hasn't attached PO line
1422
self.write(cr, uid, [line.id], {'merged_id': False}, context=context)
1423
res_merged = merged_line_obj._update(cr, uid, merged_id, line.id, -line.product_qty, line.price_unit, context=c)
1427
def create(self, cr, uid, vals, context=None):
1429
Create or update a merged line
1434
order_id = self.pool.get('purchase.order').browse(cr, uid, vals['order_id'], context=context)
1435
if order_id.from_yml_test:
1436
vals.update({'change_price_manually': True})
1437
if not vals.get('product_qty', False):
1438
vals['product_qty'] = 1.00
1440
# If we are on a RfQ, use the last entered unit price and update other lines with this price
1442
vals.update({'change_price_manually': True})
1444
if vals.get('product_qty', 0.00) == 0.00:
1445
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
1447
order_id = vals.get('order_id')
1448
product_id = vals.get('product_id')
1449
product_uom = vals.get('product_uom')
1450
order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
1451
other_lines = self.search(cr, uid, [('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
1452
stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
1454
if (other_lines and stages and order.state != 'confirmed'):
1455
context.update({'change_price_ok': False})
1457
vals = self._update_merged_line(cr, uid, False, vals, context=context)
1459
vals.update({'old_price_unit': vals.get('price_unit', False)})
1461
# add the database Id to the sync_pol_db_id
1462
po_line_id = super(purchase_order_line, self).create(cr, uid, vals, context=context)
1463
if 'sync_pol_db_id' not in vals:
1464
super(purchase_order_line, self).write(cr, uid, po_line_id, {'sync_pol_db_id': po_line_id}, context=context)
1468
def copy(self, cr, uid, line_id, defaults={}, context=None):
1470
Remove link to merged line
1472
defaults.update({'merged_id': False})
1474
return super(purchase_order_line, self).copy(cr, uid, line_id, defaults, context=context)
1476
def write(self, cr, uid, ids, vals, context=None):
1483
if isinstance(ids, (int, long)):
1486
# if ids and not isinstance(ids[0], (int, long)):
1487
# ids = [x.id for x in ids]
1489
for line in self.browse(cr, uid, ids, context=context):
1490
if vals.get('product_qty', line.product_qty) == 0.00 and not line.order_id.rfq_ok:
1491
raise osv.except_osv(_('Error'), _('You cannot save a line with no quantity !'))
1493
if not context.get('update_merge'):
1495
vals = self._update_merged_line(cr, uid, line, vals, context=context)
1497
if 'price_unit' in vals:
1498
vals.update({'old_price_unit': vals.get('price_unit')})
1500
return super(purchase_order_line, self).write(cr, uid, ids, vals, context=context)
1502
def unlink(self, cr, uid, ids, context=None):
1504
Update the merged line
1508
if isinstance(ids, (int, long)):
1511
# if the line is linked to a sale order line through procurement process,
1512
# the deletion is impossible
1513
if self.get_sol_ids_from_pol_ids(cr, uid, ids, context=context):
1514
raise osv.except_osv(_('Error'), _('You cannot delete a line which is linked to a Fo line.'))
1517
self._update_merged_line(cr, uid, line_id, False, context=context)
1519
return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
1521
def _get_fake_state(self, cr, uid, ids, field_name, args, context=None):
1522
if isinstance(ids, (int, long)):
1525
for pol in self.read(cr, uid, ids, ['state']):
1526
ret[pol['id']] = pol['state']
1529
def _get_fake_id(self, cr, uid, ids, field_name, args, context=None):
1530
if isinstance(ids, (int, long)):
1533
for pol in self.read(cr, uid, ids, ['id']):
1534
ret[pol['id']] = pol['id']
1537
def _get_stages_price(self, cr, uid, product_id, uom_id, order, context=None):
1539
Returns True if the product/supplier couple has more than 1 line
1541
suppinfo_ids = self.pool.get('product.supplierinfo').search(cr, uid, [('name', '=', order.partner_id.id),
1542
('product_id', '=', product_id)], context=context)
1544
pricelist_ids = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('currency_id', '=', order.pricelist_id.currency_id.id),
1545
('suppinfo_id', 'in', suppinfo_ids),
1546
('uom_id', '=', uom_id),
1547
'|', ('valid_till', '=', False),
1548
('valid_till', '>=', order.date_order)], context=context)
1549
if len(pricelist_ids) > 1:
1554
def _get_price_change_ok(self, cr, uid, ids, field_name, args, context=None):
1556
Returns True if the price can be changed by the user
1560
for line in self.browse(cr, uid, ids, context=context):
1562
stages = self._get_stages_price(cr, uid, line.product_id.id, line.product_uom.id, line.order_id, context=context)
1563
if line.merged_id and len(line.merged_id.order_line_ids) > 1 and line.order_id.state != 'confirmed' and stages and not line.order_id.rfq_ok:
1564
res[line.id] = False
1568
def _vals_get(self, cr, uid, ids, fields, arg, context=None):
1570
multi fields function method
1572
# Some verifications
1575
if isinstance(ids, (int, long)):
1579
sol_obj = self.pool.get('sale.order.line')
1582
for obj in self.browse(cr, uid, ids, context=context):
1584
result[obj.id] = {'order_state_purchase_order_line': False}
1585
# order_state_purchase_order_line
1587
result[obj.id].update({'order_state_purchase_order_line': obj.order_id.state})
1592
'parent_line_id': fields.many2one('purchase.order.line', string='Parent line'),
1593
'merged_id': fields.many2one('purchase.order.merged.line', string='Merged line'),
1594
'origin': fields.char(size=64, string='Origin'),
1595
'change_price_ok': fields.function(_get_price_change_ok, type='boolean', method=True, string='Price changing'),
1596
'change_price_manually': fields.boolean(string='Update price manually'),
1597
# openerp bug: eval invisible in p.o use the po line state and not the po state !
1598
'fake_state': fields.function(_get_fake_state, type='char', method=True, string='State', help='for internal use only'),
1599
# openerp bug: id is not given to onchanqge call if we are into one2many view
1600
'fake_id':fields.function(_get_fake_id, type='integer', method=True, string='Id', help='for internal use only'),
1601
'old_price_unit': fields.float(digits=(16,2), string='Old price'),
1602
'order_state_purchase_order_line': fields.function(_vals_get, method=True, type='selection', selection=PURCHASE_ORDER_STATE_SELECTION, string='State of Po', multi='get_vals_purchase_override', store=False, readonly=True),
1604
'sync_pol_db_id': fields.integer(string='PO line DB Id', required=False, readonly=True),
1605
'sync_sol_db_id': fields.integer(string='SO line DB Id', required=False, readonly=True),
1609
'change_price_manually': lambda *a: False,
1610
'product_qty': lambda *a: 0.00,
1611
'price_unit': lambda *a: 0.00,
1612
'change_price_ok': lambda *a: True,
1615
def product_uom_change(self, cr, uid, ids, pricelist, product, qty, uom,
1616
partner_id, date_order=False, fiscal_position=False, date_planned=False,
1617
name=False, price_unit=False, notes=False):
1619
res = super(purchase_order_line, self).product_uom_change(cr, uid, ids, pricelist, product, qty, uom,
1620
partner_id, date_order, fiscal_position, date_planned,
1621
name, price_unit, notes)
1624
res['value'].update({'product_qty': 0.00})
1625
res.update({'warning': {}})
1629
def product_id_on_change(self, cr, uid, ids, pricelist, product, qty, uom,
1630
partner_id, date_order=False, fiscal_position=False, date_planned=False,
1631
name=False, price_unit=False, notes=False, state=False, old_price_unit=False,
1632
nomen_manda_0=False, comment=False, context=None):
1634
suppinfo_obj = self.pool.get('product.supplierinfo')
1635
partner_price = self.pool.get('pricelist.partnerinfo')
1637
if product and not uom:
1638
uom = self.pool.get('product.product').browse(cr, uid, product).uom_po_id.id
1640
if context and context.get('purchase_id') and state == 'draft' and product:
1641
domain = [('product_id', '=', product),
1642
('product_uom', '=', uom),
1643
('order_id', '=', context.get('purchase_id'))]
1644
other_lines = self.search(cr, uid, domain)
1645
for l in self.browse(cr, uid, other_lines):
1646
all_qty += l.product_qty
1648
res = super(purchase_order_line, self).product_id_change(cr, uid, ids, pricelist, product, all_qty, uom,
1649
partner_id, date_order, fiscal_position,
1650
date_planned, name, price_unit, notes)
1652
# Remove the warning message if the product has no staged pricelist
1653
# if res.get('warning'):
1654
# supplier_info = self.pool.get('product.supplierinfo').search(cr, uid, [('product_id', '=', product)])
1655
# product_pricelist = self.pool.get('pricelist.partnerinfo').search(cr, uid, [('suppinfo_id', 'in', supplier_info)])
1656
# if not product_pricelist:
1657
# res['warning'] = {}
1658
if res.get('warning', {}).get('title', '') == 'No valid pricelist line found !' or qty == 0.00:
1659
res.update({'warning': {}})
1661
func_curr_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id.id
1663
currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
1665
currency_id = func_curr_id
1667
# Update the old price value
1668
res['value'].update({'product_qty': qty})
1669
if product and not res.get('value', {}).get('price_unit', False) and all_qty != 0.00 and qty != 0.00:
1670
# Display a warning message if the quantity is under the minimal qty of the supplier
1671
currency_id = self.pool.get('product.pricelist').browse(cr, uid, pricelist).currency_id.id
1672
tmpl_id = self.pool.get('product.product').read(cr, uid, product, ['product_tmpl_id'])['product_tmpl_id'][0]
1674
domain = [('uom_id', '=', uom),
1675
('partner_id', '=', partner_id),
1676
('product_id', '=', tmpl_id),
1677
'|', ('valid_from', '<=', date_order),
1678
('valid_from', '=', False),
1679
'|', ('valid_till', '>=', date_order),
1680
('valid_till', '=', False)]
1682
domain_cur = [('currency_id', '=', currency_id)]
1683
domain_cur.extend(domain)
1685
info_prices = partner_price.search(cr, uid, domain_cur, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
1687
info_prices = partner_price.search(cr, uid, domain, order='sequence asc, min_quantity asc, id desc', limit=1, context=context)
1690
info_price = partner_price.browse(cr, uid, info_prices[0], context=context)
1691
info_u_price = self.pool.get('res.currency').compute(cr, uid, info_price.currency_id.id, currency_id, info_price.price)
1692
res['value'].update({'old_price_unit': info_u_price, 'price_unit': info_u_price})
1693
res.update({'warning': {'title': _('Warning'), 'message': _('The product unit price has been set ' \
1694
'for a minimal quantity of %s (the min quantity of the price list), '\
1695
'it might change at the supplier confirmation.') % info_price.min_quantity}})
1697
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res['value']['price_unit'])
1698
res['value'].update({'old_price_unit': old_price})
1700
old_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, res.get('value').get('price_unit'))
1701
res['value'].update({'old_price_unit': old_price})
1703
# Set the unit price with cost price if the product has no staged pricelist
1704
if product and qty != 0.00:
1705
res['value'].update({'comment': False, 'nomen_manda_0': False, 'nomen_manda_1': False,
1706
'nomen_manda_2': False, 'nomen_manda_3': False, 'nomen_sub_0': False,
1707
'nomen_sub_1': False, 'nomen_sub_2': False, 'nomen_sub_3': False,
1708
'nomen_sub_4': False, 'nomen_sub_5': False})
1709
st_price = self.pool.get('product.product').browse(cr, uid, product).standard_price
1710
st_price = self.pool.get('res.currency').compute(cr, uid, func_curr_id, currency_id, st_price)
1712
if res.get('value', {}).get('price_unit', False) == False and (state and state == 'draft') or not state :
1713
res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
1714
elif state and state != 'draft' and old_price_unit:
1715
res['value'].update({'price_unit': old_price_unit, 'old_price_unit': old_price_unit})
1717
if res['value']['price_unit'] == 0.00:
1718
res['value'].update({'price_unit': st_price, 'old_price_unit': st_price})
1721
res['value'].update({'price_unit': 0.00, 'old_price_unit': 0.00})
1722
elif not product and not comment and not nomen_manda_0:
1723
res['value'].update({'price_unit': 0.00, 'product_qty': 0.00, 'product_uom': False, 'old_price_unit': 0.00})
1727
def price_unit_change(self, cr, uid, ids, fake_id, price_unit, product_id,
1728
product_uom, product_qty, pricelist, partner_id, date_order,
1729
change_price_ok, state, old_price_unit,
1730
nomen_manda_0=False, comment=False, context=None):
1732
Display a warning message on change price unit if there are other lines with the same product and the same uom
1739
if not product_id or not product_uom or not product_qty:
1742
order_id = context.get('purchase_id', False)
1746
order = self.pool.get('purchase.order').browse(cr, uid, order_id, context=context)
1747
other_lines = self.search(cr, uid, [('id', '!=', fake_id), ('order_id', '=', order_id), ('product_id', '=', product_id), ('product_uom', '=', product_uom)], context=context)
1748
stages = self._get_stages_price(cr, uid, product_id, product_uom, order, context=context)
1750
if not change_price_ok or (other_lines and stages and order.state != 'confirmed' and not context.get('rfq_ok')):
1751
res.update({'warning': {'title': 'Error',
1752
'message': 'This product get stages prices for this supplier, you cannot change the price manually in draft state '\
1753
'as you have multiple order lines (it is possible in "validated" state.'}})
1754
res['value'].update({'price_unit': old_price_unit})
1756
res['value'].update({'old_price_unit': price_unit})
1760
def get_sol_ids_from_pol_ids(self, cr, uid, ids, context=None):
1762
input: purchase order line ids
1763
return: sale order line ids
1765
# Some verifications
1768
if isinstance(ids, (int, long)):
1772
sol_obj = self.pool.get('sale.order.line')
1773
# procurement ids list
1775
# sale order lines list
1778
for line in self.browse(cr, uid, ids, context=context):
1779
if line.procurement_id:
1780
proc_ids.append(line.procurement_id.id)
1781
# get the corresponding sale order line list
1783
sol_ids = sol_obj.search(cr, uid, [('procurement_id', 'in', proc_ids)], context=context)
1786
def open_split_wizard(self, cr, uid, ids, context=None):
1788
Open the wizard to split the line
1793
if isinstance(ids, (int, long)):
1796
for line in self.browse(cr, uid, ids, context=context):
1797
data = {'purchase_line_id': line.id, 'original_qty': line.product_qty, 'old_line_qty': line.product_qty}
1798
wiz_id = self.pool.get('split.purchase.order.line.wizard').create(cr, uid, data, context=context)
1799
return {'type': 'ir.actions.act_window',
1800
'res_model': 'split.purchase.order.line.wizard',
1801
'view_type': 'form',
1802
'view_mode': 'form',
1808
purchase_order_line()
1810
class account_invoice(osv.osv):
1811
_name = 'account.invoice'
1812
_inherit = 'account.invoice'
1815
'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)]}),
1820
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: