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 ##############################################################################
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
26 from osv import osv, fields
29 from tools.translate import _
30 import decimal_precision as dp
31 from osv.orm import browse_record, browse_null
32 from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
34 class purchase_order(osv.osv):
36 def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
38 cur_obj=self.pool.get('res.currency')
39 for order in self.browse(cr, uid, ids, context=context):
41 'amount_untaxed': 0.0,
46 cur = order.pricelist_id.currency_id
47 for line in order.order_line:
48 val1 += line.price_subtotal
49 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']:
50 val += c.get('amount', 0.0)
51 res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
52 res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
53 res[order.id]['amount_total']=res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
56 def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context=None):
57 if not value: return False
58 if type(ids)!=type([]):
60 for po in self.browse(cr, uid, ids, context=context):
62 cr.execute("""update purchase_order_line set
66 (date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
67 cr.execute("""update purchase_order set
68 minimum_planned_date=%s where id=%s""", (value, po.id))
71 def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
73 purchase_obj=self.browse(cr, uid, ids, context=context)
74 for purchase in purchase_obj:
75 res[purchase.id] = False
76 if purchase.order_line:
77 min_date=purchase.order_line[0].date_planned
78 for line in purchase.order_line:
79 if line.date_planned < min_date:
80 min_date=line.date_planned
81 res[purchase.id]=min_date
85 def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
87 for purchase in self.browse(cursor, user, ids, context=context):
89 for invoice in purchase.invoice_ids:
90 if invoice.state not in ('draft','cancel'):
91 tot += invoice.amount_untaxed
92 if purchase.amount_untaxed:
93 res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
95 res[purchase.id] = 0.0
98 def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
104 p.purchase_id,sum(m.product_qty), m.state
108 stock_picking p on (p.id=m.picking_id)
110 p.purchase_id IN %s GROUP BY m.state, p.purchase_id''',(tuple(ids),))
111 for oid,nbr,state in cr.fetchall():
115 res[oid][0] += nbr or 0.0
116 res[oid][1] += nbr or 0.0
118 res[oid][1] += nbr or 0.0
123 res[r] = 100.0 * res[r][0] / res[r][1]
126 def _get_order(self, cr, uid, ids, context=None):
128 for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
129 result[line.order_id.id] = True
132 def _invoiced(self, cursor, user, ids, name, arg, context=None):
134 for purchase in self.browse(cursor, user, ids, context=context):
136 if purchase.invoiced_rate == 100.00:
138 res[purchase.id] = invoiced
141 def _get_journal(self, cr, uid, context=None):
144 user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
145 company_id = context.get('company_id', user.company_id.id)
146 journal_obj = self.pool.get('account.journal')
147 res = journal_obj.search(cr, uid, [('type', '=', 'purchase'),
148 ('company_id', '=', company_id)],
150 return res and res[0] or False
153 ('draft', 'Draft PO'),
154 ('sent', 'RFQ Sent'),
155 ('confirmed', 'Waiting Approval'),
156 ('approved', 'Purchase Order'),
157 ('except_picking', 'Shipping Exception'),
158 ('except_invoice', 'Invoice Exception'),
160 ('cancel', 'Cancelled')
164 '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."),
165 'origin': fields.char('Source Document', size=64,
166 help="Reference of the document that generated this purchase order request; a sale order or an internal procurement request."
168 'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64,
169 help="Reference of the sale 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."),
170 '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."),
171 'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
172 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
173 'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
174 states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
175 help="Put an address if you want to deliver directly from the supplier to the customer. " \
176 "Otherwise, keep empty to deliver to your own company."
178 'warehouse_id': fields.many2one('stock.warehouse', 'Destination Warehouse'),
179 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]} ),
180 '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."),
181 'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", readonly=True, required=True),
182 '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),
183 'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
184 'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
185 'notes': fields.text('Terms and Conditions'),
186 'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
187 '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."),
188 'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
189 'shipped_rate': fields.function(_shipped_rate, string='Received', type='float'),
190 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
191 'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
192 '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,
193 readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
194 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" \
195 "Based on generated invoice: create a draft invoice you can validate later.\n" \
196 "Bases on incoming shipments: let you create an invoice when receptions are validated."
198 '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.",
200 'purchase.order.line': (_get_order, ['date_planned'], 10),
203 'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
205 'purchase.order.line': (_get_order, None, 10),
206 }, multi="sums", help="The amount without tax"),
207 'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
209 'purchase.order.line': (_get_order, None, 10),
210 }, multi="sums", help="The tax amount"),
211 'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Total',
213 'purchase.order.line': (_get_order, None, 10),
214 }, multi="sums",help="The total amount"),
215 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
216 'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
217 'create_uid': fields.many2one('res.users', 'Responsible'),
218 'company_id': fields.many2one('res.company','Company',required=True,select=1, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
219 'journal_id': fields.many2one('account.journal', 'Journal'),
222 'date_order': fields.date.context_today,
224 'name': lambda obj, cr, uid, context: '/',
226 'invoice_method': 'order',
228 '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,
229 'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
230 'journal_id': _get_journal,
233 ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
235 _name = "purchase.order"
236 _inherit = ['mail.thread', 'ir.needaction_mixin']
237 _description = "Purchase Order"
240 def create(self, cr, uid, vals, context=None):
241 if vals.get('name','/')=='/':
242 vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
243 order = super(purchase_order, self).create(cr, uid, vals, context=context)
245 self.create_send_note(cr, uid, [order], context=context)
248 def unlink(self, cr, uid, ids, context=None):
249 purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
251 for s in purchase_orders:
252 if s['state'] in ['draft','cancel']:
253 unlink_ids.append(s['id'])
255 raise osv.except_osv(_('Invalid Action!'), _('In order to delete a purchase order, you must cancel it first.'))
257 # automatically sending subflow.delete upon deletion
258 wf_service = netsvc.LocalService("workflow")
259 for id in unlink_ids:
260 wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
262 return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
264 def button_dummy(self, cr, uid, ids, context=None):
267 def onchange_pricelist(self, cr, uid, ids, pricelist_id, context=None):
270 return {'value': {'currency_id': self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context).currency_id.id}}
272 def onchange_dest_address_id(self, cr, uid, ids, address_id):
275 address = self.pool.get('res.partner')
276 values = {'warehouse_id': False}
277 supplier = address.browse(cr, uid, address_id)
279 location_id = supplier.property_stock_customer.id
280 values.update({'location_id': location_id})
281 return {'value':values}
283 def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
286 warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id)
287 return {'value':{'location_id': warehouse.lot_input_id.id, 'dest_address_id': False}}
289 def onchange_partner_id(self, cr, uid, ids, partner_id):
290 partner = self.pool.get('res.partner')
292 return {'value':{'fiscal_position': False}}
293 supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
294 supplier = partner.browse(cr, uid, partner_id)
295 pricelist = supplier.property_product_pricelist_purchase.id
296 fiscal_position = supplier.property_account_position and supplier.property_account_position.id or False
297 return {'value':{'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
299 def invoice_open(self, cr, uid, ids, context=None):
300 mod_obj = self.pool.get('ir.model.data')
301 act_obj = self.pool.get('ir.actions.act_window')
303 result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree2')
304 id = result and result[1] or False
305 result = act_obj.read(cr, uid, [id], context=context)[0]
307 for po in self.browse(cr, uid, ids, context=context):
308 inv_ids+= [invoice.id for invoice in po.invoice_ids]
310 raise osv.except_osv(_('Error!'), _('Please create Invoices.'))
311 #choose the view_mode accordingly
313 result['domain'] = "[('id','in',["+','.join(map(str, inv_ids))+"])]"
315 res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
316 result['views'] = [(res and res[1] or False, 'form')]
317 result['res_id'] = inv_ids and inv_ids[0] or False
320 def view_invoice(self, cr, uid, ids, context=None):
322 This function returns an action that display existing invoices of given sale order ids. It can either be a in a list or in a form view, if there is only one invoice to show.
324 mod_obj = self.pool.get('ir.model.data')
325 wizard_obj = self.pool.get('purchase.order.line_invoice')
326 #compute the number of invoices to display
328 for po in self.browse(cr, uid, ids, context=context):
329 if po.invoice_method == 'manual':
330 if not po.invoice_ids:
331 context.update({'active_ids' : [line.id for line in po.order_line]})
332 wizard_obj.makeInvoices(cr, uid, [], context=context)
334 for po in self.browse(cr, uid, ids, context=context):
335 inv_ids+= [invoice.id for invoice in po.invoice_ids]
336 res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
337 res_id = res and res[1] or False
340 'name': _('Supplier Invoices'),
344 'res_model': 'account.invoice',
345 'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
346 'type': 'ir.actions.act_window',
349 'res_id': inv_ids and inv_ids[0] or False,
352 def view_picking(self, cr, uid, ids, context=None):
354 This function returns an action that display existing pîcking orders of given purchase order ids.
356 mod_obj = self.pool.get('ir.model.data')
358 for po in self.browse(cr, uid, ids, context=context):
359 pick_ids += [picking.id for picking in po.picking_ids]
361 action_model, action_id = tuple(mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree4'))
362 action = self.pool.get(action_model).read(cr, uid, action_id, context=context)
363 ctx = eval(action['context'])
365 'search_default_purchase_id': ids[0]
367 if pick_ids and len(pick_ids) == 1:
368 form_view_ids = [view_id for view_id, view in action['views'] if view == 'form']
369 view_id = form_view_ids and form_view_ids[0] or False
374 'res_id': pick_ids[0]
382 def wkf_approve_order(self, cr, uid, ids, context=None):
383 self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
386 def wkf_send_rfq(self, cr, uid, ids, context=None):
388 This function opens a window to compose an email, with the edi purchase template message loaded by default
390 ir_model_data = self.pool.get('ir.model.data')
392 template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
396 compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
398 compose_form_id = False
401 'default_model': 'purchase.order',
402 'default_res_id': ids[0],
403 'default_use_template': bool(template_id),
404 'default_template_id': template_id,
405 'default_composition_mode': 'comment',
408 'type': 'ir.actions.act_window',
411 'res_model': 'mail.compose.message',
412 'views': [(compose_form_id, 'form')],
413 'view_id': compose_form_id,
418 def print_quotation(self, cr, uid, ids, context=None):
420 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
422 assert len(ids) == 1, 'This option should only be used for a single id at a time'
423 wf_service = netsvc.LocalService("workflow")
424 wf_service.trg_validate(uid, 'purchase.order', ids[0], 'send_rfq', cr)
426 'model': 'purchase.order',
428 'form': self.read(cr, uid, ids[0], context=context),
430 return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True}
432 #TODO: implement messages system
433 def wkf_confirm_order(self, cr, uid, ids, context=None):
435 for po in self.browse(cr, uid, ids, context=context):
436 if not po.order_line:
437 raise osv.except_osv(_('Error!'),_('You cannot confirm a purchase order without any purchase order line.'))
438 for line in po.order_line:
439 if line.state=='draft':
442 self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
444 self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
445 self.confirm_send_note(cr, uid, ids, context)
448 def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
449 """Collects require data from purchase order line that is used to create invoice line
450 for that purchase order line
451 :param account_id: Expense account of the product of PO line if any.
452 :param browse_record order_line: Purchase order line browse record
453 :return: Value for fields of invoice lines.
457 'name': order_line.name,
458 'account_id': account_id,
459 'price_unit': order_line.price_unit or 0.0,
460 'quantity': order_line.product_qty,
461 'product_id': order_line.product_id.id or False,
462 'uos_id': order_line.product_uom.id or False,
463 'invoice_line_tax_id': [(6, 0, [x.id for x in order_line.taxes_id])],
464 'account_analytic_id': order_line.account_analytic_id.id or False,
467 def action_cancel_draft(self, cr, uid, ids, context=None):
470 self.write(cr, uid, ids, {'state':'draft','shipped':0})
471 wf_service = netsvc.LocalService("workflow")
473 # Deleting the existing instance of workflow for PO
474 wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
475 wf_service.trg_create(uid, 'purchase.order', p_id, cr)
476 self.draft_send_note(cr, uid, ids, context=context)
479 def action_invoice_create(self, cr, uid, ids, context=None):
480 """Generates invoice for given ids of purchase orders and links that invoice ID to purchase order.
481 :param ids: list of ids of purchase orders.
482 :return: ID of created invoice.
487 journal_obj = self.pool.get('account.journal')
488 inv_obj = self.pool.get('account.invoice')
489 inv_line_obj = self.pool.get('account.invoice.line')
490 fiscal_obj = self.pool.get('account.fiscal.position')
491 property_obj = self.pool.get('ir.property')
493 for order in self.browse(cr, uid, ids, context=context):
494 pay_acc_id = order.partner_id.property_account_payable.id
495 journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', order.company_id.id)], limit=1)
497 raise osv.except_osv(_('Error!'),
498 _('Define purchase journal for this company: "%s" (id:%d).') % (order.company_id.name, order.company_id.id))
500 # generate invoice line correspond to PO line and link that to created invoice (inv_id) and PO line
502 for po_line in order.order_line:
503 if po_line.product_id:
504 acc_id = po_line.product_id.product_tmpl_id.property_account_expense.id
506 acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
508 raise osv.except_osv(_('Error!'), _('Define expense account for this company: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
510 acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category').id
511 fpos = order.fiscal_position or False
512 acc_id = fiscal_obj.map_account(cr, uid, fpos, acc_id)
514 inv_line_data = self._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
515 inv_line_id = inv_line_obj.create(cr, uid, inv_line_data, context=context)
516 inv_lines.append(inv_line_id)
518 po_line.write({'invoiced':True, 'invoice_lines': [(4, inv_line_id)]}, context=context)
520 # get invoice data and create invoice
522 'name': order.partner_ref or order.name,
523 'reference': order.partner_ref or order.name,
524 'account_id': pay_acc_id,
525 'type': 'in_invoice',
526 'partner_id': order.partner_id.id,
527 'currency_id': order.pricelist_id.currency_id.id,
528 'journal_id': len(journal_ids) and journal_ids[0] or False,
529 'invoice_line': [(6, 0, inv_lines)],
530 'origin': order.name,
531 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
532 'payment_term': order.partner_id.property_payment_term and order.partner_id.property_payment_term.id or False,
533 'company_id': order.company_id.id,
535 inv_id = inv_obj.create(cr, uid, inv_data, context=context)
537 # compute the invoice
538 inv_obj.button_compute(cr, uid, [inv_id], context=context, set_total=True)
540 # Link this new invoice to related purchase order
541 order.write({'invoice_ids': [(4, inv_id)]}, context=context)
544 self.invoice_send_note(cr, uid, ids, res, context)
547 def invoice_done(self, cr, uid, ids, context=None):
548 self.write(cr, uid, ids, {'state':'approved'}, context=context)
549 self.invoice_done_send_note(cr, uid, ids, context=context)
552 def has_stockable_product(self, cr, uid, ids, *args):
553 for order in self.browse(cr, uid, ids):
554 for order_line in order.order_line:
555 if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
559 def action_cancel(self, cr, uid, ids, context=None):
560 wf_service = netsvc.LocalService("workflow")
561 for purchase in self.browse(cr, uid, ids, context=context):
562 for pick in purchase.picking_ids:
563 if pick.state not in ('draft','cancel'):
564 raise osv.except_osv(
565 _('Unable to cancel this purchase order.'),
566 _('First cancel all receptions related to this purchase order.'))
567 for pick in purchase.picking_ids:
568 wf_service.trg_validate(uid, 'stock.picking', pick.id, 'button_cancel', cr)
569 for inv in purchase.invoice_ids:
570 if inv and inv.state not in ('cancel','draft'):
571 raise osv.except_osv(
572 _('Unable to cancel this purchase order.'),
573 _('You must first cancel all receptions related to this purchase order.'))
575 wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
576 self.write(cr,uid,ids,{'state':'cancel'})
578 for (id, name) in self.name_get(cr, uid, ids):
579 wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
580 self.cancel_send_note(cr, uid, ids, context)
583 def _prepare_order_picking(self, cr, uid, order, context=None):
585 'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
586 'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
587 'date': order.date_order,
588 'partner_id': order.dest_address_id.id or order.partner_id.id,
589 'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
591 'partner_id': order.dest_address_id.id or order.partner_id.id,
592 'purchase_id': order.id,
593 'company_id': order.company_id.id,
597 def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, context=None):
599 'name': order.name + ': ' + (order_line.name or ''),
600 'product_id': order_line.product_id.id,
601 'product_qty': order_line.product_qty,
602 'product_uos_qty': order_line.product_qty,
603 'product_uom': order_line.product_uom.id,
604 'product_uos': order_line.product_uom.id,
605 'date': order_line.date_planned,
606 'date_expected': order_line.date_planned,
607 'location_id': order.partner_id.property_stock_supplier.id,
608 'location_dest_id': order.location_id.id,
609 'picking_id': picking_id,
610 'partner_id': order.dest_address_id.id or order.partner_id.id,
611 'move_dest_id': order_line.move_dest_id.id,
614 'purchase_line_id': order_line.id,
615 'company_id': order.company_id.id,
616 'price_unit': order_line.price_unit
619 def _create_pickings(self, cr, uid, order, order_lines, picking_id=False, context=None):
620 """Creates pickings and appropriate stock moves for given order lines, then
621 confirms the moves, makes them available, and confirms the picking.
623 If ``picking_id`` is provided, the stock moves will be added to it, otherwise
624 a standard outgoing picking will be created to wrap the stock moves, as returned
625 by :meth:`~._prepare_order_picking`.
627 Modules that wish to customize the procurements or partition the stock moves over
628 multiple stock pickings may override this method and call ``super()`` with
629 different subsets of ``order_lines`` and/or preset ``picking_id`` values.
631 :param browse_record order: purchase order to which the order lines belong
632 :param list(browse_record) order_lines: purchase order line records for which picking
633 and moves should be created.
634 :param int picking_id: optional ID of a stock picking to which the created stock moves
635 will be added. A new picking will be created if omitted.
636 :return: list of IDs of pickings used/created for the given order lines (usually just one)
639 picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
641 stock_move = self.pool.get('stock.move')
642 wf_service = netsvc.LocalService("workflow")
643 for order_line in order_lines:
644 if not order_line.product_id:
646 if order_line.product_id.type in ('product', 'consu'):
647 move = stock_move.create(cr, uid, self._prepare_order_line_move(cr, uid, order, order_line, picking_id, context=context))
648 if order_line.move_dest_id:
649 order_line.move_dest_id.write({'location_id': order.location_id.id})
650 todo_moves.append(move)
651 stock_move.action_confirm(cr, uid, todo_moves)
652 stock_move.force_assign(cr, uid, todo_moves)
653 wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
656 def action_picking_create(self, cr, uid, ids, context=None):
658 for order in self.browse(cr, uid, ids):
659 picking_ids.extend(self._create_pickings(cr, uid, order, order.order_line, None, context=context))
661 # Must return one unique picking ID: the one to connect in the subflow of the purchase order.
662 # In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
663 # one that should trigger the advancement of the purchase workflow.
664 # By default we will consider the first one as most important, but this behavior can be overridden.
666 self.shipment_send_note(cr, uid, ids, picking_ids[0], context=context)
667 return picking_ids[0] if picking_ids else False
669 def picking_done(self, cr, uid, ids, context=None):
670 self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
671 self.shipment_done_send_note(cr, uid, ids, context=context)
674 def copy(self, cr, uid, id, default=None, context=None):
683 'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
685 return super(purchase_order, self).copy(cr, uid, id, default, context)
687 def do_merge(self, cr, uid, ids, context=None):
689 To merge similar type of purchase orders.
690 Orders will only be merged if:
691 * Purchase Orders are in draft
692 * Purchase Orders belong to the same partner
693 * Purchase Orders are have same stock location, same pricelist
694 Lines will only be merged if:
695 * Order lines are exactly the same except for the quantity and unit
697 @param self: The object pointer.
698 @param cr: A database cursor
699 @param uid: ID of the user currently logged in
700 @param ids: the ID or list of IDs
701 @param context: A standard dictionary
703 @return: new purchase order id
706 #TOFIX: merged order line should be unlink
707 wf_service = netsvc.LocalService("workflow")
708 def make_key(br, fields):
711 field_val = getattr(br, field)
712 if field in ('product_id', 'move_dest_id', 'account_analytic_id'):
715 if isinstance(field_val, browse_record):
716 field_val = field_val.id
717 elif isinstance(field_val, browse_null):
719 elif isinstance(field_val, list):
720 field_val = ((6, 0, tuple([v.id for v in field_val])),)
721 list_key.append((field, field_val))
723 return tuple(list_key)
725 # Compute what the new orders should contain
729 for porder in [order for order in self.browse(cr, uid, ids, context=context) if order.state == 'draft']:
730 order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id'))
731 new_order = new_orders.setdefault(order_key, ({}, []))
732 new_order[1].append(porder.id)
733 order_infos = new_order[0]
736 'origin': porder.origin,
737 'date_order': porder.date_order,
738 'partner_id': porder.partner_id.id,
739 'dest_address_id': porder.dest_address_id.id,
740 'warehouse_id': porder.warehouse_id.id,
741 'location_id': porder.location_id.id,
742 'pricelist_id': porder.pricelist_id.id,
745 'notes': '%s' % (porder.notes or '',),
746 'fiscal_position': porder.fiscal_position and porder.fiscal_position.id or False,
749 if porder.date_order < order_infos['date_order']:
750 order_infos['date_order'] = porder.date_order
752 order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
754 order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
756 for order_line in porder.order_line:
757 line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'product_id', 'move_dest_id', 'account_analytic_id'))
758 o_line = order_infos['order_line'].setdefault(line_key, {})
760 # merge the line with an existing line
761 o_line['product_qty'] += order_line.product_qty * order_line.product_uom.factor / o_line['uom_factor']
763 # append a new "standalone" line
764 for field in ('product_qty', 'product_uom'):
765 field_val = getattr(order_line, field)
766 if isinstance(field_val, browse_record):
767 field_val = field_val.id
768 o_line[field] = field_val
769 o_line['uom_factor'] = order_line.product_uom and order_line.product_uom.factor or 1.0
775 for order_key, (order_data, old_ids) in new_orders.iteritems():
776 # skip merges with only one order
778 allorders += (old_ids or [])
781 # cleanup order line data
782 for key, value in order_data['order_line'].iteritems():
783 del value['uom_factor']
784 value.update(dict(key))
785 order_data['order_line'] = [(0, 0, value) for value in order_data['order_line'].itervalues()]
787 # create the new order
788 neworder_id = self.create(cr, uid, order_data)
789 orders_info.update({neworder_id: old_ids})
790 allorders.append(neworder_id)
792 # make triggers pointing to the old orders point to the new order
793 for old_id in old_ids:
794 wf_service.trg_redirect(uid, 'purchase.order', old_id, neworder_id, cr)
795 wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
798 # --------------------------------------
799 # OpenChatter methods and notifications
800 # --------------------------------------
802 def needaction_domain_get(self, cr, uid, ids, context=None):
803 return [('state', '=', 'draft')]
805 def create_send_note(self, cr, uid, ids, context=None):
806 return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
808 def confirm_send_note(self, cr, uid, ids, context=None):
809 for obj in self.browse(cr, uid, ids, context=context):
810 self.message_post(cr, uid, [obj.id], body=_("Quotation for <em>%s</em> <b>converted</b> to a Purchase Order of %s %s.") % (obj.partner_id.name, obj.amount_total, obj.pricelist_id.currency_id.symbol), context=context)
812 def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
813 for order in self.browse(cr, uid, ids, context=context):
814 for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
815 # convert datetime field to a datetime, using server format, then
816 # convert it to the user TZ and re-render it with %Z to add the timezone
817 picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
818 picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
819 self.message_post(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
821 def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
822 for order in self.browse(cr, uid, ids, context=context):
823 for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
824 self.message_post(cr, uid, [order.id], body=_("Draft Invoice of %s %s is <b>waiting for validation</b>.") % (invoice.amount_total, invoice.currency_id.symbol), context=context)
826 def shipment_done_send_note(self, cr, uid, ids, context=None):
827 self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
829 def invoice_done_send_note(self, cr, uid, ids, context=None):
830 self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
832 def draft_send_note(self, cr, uid, ids, context=None):
833 return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
835 def cancel_send_note(self, cr, uid, ids, context=None):
836 for obj in self.browse(cr, uid, ids, context=context):
837 self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
841 class purchase_order_line(osv.osv):
842 def _amount_line(self, cr, uid, ids, prop, arg, context=None):
844 cur_obj=self.pool.get('res.currency')
845 tax_obj = self.pool.get('account.tax')
846 for line in self.browse(cr, uid, ids, context=context):
847 taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, line.order_id.partner_id)
848 cur = line.order_id.pricelist_id.currency_id
849 res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
853 'name': fields.text('Description', required=True),
854 'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
855 'date_planned': fields.date('Scheduled Date', required=True, select=True),
856 'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
857 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
858 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
859 'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
860 'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
861 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
862 'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Account')),
863 'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
864 'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
865 'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
866 'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
867 help=' * The \'Draft\' status is set automatically when purchase order in draft status. \
868 \n* The \'Confirmed\' status is set automatically as confirm when purchase order in confirm status. \
869 \n* The \'Done\' status is set automatically when purchase order is set as done. \
870 \n* The \'Cancelled\' status is set automatically when user cancel purchase order.'),
871 'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
872 'invoiced': fields.boolean('Invoiced', readonly=True),
873 'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
874 'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
878 'product_qty': lambda *a: 1.0,
879 'state': lambda *args: 'draft',
880 'invoiced': lambda *a: 0,
882 _table = 'purchase_order_line'
883 _name = 'purchase.order.line'
884 _description = 'Purchase Order Line'
886 def copy_data(self, cr, uid, id, default=None, context=None):
889 default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
890 return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
892 def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
893 partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
894 name=False, price_unit=False, context=None):
896 onchange handler of product_uom.
899 return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
900 return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
901 partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
902 name=name, price_unit=price_unit, context=context)
904 def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
905 """Return the datetime value to use as Schedule Date (``date_planned``) for
906 PO Lines that correspond to the given product.supplierinfo,
907 when ordered at `date_order_str`.
909 :param browse_record | False supplier_info: product.supplierinfo, used to
910 determine delivery delay (if False, default delay = 0)
911 :param str date_order_str: date of order, as a string in
912 DEFAULT_SERVER_DATE_FORMAT
914 :return: desired Schedule Date for the PO line
916 supplier_delay = int(supplier_info.delay) if supplier_info else 0
917 return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
919 def _check_product_uom_group(self, cr, uid, context=None):
920 group_uom = self.pool.get('ir.model.data').get_object(cr, uid, 'product', 'group_uom')
921 res = [user for user in group_uom.users if user.id == uid]
922 return len(res) and True or False
925 def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
926 partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
927 name=False, price_unit=False, context=None):
929 onchange handler of product_id.
934 res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
938 product_product = self.pool.get('product.product')
939 product_uom = self.pool.get('product.uom')
940 res_partner = self.pool.get('res.partner')
941 product_supplierinfo = self.pool.get('product.supplierinfo')
942 product_pricelist = self.pool.get('product.pricelist')
943 account_fiscal_position = self.pool.get('account.fiscal.position')
944 account_tax = self.pool.get('account.tax')
946 # - check for the presence of partner_id and pricelist_id
948 # raise osv.except_osv(_('No Partner!'), _('Select a partner in purchase order to choose a product.'))
949 #if not pricelist_id:
950 # raise osv.except_osv(_('No Pricelist !'), _('Select a price list in the purchase order form before choosing a product.'))
952 # - determine name and notes based on product in partner lang.
953 context_partner = context.copy()
955 lang = res_partner.browse(cr, uid, partner_id).lang
956 context_partner.update( {'lang': lang, 'partner_id': partner_id} )
957 product = product_product.browse(cr, uid, product_id, context=context_partner)
959 if product.description_purchase:
960 name += '\n' + product.description_purchase
961 res['value'].update({'name': name})
963 # - set a domain on product_uom
964 res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
966 # - check that uom and product uom belong to the same category
967 product_uom_po_id = product.uom_po_id.id
969 uom_id = product_uom_po_id
971 if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
972 if self._check_product_uom_group(cr, uid, context=context):
973 res['warning'] = {'title': _('Warning!'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')}
974 uom_id = product_uom_po_id
976 res['value'].update({'product_uom': uom_id})
978 # - determine product_qty and date_planned based on seller info
980 date_order = fields.date.context_today(self,cr,uid,context=context)
984 for supplier in product.seller_ids:
985 if partner_id and (supplier.name.id == partner_id):
986 supplierinfo = supplier
987 if supplierinfo.product_uom.id != uom_id:
988 res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
989 min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
990 if qty < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
991 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)}
994 dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
996 res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty})
998 # - determine price_unit and taxes_id
1000 price = product_pricelist.price_get(cr, uid, [pricelist_id],
1001 product.id, qty or 1.0, partner_id or False, {'uom': uom_id, 'date': date_order})[pricelist_id]
1003 price = product.standard_price
1005 taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
1006 fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
1007 taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
1008 res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
1012 product_id_change = onchange_product_id
1013 product_uom_change = onchange_product_uom
1015 def action_confirm(self, cr, uid, ids, context=None):
1016 self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
1019 purchase_order_line()
1021 class procurement_order(osv.osv):
1022 _inherit = 'procurement.order'
1024 'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
1027 def action_po_assign(self, cr, uid, ids, context=None):
1028 """ This is action which call from workflow to assign purchase order to procurements
1031 res = self.make_po(cr, uid, ids, context=context)
1033 return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1035 def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
1036 """Create the purchase order from the procurement, using
1037 the provided field values, after adding the given purchase
1038 order line in the purchase order.
1040 :params procurement: the procurement object generating the purchase order
1041 :params dict po_vals: field values for the new purchase order (the
1042 ``order_line`` field will be overwritten with one
1043 single line, as passed in ``line_vals``).
1044 :params dict line_vals: field values of the single purchase order line that
1045 the purchase order will contain.
1046 :return: id of the newly created purchase order
1049 po_vals.update({'order_line': [(0,0,line_vals)]})
1050 return self.pool.get('purchase.order').create(cr, uid, po_vals, context=context)
1052 def _get_purchase_schedule_date(self, cr, uid, procurement, company, context=None):
1053 """Return the datetime value to use as Schedule Date (``date_planned``) for the
1054 Purchase Order Lines created to satisfy the given procurement.
1056 :param browse_record procurement: the procurement for which a PO will be created.
1057 :param browse_report company: the company to which the new PO will belong to.
1059 :return: the desired Schedule Date for the PO lines
1061 procurement_date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
1062 schedule_date = (procurement_date_planned - relativedelta(days=company.po_lead))
1063 return schedule_date
1065 def _get_purchase_order_date(self, cr, uid, procurement, company, schedule_date, context=None):
1066 """Return the datetime value to use as Order Date (``date_order``) for the
1067 Purchase Order created to satisfy the given procurement.
1069 :param browse_record procurement: the procurement for which a PO will be created.
1070 :param browse_report company: the company to which the new PO will belong to.
1071 :param datetime schedule_date: desired Scheduled Date for the Purchase Order lines.
1073 :return: the desired Order Date for the PO
1075 seller_delay = int(procurement.product_id.seller_delay)
1076 return schedule_date - relativedelta(days=seller_delay)
1078 def make_po(self, cr, uid, ids, context=None):
1079 """ Make purchase order from procurement
1080 @return: New created Purchase Orders procurement wise
1085 company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1086 partner_obj = self.pool.get('res.partner')
1087 uom_obj = self.pool.get('product.uom')
1088 pricelist_obj = self.pool.get('product.pricelist')
1089 prod_obj = self.pool.get('product.product')
1090 acc_pos_obj = self.pool.get('account.fiscal.position')
1091 seq_obj = self.pool.get('ir.sequence')
1092 warehouse_obj = self.pool.get('stock.warehouse')
1093 for procurement in self.browse(cr, uid, ids, context=context):
1094 res_id = procurement.move_id.id
1095 partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
1096 seller_qty = procurement.product_id.seller_qty
1097 partner_id = partner.id
1098 address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1099 pricelist_id = partner.property_product_pricelist_purchase.id
1100 warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context)
1101 uom_id = procurement.product_id.uom_po_id.id
1103 qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1105 qty = max(qty,seller_qty)
1107 price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
1109 schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
1110 purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
1112 #Passing partner_id to context for purchase order line integrity of Line name
1113 new_context = context.copy()
1114 new_context.update({'lang': partner.lang, 'partner_id': partner_id})
1116 product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context)
1117 taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
1118 taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
1120 name = product.partner_ref
1121 if product.description_purchase:
1122 name += '\n'+ product.description_purchase
1126 'product_id': procurement.product_id.id,
1127 'product_uom': uom_id,
1128 'price_unit': price or 0.0,
1129 'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1130 'move_dest_id': res_id,
1131 'taxes_id': [(6,0,taxes)],
1133 name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
1136 'origin': procurement.origin,
1137 'partner_id': partner_id,
1138 'location_id': procurement.location_id.id,
1139 'warehouse_id': warehouse_id and warehouse_id[0] or False,
1140 'pricelist_id': pricelist_id,
1141 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1142 'company_id': procurement.company_id.id,
1143 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
1145 res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=new_context)
1146 self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
1147 self.purchase_order_create_note(cr, uid, ids, context=context)
1150 def _product_virtual_get(self, cr, uid, order_point):
1151 procurement = order_point.procurement_id
1152 if procurement and procurement.state != 'exception' and procurement.purchase_id and procurement.purchase_id.state in ('draft', 'confirmed'):
1154 return super(procurement_order, self)._product_virtual_get(cr, uid, order_point)
1156 def purchase_order_create_note(self, cr, uid, ids, context=None):
1157 for procurement in self.browse(cr, uid, ids, context=context):
1158 body = _("Draft Purchase Order created")
1159 self.message_post(cr, uid, [procurement.id], body=body, context=context)
1163 class mail_mail(osv.osv):
1165 _inherit = 'mail.mail'
1167 def _postprocess_sent_message(self, cr, uid, mail, context=None):
1168 if mail.model == 'purchase.order':
1169 wf_service = netsvc.LocalService("workflow")
1170 wf_service.trg_validate(uid, 'purchase.order', mail.res_id, 'send_rfq', cr)
1171 return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context)
1175 class product_template(osv.osv):
1176 _name = 'product.template'
1177 _inherit = 'product.template'
1179 'purchase_ok': fields.boolean('Can be Purchased', help="Specify if the product can be selected in a purchase order line."),
1187 class mail_compose_message(osv.osv):
1188 _inherit = 'mail.compose.message'
1189 def send_mail(self, cr, uid, ids, context=None):
1190 context = context or {}
1191 if context.get('default_model') == 'purchase.order' and context.get('default_res_id'):
1192 wf_service = netsvc.LocalService("workflow")
1193 wf_service.trg_validate(uid, 'purchase.order', context['default_res_id'], 'send_rfq', cr)
1194 return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
1196 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: