1
# -*- coding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
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
##############################################################################
24
from openerp import SUPERUSER_ID
25
from datetime import datetime
26
from dateutil.relativedelta import relativedelta
28
from openerp.osv import fields, osv
29
from openerp import netsvc
30
from openerp import pooler
31
from openerp.tools.translate import _
32
import openerp.addons.decimal_precision as dp
33
from openerp.osv.orm import browse_record, browse_null
34
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
36
class purchase_order(osv.osv):
38
def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
40
cur_obj=self.pool.get('res.currency')
41
for order in self.browse(cr, uid, ids, context=context):
43
'amount_untaxed': 0.0,
48
cur = order.pricelist_id.currency_id
49
for line in order.order_line:
50
val1 += line.price_subtotal
51
for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, order.partner_id)['taxes']:
52
val += c.get('amount', 0.0)
53
res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
54
res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
55
res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
58
def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context=None):
59
if not value: return False
60
if type(ids)!=type([]):
62
for po in self.browse(cr, uid, ids, context=context):
64
cr.execute("""update purchase_order_line set
68
(date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
69
cr.execute("""update purchase_order set
70
minimum_planned_date=%s where id=%s""", (value, po.id))
73
def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
75
purchase_obj=self.browse(cr, uid, ids, context=context)
76
for purchase in purchase_obj:
77
res[purchase.id] = False
78
if purchase.order_line:
79
min_date=purchase.order_line[0].date_planned
80
for line in purchase.order_line:
81
if line.date_planned < min_date:
82
min_date=line.date_planned
83
res[purchase.id]=min_date
87
def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
89
for purchase in self.browse(cursor, user, ids, context=context):
91
for invoice in purchase.invoice_ids:
92
if invoice.state not in ('draft','cancel'):
93
tot += invoice.amount_untaxed
94
if purchase.amount_untaxed:
95
res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
97
res[purchase.id] = 0.0
100
def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
101
if not ids: return {}
106
p.purchase_id,sum(m.product_qty), m.state
110
stock_picking p on (p.id=m.picking_id)
112
p.purchase_id IN %s GROUP BY m.state, p.purchase_id''',(tuple(ids),))
113
for oid,nbr,state in cr.fetchall():
117
res[oid][0] += nbr or 0.0
118
res[oid][1] += nbr or 0.0
120
res[oid][1] += nbr or 0.0
125
res[r] = 100.0 * res[r][0] / res[r][1]
128
def _get_order(self, cr, uid, ids, context=None):
130
for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
131
result[line.order_id.id] = True
134
def _invoiced(self, cursor, user, ids, name, arg, context=None):
136
for purchase in self.browse(cursor, user, ids, context=context):
138
if purchase.invoiced_rate == 100.00:
140
res[purchase.id] = invoiced
143
def _get_journal(self, cr, uid, context=None):
146
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
147
company_id = context.get('company_id', user.company_id.id)
148
journal_obj = self.pool.get('account.journal')
149
res = journal_obj.search(cr, uid, [('type', '=', 'purchase'),
150
('company_id', '=', company_id)],
152
return res and res[0] or False
155
('draft', 'Draft PO'),
156
('sent', 'RFQ Sent'),
157
('confirmed', 'Waiting Approval'),
158
('approved', 'Purchase Order'),
159
('except_picking', 'Shipping Exception'),
160
('except_invoice', 'Invoice Exception'),
162
('cancel', 'Cancelled')
166
'purchase.mt_rfq_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirmed',
167
'purchase.mt_rfq_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'approved',
171
'name': fields.char('Order Reference', size=64, required=True, select=True, help="Unique number of the purchase order, computed automatically when the purchase order is created."),
172
'origin': fields.char('Source Document', size=64,
173
help="Reference of the document that generated this purchase order request; a sales order or an internal procurement request."
175
'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64,
176
help="Reference of the sales order or quotation sent by your supplier. It's mainly used to do the matching when you receive the products as this reference is usually written on the delivery order sent by your supplier."),
177
'date_order':fields.date('Order Date', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}, select=True, help="Date on which this document has been created."),
178
'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
179
'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
180
change_default=True, track_visibility='always'),
181
'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
182
states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
183
help="Put an address if you want to deliver directly from the supplier to the customer. " \
184
"Otherwise, keep empty to deliver to your own company."
186
'warehouse_id': fields.many2one('stock.warehouse', 'Destination Warehouse'),
187
'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]} ),
188
'pricelist_id':fields.many2one('product.pricelist', 'Pricelist', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, help="The pricelist sets the currency used for this purchase order. It also computes the supplier price for the selected products/quantities."),
189
'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", string="Currency",readonly=True, required=True),
190
'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, help="The status of the purchase order or the quotation request. A quotation is a purchase order in a 'Draft' status. Then the order has to be confirmed by the user, the status switch to 'Confirmed'. Then the supplier must confirm the order to change the status to 'Approved'. When the purchase order is paid and received, the status becomes 'Done'. If a cancel action occurs in the invoice or in the reception of goods, the status becomes in exception.", select=True),
191
'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
192
'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
193
'notes': fields.text('Terms and Conditions'),
194
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
195
'picking_ids': fields.one2many('stock.picking.in', 'purchase_id', 'Picking List', readonly=True, help="This is the list of incoming shipments that have been generated for this purchase order."),
196
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
197
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
198
'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
199
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
200
'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True,
201
readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
202
help="Based on Purchase Order lines: place individual lines in 'Invoice Control > Based on P.O. lines' from where you can selectively create an invoice.\n" \
203
"Based on generated invoice: create a draft invoice you can validate later.\n" \
204
"Bases on incoming shipments: let you create an invoice when receptions are validated."
206
'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, string='Expected Date', type='date', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
208
'purchase.order.line': (_get_order, ['date_planned'], 10),
211
'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
213
'purchase.order.line': (_get_order, None, 10),
214
}, multi="sums", help="The amount without tax", track_visibility='always'),
215
'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
217
'purchase.order.line': (_get_order, None, 10),
218
}, multi="sums", help="The tax amount"),
219
'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Total',
221
'purchase.order.line': (_get_order, None, 10),
222
}, multi="sums",help="The total amount"),
223
'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
224
'payment_term_id': fields.many2one('account.payment.term', 'Payment Term'),
225
'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
226
'create_uid': fields.many2one('res.users', 'Responsible'),
227
'company_id': fields.many2one('res.company','Company',required=True,select=1, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
228
'journal_id': fields.many2one('account.journal', 'Journal'),
229
'num_semaine': fields.char('Semaine de livraison'),
232
'date_order': fields.date.context_today,
234
'name': lambda obj, cr, uid, context: '/',
236
'invoice_method': 'order',
238
'pricelist_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').browse(cr, uid, context['partner_id']).property_product_pricelist_purchase.id,
239
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
240
'journal_id': _get_journal,
241
'num_semaine': lambda *a: time.strftime('%W') + '/' + time.strftime('%Y'),
244
('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
246
_name = "purchase.order"
247
_inherit = ['mail.thread', 'ir.needaction_mixin']
248
_description = "Purchase Order"
251
def create(self, cr, uid, vals, context=None):
252
if vals.get('name','/')=='/':
253
vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
254
order = super(purchase_order, self).create(cr, uid, vals, context=context)
257
def unlink(self, cr, uid, ids, context=None):
258
purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
260
for s in purchase_orders:
261
if s['state'] in ['draft','cancel']:
262
unlink_ids.append(s['id'])
264
raise osv.except_osv(_('Invalid Action!'), _('In order to delete a purchase order, you must cancel it first.'))
266
# automatically sending subflow.delete upon deletion
267
wf_service = netsvc.LocalService("workflow")
268
for id in unlink_ids:
269
wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
271
return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
273
def button_dummy(self, cr, uid, ids, context=None):
276
def onchange_pricelist(self, cr, uid, ids, pricelist_id, context=None):
279
return {'value': {'currency_id': self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context).currency_id.id}}
281
def onchange_dest_address_id(self, cr, uid, ids, address_id):
284
address = self.pool.get('res.partner')
285
values = {'warehouse_id': False}
286
supplier = address.browse(cr, uid, address_id)
288
location_id = supplier.property_stock_customer.id
289
values.update({'location_id': location_id})
290
return {'value':values}
292
def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
295
warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id)
296
return {'value':{'location_id': warehouse.lot_input_id.id, 'dest_address_id': False}}
298
def onchange_partner_id(self, cr, uid, ids, partner_id):
299
partner = self.pool.get('res.partner')
302
'fiscal_position': False,
303
'payment_term_id': False,
305
supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
306
supplier = partner.browse(cr, uid, partner_id)
308
'pricelist_id': supplier.property_product_pricelist_purchase.id,
309
'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
310
'payment_term_id': supplier.property_supplier_payment_term.id or False,
313
def invoice_open(self, cr, uid, ids, context=None):
314
mod_obj = self.pool.get('ir.model.data')
315
act_obj = self.pool.get('ir.actions.act_window')
317
result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree2')
318
id = result and result[1] or False
319
result = act_obj.read(cr, uid, [id], context=context)[0]
321
for po in self.browse(cr, uid, ids, context=context):
322
inv_ids+= [invoice.id for invoice in po.invoice_ids]
324
raise osv.except_osv(_('Error!'), _('Please create Invoices.'))
325
#choose the view_mode accordingly
327
result['domain'] = "[('id','in',["+','.join(map(str, inv_ids))+"])]"
329
res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
330
result['views'] = [(res and res[1] or False, 'form')]
331
result['res_id'] = inv_ids and inv_ids[0] or False
334
def view_invoice(self, cr, uid, ids, context=None):
336
This function returns an action that display existing invoices of given sales order ids. It can either be a in a list or in a form view, if there is only one invoice to show.
338
mod_obj = self.pool.get('ir.model.data')
339
wizard_obj = self.pool.get('purchase.order.line_invoice')
340
#compute the number of invoices to display
342
for po in self.browse(cr, uid, ids, context=context):
343
if po.invoice_method == 'manual':
344
if not po.invoice_ids:
345
context.update({'active_ids' : [line.id for line in po.order_line]})
346
wizard_obj.makeInvoices(cr, uid, [], context=context)
348
for po in self.browse(cr, uid, ids, context=context):
349
inv_ids+= [invoice.id for invoice in po.invoice_ids]
350
res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
351
res_id = res and res[1] or False
354
'name': _('Supplier Invoices'),
358
'res_model': 'account.invoice',
359
'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
360
'type': 'ir.actions.act_window',
363
'res_id': inv_ids and inv_ids[0] or False,
366
def view_picking(self, cr, uid, ids, context=None):
368
This function returns an action that display existing pîcking orders of given purchase order ids.
370
mod_obj = self.pool.get('ir.model.data')
372
for po in self.browse(cr, uid, ids, context=context):
373
pick_ids += [picking.id for picking in po.picking_ids]
375
action_model, action_id = tuple(mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree4'))
376
action = self.pool.get(action_model).read(cr, uid, action_id, context=context)
377
ctx = eval(action['context'])
379
'search_default_purchase_id': ids[0]
381
if pick_ids and len(pick_ids) == 1:
382
form_view_ids = [view_id for view_id, view in action['views'] if view == 'form']
383
view_id = form_view_ids and form_view_ids[0] or False
388
'res_id': pick_ids[0]
396
def wkf_approve_order(self, cr, uid, ids, context=None):
397
self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
400
def print_confirm(self,cr,uid,ids,context=None):
403
def print_double(self,cr,uid,ids,context=None):
404
print "double Approval"
406
def print_router(self,cr,uid,ids,context=None):
409
def wkf_send_rfq(self, cr, uid, ids, context=None):
411
This function opens a window to compose an email, with the edi purchase template message loaded by default
413
ir_model_data = self.pool.get('ir.model.data')
415
template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
419
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
421
compose_form_id = False
424
'default_model': 'purchase.order',
425
'default_res_id': ids[0],
426
'default_use_template': bool(template_id),
427
'default_template_id': template_id,
428
'default_composition_mode': 'comment',
431
'type': 'ir.actions.act_window',
434
'res_model': 'mail.compose.message',
435
'views': [(compose_form_id, 'form')],
436
'view_id': compose_form_id,
441
def print_quotation(self, cr, uid, ids, context=None):
443
This function prints the request for quotation and mark it as sent, so that we can see more easily the next step of the workflow
445
assert len(ids) == 1, 'This option should only be used for a single id at a time'
446
wf_service = netsvc.LocalService("workflow")
447
wf_service.trg_validate(uid, 'purchase.order', ids[0], 'send_rfq', cr)
449
'model': 'purchase.order',
451
'form': self.read(cr, uid, ids[0], context=context),
453
return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True}
455
#TODO: implement messages system
456
def wkf_confirm_order(self, cr, uid, ids, context=None):
458
for po in self.browse(cr, uid, ids, context=context):
459
if not po.order_line:
460
raise osv.except_osv(_('Error!'),_('You cannot confirm a purchase order without any purchase order line.'))
461
for line in po.order_line:
462
if line.state=='draft':
465
self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
467
self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
470
def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
471
"""Collects require data from purchase order line that is used to create invoice line
472
for that purchase order line
473
:param account_id: Expense account of the product of PO line if any.
474
:param browse_record order_line: Purchase order line browse record
475
:return: Value for fields of invoice lines.
479
'name': order_line.name,
480
'account_id': account_id,
481
'price_unit': order_line.price_unit or 0.0,
482
'quantity': order_line.product_qty,
483
'product_id': order_line.product_id.id or False,
484
'uos_id': order_line.product_uom.id or False,
485
'invoice_line_tax_id': [(6, 0, [x.id for x in order_line.taxes_id])],
486
'account_analytic_id': order_line.account_analytic_id.id or False,
489
def action_cancel_draft(self, cr, uid, ids, context=None):
492
self.write(cr, uid, ids, {'state':'draft','shipped':0})
493
wf_service = netsvc.LocalService("workflow")
495
# Deleting the existing instance of workflow for PO
496
wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
497
wf_service.trg_create(uid, 'purchase.order', p_id, cr)
500
def action_invoice_create(self, cr, uid, ids, context=None):
501
"""Generates invoice for given ids of purchase orders and links that invoice ID to purchase order.
502
:param ids: list of ids of purchase orders.
503
:return: ID of created invoice.
508
journal_obj = self.pool.get('account.journal')
509
inv_obj = self.pool.get('account.invoice')
510
inv_line_obj = self.pool.get('account.invoice.line')
511
fiscal_obj = self.pool.get('account.fiscal.position')
512
property_obj = self.pool.get('ir.property')
514
for order in self.browse(cr, uid, ids, context=context):
515
pay_acc_id = order.partner_id.property_account_payable.id
516
journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', order.company_id.id)], limit=1)
518
raise osv.except_osv(_('Error!'),
519
_('Define purchase journal for this company: "%s" (id:%d).') % (order.company_id.name, order.company_id.id))
521
# generate invoice line correspond to PO line and link that to created invoice (inv_id) and PO line
523
for po_line in order.order_line:
524
if po_line.product_id:
525
acc_id = po_line.product_id.property_account_expense.id
527
acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
529
raise osv.except_osv(_('Error!'), _('Define expense account for this company: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
531
acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category').id
532
fpos = order.fiscal_position or False
533
acc_id = fiscal_obj.map_account(cr, uid, fpos, acc_id)
535
inv_line_data = self._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
536
inv_line_id = inv_line_obj.create(cr, uid, inv_line_data, context=context)
537
inv_lines.append(inv_line_id)
539
po_line.write({'invoiced':True, 'invoice_lines': [(4, inv_line_id)]}, context=context)
541
# get invoice data and create invoice
543
'name': order.partner_ref or order.name,
544
'reference': order.partner_ref or order.name,
545
'account_id': pay_acc_id,
546
'type': 'in_invoice',
547
'partner_id': order.partner_id.id,
548
'currency_id': order.pricelist_id.currency_id.id,
549
'journal_id': len(journal_ids) and journal_ids[0] or False,
550
'invoice_line': [(6, 0, inv_lines)],
551
'origin': order.name,
552
'fiscal_position': order.fiscal_position.id or False,
553
'payment_term': order.payment_term_id.id or False,
554
'company_id': order.company_id.id,
556
inv_id = inv_obj.create(cr, uid, inv_data, context=context)
558
# compute the invoice
559
inv_obj.button_compute(cr, uid, [inv_id], context=context, set_total=True)
561
# Link this new invoice to related purchase order
562
order.write({'invoice_ids': [(4, inv_id)]}, context=context)
566
def invoice_done(self, cr, uid, ids, context=None):
567
self.write(cr, uid, ids, {'state':'approved'}, context=context)
570
def has_stockable_product(self, cr, uid, ids, *args):
571
for order in self.browse(cr, uid, ids):
572
for order_line in order.order_line:
573
if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
577
def action_cancel(self, cr, uid, ids, context=None):
578
wf_service = netsvc.LocalService("workflow")
579
for purchase in self.browse(cr, uid, ids, context=context):
580
for pick in purchase.picking_ids:
581
if pick.state not in ('draft','cancel'):
582
raise osv.except_osv(
583
_('Unable to cancel this purchase order.'),
584
_('First cancel all receptions related to this purchase order.'))
585
for pick in purchase.picking_ids:
586
wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
587
for inv in purchase.invoice_ids:
588
if inv and inv.state not in ('cancel','draft'):
589
raise osv.except_osv(
590
_('Unable to cancel this purchase order.'),
591
_('You must first cancel all receptions related to this purchase order.'))
593
wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
594
self.write(cr,uid,ids,{'state':'cancel'})
596
for (id, name) in self.name_get(cr, uid, ids):
597
wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
600
def date_to_datetime(self, cr, uid, userdate, context=None):
601
""" Convert date values expressed in user's timezone to
602
server-side UTC timestamp, assuming a default arbitrary
603
time of 12:00 AM - because a time is needed.
605
:param str userdate: date string in in user time zone
606
:return: UTC datetime string for server-side use
608
# TODO: move to fields.datetime in server after 7.0
609
user_date = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT)
610
if context and context.get('tz'):
611
tz_name = context['tz']
613
tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
615
utc = pytz.timezone('UTC')
616
context_tz = pytz.timezone(tz_name)
617
user_datetime = user_date + relativedelta(hours=12.0)
618
local_timestamp = context_tz.localize(user_datetime, is_dst=False)
619
user_datetime = local_timestamp.astimezone(utc)
620
return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
621
return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
623
def _prepare_order_picking(self, cr, uid, order, context=None):
625
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
626
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
627
'date': self.date_to_datetime(cr, uid, order.date_order, context),
628
'partner_id': order.dest_address_id.id or order.partner_id.id,
629
'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
631
'partner_id': order.dest_address_id.id or order.partner_id.id,
632
'purchase_id': order.id,
633
'company_id': order.company_id.id,
637
def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, context=None):
639
'name': order_line.name or '',
640
'product_id': order_line.product_id.id,
641
'product_qty': order_line.product_qty,
642
'product_uos_qty': order_line.product_qty,
643
'product_uom': order_line.product_uom.id,
644
'product_uos': order_line.product_uom.id,
645
'date': self.date_to_datetime(cr, uid, order.date_order, context),
646
'date_expected': self.date_to_datetime(cr, uid, order_line.date_planned, context),
647
'location_id': order.partner_id.property_stock_supplier.id,
648
'location_dest_id': order.location_id.id,
649
'picking_id': picking_id,
650
'partner_id': order.dest_address_id.id or order.partner_id.id,
651
'move_dest_id': order_line.move_dest_id.id,
654
'purchase_line_id': order_line.id,
655
'company_id': order.company_id.id,
656
'price_unit': order_line.price_unit
659
def _create_pickings(self, cr, uid, order, order_lines, picking_id=False, context=None):
660
"""Creates pickings and appropriate stock moves for given order lines, then
661
confirms the moves, makes them available, and confirms the picking.
663
If ``picking_id`` is provided, the stock moves will be added to it, otherwise
664
a standard outgoing picking will be created to wrap the stock moves, as returned
665
by :meth:`~._prepare_order_picking`.
667
Modules that wish to customize the procurements or partition the stock moves over
668
multiple stock pickings may override this method and call ``super()`` with
669
different subsets of ``order_lines`` and/or preset ``picking_id`` values.
671
:param browse_record order: purchase order to which the order lines belong
672
:param list(browse_record) order_lines: purchase order line records for which picking
673
and moves should be created.
674
:param int picking_id: optional ID of a stock picking to which the created stock moves
675
will be added. A new picking will be created if omitted.
676
:return: list of IDs of pickings used/created for the given order lines (usually just one)
679
picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
681
stock_move = self.pool.get('stock.move')
682
wf_service = netsvc.LocalService("workflow")
683
for order_line in order_lines:
684
if not order_line.product_id:
686
if order_line.product_id.type in ('product', 'consu'):
687
move = stock_move.create(cr, uid, self._prepare_order_line_move(cr, uid, order, order_line, picking_id, context=context))
688
if order_line.move_dest_id:
689
order_line.move_dest_id.write({'location_id': order.location_id.id})
690
todo_moves.append(move)
691
stock_move.action_confirm(cr, uid, todo_moves)
692
stock_move.force_assign(cr, uid, todo_moves)
693
wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
696
def action_picking_create(self, cr, uid, ids, context=None):
698
for order in self.browse(cr, uid, ids):
699
picking_ids.extend(self._create_pickings(cr, uid, order, order.order_line, None, context=context))
701
# Must return one unique picking ID: the one to connect in the subflow of the purchase order.
702
# In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
703
# one that should trigger the advancement of the purchase workflow.
704
# By default we will consider the first one as most important, but this behavior can be overridden.
705
return picking_ids[0] if picking_ids else False
707
def picking_done(self, cr, uid, ids, context=None):
708
self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
711
def copy(self, cr, uid, id, default=None, context=None):
720
'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
722
return super(purchase_order, self).copy(cr, uid, id, default, context)
724
def do_merge(self, cr, uid, ids, context=None):
726
To merge similar type of purchase orders.
727
Orders will only be merged if:
728
* Purchase Orders are in draft
729
* Purchase Orders belong to the same partner
730
* Purchase Orders are have same stock location, same pricelist
731
Lines will only be merged if:
732
* Order lines are exactly the same except for the quantity and unit
734
@param self: The object pointer.
735
@param cr: A database cursor
736
@param uid: ID of the user currently logged in
737
@param ids: the ID or list of IDs
738
@param context: A standard dictionary
740
@return: new purchase order id
743
#TOFIX: merged order line should be unlink
744
wf_service = netsvc.LocalService("workflow")
745
def make_key(br, fields):
748
field_val = getattr(br, field)
749
if field in ('product_id', 'move_dest_id', 'account_analytic_id'):
752
if isinstance(field_val, browse_record):
753
field_val = field_val.id
754
elif isinstance(field_val, browse_null):
756
elif isinstance(field_val, list):
757
field_val = ((6, 0, tuple([v.id for v in field_val])),)
758
list_key.append((field, field_val))
760
return tuple(list_key)
762
# Compute what the new orders should contain
766
for porder in [order for order in self.browse(cr, uid, ids, context=context) if order.state == 'draft']:
767
order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id'))
768
new_order = new_orders.setdefault(order_key, ({}, []))
769
new_order[1].append(porder.id)
770
order_infos = new_order[0]
773
'origin': porder.origin,
774
'date_order': porder.date_order,
775
'partner_id': porder.partner_id.id,
776
'dest_address_id': porder.dest_address_id.id,
777
'warehouse_id': porder.warehouse_id.id,
778
'location_id': porder.location_id.id,
779
'pricelist_id': porder.pricelist_id.id,
782
'notes': '%s' % (porder.notes or '',),
783
'fiscal_position': porder.fiscal_position and porder.fiscal_position.id or False,
786
if porder.date_order < order_infos['date_order']:
787
order_infos['date_order'] = porder.date_order
789
order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
791
order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
793
for order_line in porder.order_line:
794
line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'product_id', 'move_dest_id', 'account_analytic_id'))
795
o_line = order_infos['order_line'].setdefault(line_key, {})
797
# merge the line with an existing line
798
o_line['product_qty'] += order_line.product_qty * order_line.product_uom.factor / o_line['uom_factor']
800
# append a new "standalone" line
801
for field in ('product_qty', 'product_uom'):
802
field_val = getattr(order_line, field)
803
if isinstance(field_val, browse_record):
804
field_val = field_val.id
805
o_line[field] = field_val
806
o_line['uom_factor'] = order_line.product_uom and order_line.product_uom.factor or 1.0
812
for order_key, (order_data, old_ids) in new_orders.iteritems():
813
# skip merges with only one order
815
allorders += (old_ids or [])
818
# cleanup order line data
819
for key, value in order_data['order_line'].iteritems():
820
del value['uom_factor']
821
value.update(dict(key))
822
order_data['order_line'] = [(0, 0, value) for value in order_data['order_line'].itervalues()]
824
# create the new order
825
neworder_id = self.create(cr, uid, order_data)
826
orders_info.update({neworder_id: old_ids})
827
allorders.append(neworder_id)
829
# make triggers pointing to the old orders point to the new order
830
for old_id in old_ids:
831
wf_service.trg_redirect(uid, 'purchase.order', old_id, neworder_id, cr)
832
wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
836
class purchase_order_line(osv.osv):
837
def _amount_line(self, cr, uid, ids, prop, arg, context=None):
839
cur_obj=self.pool.get('res.currency')
840
tax_obj = self.pool.get('account.tax')
841
for line in self.browse(cr, uid, ids, context=context):
842
taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, line.order_id.partner_id)
843
cur = line.order_id.pricelist_id.currency_id
844
res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
848
'name': fields.text('Description', required=True),
849
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
850
'date_planned': fields.date('Scheduled Date', required=True, select=True),
851
'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
852
'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
853
'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
854
'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
855
'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
856
'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
857
'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Account')),
858
'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
859
'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
860
'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
861
'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
862
help=' * The \'Draft\' status is set automatically when purchase order in draft status. \
863
\n* The \'Confirmed\' status is set automatically as confirm when purchase order in confirm status. \
864
\n* The \'Done\' status is set automatically when purchase order is set as done. \
865
\n* The \'Cancelled\' status is set automatically when user cancel purchase order.'),
866
'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
867
'invoiced': fields.boolean('Invoiced', readonly=True),
868
'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
869
'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
873
'product_qty': lambda *a: 1.0,
874
'state': lambda *args: 'draft',
875
'invoiced': lambda *a: 0,
877
_table = 'purchase_order_line'
878
_name = 'purchase.order.line'
879
_description = 'Purchase Order Line'
881
def copy_data(self, cr, uid, id, default=None, context=None):
884
default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
885
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
887
def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
888
partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
889
name=False, price_unit=False, context=None):
891
onchange handler of product_uom.
894
return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
895
return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
896
partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
897
name=name, price_unit=price_unit, context=context)
899
def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
900
"""Return the datetime value to use as Schedule Date (``date_planned``) for
901
PO Lines that correspond to the given product.supplierinfo,
902
when ordered at `date_order_str`.
904
:param browse_record | False supplier_info: product.supplierinfo, used to
905
determine delivery delay (if False, default delay = 0)
906
:param str date_order_str: date of order, as a string in
907
DEFAULT_SERVER_DATE_FORMAT
909
:return: desired Schedule Date for the PO line
911
supplier_delay = int(supplier_info.delay) if supplier_info else 0
912
return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
914
def _check_product_uom_group(self, cr, uid, context=None):
915
group_uom = self.pool.get('ir.model.data').get_object(cr, uid, 'product', 'group_uom')
916
res = [user for user in group_uom.users if user.id == uid]
917
return len(res) and True or False
920
def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
921
partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
922
name=False, price_unit=False, context=None):
924
onchange handler of product_id.
929
res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
933
product_product = self.pool.get('product.product')
934
product_uom = self.pool.get('product.uom')
935
res_partner = self.pool.get('res.partner')
936
product_supplierinfo = self.pool.get('product.supplierinfo')
937
product_pricelist = self.pool.get('product.pricelist')
938
account_fiscal_position = self.pool.get('account.fiscal.position')
939
account_tax = self.pool.get('account.tax')
941
# - check for the presence of partner_id and pricelist_id
943
# raise osv.except_osv(_('No Partner!'), _('Select a partner in purchase order to choose a product.'))
944
#if not pricelist_id:
945
# raise osv.except_osv(_('No Pricelist !'), _('Select a price list in the purchase order form before choosing a product.'))
947
# - determine name and notes based on product in partner lang.
948
context_partner = context.copy()
950
lang = res_partner.browse(cr, uid, partner_id).lang
951
context_partner.update( {'lang': lang, 'partner_id': partner_id} )
952
product = product_product.browse(cr, uid, product_id, context=context_partner)
953
#call name_get() with partner in the context to eventually match name and description in the seller_ids field
954
dummy, name = product_product.name_get(cr, uid, product_id, context=context_partner)[0]
955
if product.description_purchase:
956
name += '\n' + product.description_purchase
957
res['value'].update({'name': name})
959
# - set a domain on product_uom
960
res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
962
# - check that uom and product uom belong to the same category
963
product_uom_po_id = product.uom_po_id.id
965
uom_id = product_uom_po_id
967
if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
968
if self._check_product_uom_group(cr, uid, context=context):
969
res['warning'] = {'title': _('Warning!'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')}
970
uom_id = product_uom_po_id
972
res['value'].update({'product_uom': uom_id})
974
# - determine product_qty and date_planned based on seller info
976
date_order = fields.date.context_today(self,cr,uid,context=context)
980
for supplier in product.seller_ids:
981
if partner_id and (supplier.name.id == partner_id):
982
supplierinfo = supplier
983
if supplierinfo.product_uom.id != uom_id:
984
res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
985
min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
986
if (qty or 0.0) < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
988
res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier has a minimal quantity set to %s %s, you should not purchase less.') % (supplierinfo.min_qty, supplierinfo.product_uom.name)}
990
dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
992
res['value'].update({'date_planned': date_planned or dt})
994
res['value'].update({'product_qty': qty})
996
# - determine price_unit and taxes_id
998
price = product_pricelist.price_get(cr, uid, [pricelist_id],
999
product.id, qty or 1.0, partner_id or False, {'uom': uom_id, 'date': date_order})[pricelist_id]
1001
price = product.standard_price
1003
taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
1004
fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
1005
taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
1006
res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
1010
product_id_change = onchange_product_id
1011
product_uom_change = onchange_product_uom
1013
def action_confirm(self, cr, uid, ids, context=None):
1014
self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
1017
purchase_order_line()
1019
class procurement_order(osv.osv):
1020
_inherit = 'procurement.order'
1022
'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
1025
def check_buy(self, cr, uid, ids, context=None):
1026
''' return True if the supply method of the mto product is 'buy'
1028
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1029
for procurement in self.browse(cr, uid, ids, context=context):
1030
if procurement.product_id.supply_method <> 'buy':
1034
def check_supplier_info(self, cr, uid, ids, context=None):
1035
partner_obj = self.pool.get('res.partner')
1036
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1037
for procurement in self.browse(cr, uid, ids, context=context):
1038
if not procurement.product_id.seller_ids:
1039
message = _('No supplier defined for this product !')
1040
self.message_post(cr, uid, [procurement.id], body=message)
1041
cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
1043
partner = procurement.product_id.seller_id #Taken Main Supplier of Product of Procurement.
1046
message = _('No default supplier defined for this product')
1047
self.message_post(cr, uid, [procurement.id], body=message)
1048
cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
1050
if user.company_id and user.company_id.partner_id:
1051
if partner.id == user.company_id.partner_id.id:
1052
raise osv.except_osv(_('Configuration Error!'), _('The product "%s" has been defined with your company as reseller which seems to be a configuration error!' % procurement.product_id.name))
1054
address_id = partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1056
message = _('No address defined for the supplier')
1057
self.message_post(cr, uid, [procurement.id], body=message)
1058
cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
1063
def action_po_assign(self, cr, uid, ids, context=None):
1064
""" This is action which call from workflow to assign purchase order to procurements
1067
res = self.make_po(cr, uid, ids, context=context)
1069
return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1071
def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
1072
"""Create the purchase order from the procurement, using
1073
the provided field values, after adding the given purchase
1074
order line in the purchase order.
1076
:params procurement: the procurement object generating the purchase order
1077
:params dict po_vals: field values for the new purchase order (the
1078
``order_line`` field will be overwritten with one
1079
single line, as passed in ``line_vals``).
1080
:params dict line_vals: field values of the single purchase order line that
1081
the purchase order will contain.
1082
:return: id of the newly created purchase order
1085
po_vals.update({'order_line': [(0,0,line_vals)]})
1086
return self.pool.get('purchase.order').create(cr, uid, po_vals, context=context)
1088
def _get_purchase_schedule_date(self, cr, uid, procurement, company, context=None):
1089
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
1090
Purchase Order Lines created to satisfy the given procurement.
1092
:param browse_record procurement: the procurement for which a PO will be created.
1093
:param browse_report company: the company to which the new PO will belong to.
1095
:return: the desired Schedule Date for the PO lines
1097
procurement_date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
1098
schedule_date = (procurement_date_planned - relativedelta(days=company.po_lead))
1099
return schedule_date
1101
def _get_purchase_order_date(self, cr, uid, procurement, company, schedule_date, context=None):
1102
"""Return the datetime value to use as Order Date (``date_order``) for the
1103
Purchase Order created to satisfy the given procurement.
1105
:param browse_record procurement: the procurement for which a PO will be created.
1106
:param browse_report company: the company to which the new PO will belong to.
1107
:param datetime schedule_date: desired Scheduled Date for the Purchase Order lines.
1109
:return: the desired Order Date for the PO
1111
seller_delay = int(procurement.product_id.seller_delay)
1112
return schedule_date - relativedelta(days=seller_delay)
1114
def make_po(self, cr, uid, ids, context=None):
1115
""" Make purchase order from procurement
1116
@return: New created Purchase Orders procurement wise
1121
company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1122
partner_obj = self.pool.get('res.partner')
1123
uom_obj = self.pool.get('product.uom')
1124
pricelist_obj = self.pool.get('product.pricelist')
1125
prod_obj = self.pool.get('product.product')
1126
acc_pos_obj = self.pool.get('account.fiscal.position')
1127
seq_obj = self.pool.get('ir.sequence')
1128
warehouse_obj = self.pool.get('stock.warehouse')
1129
for procurement in self.browse(cr, uid, ids, context=context):
1130
res_id = procurement.move_id.id
1131
partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
1132
seller_qty = procurement.product_id.seller_qty
1133
partner_id = partner.id
1134
address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1135
pricelist_id = partner.property_product_pricelist_purchase.id
1136
warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context)
1137
uom_id = procurement.product_id.uom_po_id.id
1139
qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1141
qty = max(qty,seller_qty)
1143
price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
1145
schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
1146
purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
1148
#Passing partner_id to context for purchase order line integrity of Line name
1149
new_context = context.copy()
1150
new_context.update({'lang': partner.lang, 'partner_id': partner_id})
1152
product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context)
1153
taxes_ids = procurement.product_id.supplier_taxes_id
1154
taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
1156
name = product.partner_ref
1157
if product.description_purchase:
1158
name += '\n'+ product.description_purchase
1162
'product_id': procurement.product_id.id,
1163
'product_uom': uom_id,
1164
'price_unit': price or 0.0,
1165
'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1166
'move_dest_id': res_id,
1167
'taxes_id': [(6,0,taxes)],
1169
name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
1172
'origin': procurement.origin,
1173
'partner_id': partner_id,
1174
'location_id': procurement.location_id.id,
1175
'warehouse_id': warehouse_id and warehouse_id[0] or False,
1176
'pricelist_id': pricelist_id,
1177
'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1178
'company_id': procurement.company_id.id,
1179
'fiscal_position': partner.property_account_position and partner.property_account_position.id or False,
1180
'payment_term_id': partner.property_supplier_payment_term.id or False,
1182
res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=new_context)
1183
self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
1184
self.message_post(cr, uid, ids, body=_("Draft Purchase Order created"), context=context)
1187
def _product_virtual_get(self, cr, uid, order_point):
1188
procurement = order_point.procurement_id
1189
if procurement and procurement.state != 'exception' and procurement.purchase_id and procurement.purchase_id.state in ('draft', 'confirmed'):
1191
return super(procurement_order, self)._product_virtual_get(cr, uid, order_point)
1194
class mail_mail(osv.Model):
1196
_inherit = 'mail.mail'
1198
def _postprocess_sent_message(self, cr, uid, mail, context=None):
1199
if mail.model == 'purchase.order':
1200
wf_service = netsvc.LocalService("workflow")
1201
wf_service.trg_validate(uid, 'purchase.order', mail.res_id, 'send_rfq', cr)
1202
return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context)
1205
class product_template(osv.Model):
1206
_name = 'product.template'
1207
_inherit = 'product.template'
1209
'purchase_ok': fields.boolean('Can be Purchased', help="Specify if the product can be selected in a purchase order line."),
1216
class mail_compose_message(osv.Model):
1217
_inherit = 'mail.compose.message'
1219
def send_mail(self, cr, uid, ids, context=None):
1220
context = context or {}
1221
if context.get('default_model') == 'purchase.order' and context.get('default_res_id'):
1222
context = dict(context, mail_post_autofollow=True)
1223
wf_service = netsvc.LocalService("workflow")
1224
wf_service.trg_validate(uid, 'purchase.order', context['default_res_id'], 'send_rfq', cr)
1225
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
1227
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: