[IMP] Added message post for 'Invoice Paid.' when the invoice is paid in PO.
[odoo/odoo.git] / addons / purchase / purchase.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 import time
23 from datetime import datetime
24 from dateutil.relativedelta import relativedelta
25 from operator import attrgetter
26
27 from openerp.osv import fields, osv
28 from openerp import pooler
29 from openerp.tools.translate import _
30 import openerp.addons.decimal_precision as dp
31 from openerp.osv.orm import browse_record, browse_null
32 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
33
34 class purchase_order(osv.osv):
35
36     def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
37         res = {}
38         cur_obj=self.pool.get('res.currency')
39         for order in self.browse(cr, uid, ids, context=context):
40             res[order.id] = {
41                 'amount_untaxed': 0.0,
42                 'amount_tax': 0.0,
43                 'amount_total': 0.0,
44             }
45             val = val1 = 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']
54         return res
55
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([]):
59             ids=[ids]
60         for po in self.browse(cr, uid, ids, context=context):
61             if po.order_line:
62                 cr.execute("""update purchase_order_line set
63                         date_planned=%s
64                     where
65                         order_id=%s and
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))
69         return True
70
71     def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
72         res={}
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
82         return res
83
84
85     def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
86         res = {}
87         for purchase in self.browse(cursor, user, ids, context=context):
88             tot = 0.0
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
94             else:
95                 res[purchase.id] = 0.0
96         return res
97
98     def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
99         if not ids: return {}
100         res = {}
101         for id in ids:
102             res[id] = [0.0,0.0]
103         cr.execute('''SELECT
104                 p.purchase_id,sum(m.product_qty), m.state
105             FROM
106                 stock_move m
107             LEFT JOIN
108                 stock_picking p on (p.id=m.picking_id)
109             WHERE
110                 p.purchase_id IN %s GROUP BY m.state, p.purchase_id''',(tuple(ids),))
111         for oid,nbr,state in cr.fetchall():
112             if state=='cancel':
113                 continue
114             if state=='done':
115                 res[oid][0] += nbr or 0.0
116                 res[oid][1] += nbr or 0.0
117             else:
118                 res[oid][1] += nbr or 0.0
119         for r in res:
120             if not res[r][1]:
121                 res[r] = 0.0
122             else:
123                 res[r] = 100.0 * res[r][0] / res[r][1]
124         return res
125
126     def _get_order(self, cr, uid, ids, context=None):
127         result = {}
128         for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
129             result[line.order_id.id] = True
130         return result.keys()
131
132     def _invoiced(self, cursor, user, ids, name, arg, context=None):
133         res = {}
134         for purchase in self.browse(cursor, user, ids, context=context):
135             invoiced = False
136             if purchase.invoiced_rate == 100.00:
137                 invoiced = True
138             res[purchase.id] = invoiced
139         return res
140     
141     def _get_journal(self, cr, uid, context=None):
142         if context is None:
143             context = {}
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)],
149                                                 limit=1)
150         return res and res[0] or False  
151
152     STATE_SELECTION = [
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'),
159         ('done', 'Done'),
160         ('cancel', 'Cancelled')
161     ]
162     _track = {
163         'state': {
164             'purchase.mt_rfq_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirmed',
165             'purchase.mt_rfq_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'approved',
166         },
167     }
168     _columns = {
169         '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."),
170         'origin': fields.char('Source Document', size=64,
171             help="Reference of the document that generated this purchase order request; a sales order or an internal procurement request."
172         ),
173         'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64,
174             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."),
175         '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."),
176         'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
177         'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
178             change_default=True, track_visibility='always'),
179         'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
180             states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
181             help="Put an address if you want to deliver directly from the supplier to the customer. " \
182                 "Otherwise, keep empty to deliver to your own company."
183         ),
184         'warehouse_id': fields.many2one('stock.warehouse', 'Destination Warehouse'),
185         'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]} ),
186         '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."),
187         'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", string="Currency",readonly=True, required=True),
188         '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),
189         'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
190         'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
191         'notes': fields.text('Terms and Conditions'),
192         'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
193         '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."),
194         'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
195         'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
196         'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
197         'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
198         '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,
199             readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
200             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" \
201                 "Based on generated invoice: create a draft invoice you can validate later.\n" \
202                 "Bases on incoming shipments: let you create an invoice when receptions are validated."
203         ),
204         '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.",
205             store = {
206                 'purchase.order.line': (_get_order, ['date_planned'], 10),
207             }
208         ),
209         'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
210             store={
211                 'purchase.order.line': (_get_order, None, 10),
212             }, multi="sums", help="The amount without tax", track_visibility='always'),
213         'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
214             store={
215                 'purchase.order.line': (_get_order, None, 10),
216             }, multi="sums", help="The tax amount"),
217         'amount_total': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Total',
218             store={
219                 'purchase.order.line': (_get_order, None, 10),
220             }, multi="sums",help="The total amount"),
221         'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
222         'payment_term_id': fields.many2one('account.payment.term', 'Payment Term'),
223         'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
224         'create_uid':  fields.many2one('res.users', 'Responsible'),
225         'company_id': fields.many2one('res.company','Company',required=True,select=1, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
226         'journal_id': fields.many2one('account.journal', 'Journal'),
227     }
228     _defaults = {
229         'date_order': fields.date.context_today,
230         'state': 'draft',
231         'name': lambda obj, cr, uid, context: '/',
232         'shipped': 0,
233         'invoice_method': 'order',
234         'invoiced': 0,
235         '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,
236         'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
237         'journal_id': _get_journal,
238     }
239     _sql_constraints = [
240         ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
241     ]
242     _name = "purchase.order"
243     _inherit = ['mail.thread', 'ir.needaction_mixin']
244     _description = "Purchase Order"
245     _order = "name desc"
246
247     def create(self, cr, uid, vals, context=None):
248         if vals.get('name','/')=='/':
249             vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
250         order =  super(purchase_order, self).create(cr, uid, vals, context=context)
251         return order
252
253     def unlink(self, cr, uid, ids, context=None):
254         purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
255         unlink_ids = []
256         for s in purchase_orders:
257             if s['state'] in ['draft','cancel']:
258                 unlink_ids.append(s['id'])
259             else:
260                 raise osv.except_osv(_('Invalid Action!'), _('In order to delete a purchase order, you must cancel it first.'))
261
262         # automatically sending subflow.delete upon deletion
263         self.signal_purchase_cancel(cr, uid, unlink_ids)
264
265         return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
266
267     def button_dummy(self, cr, uid, ids, context=None):
268         return True
269
270     def onchange_pricelist(self, cr, uid, ids, pricelist_id, context=None):
271         if not pricelist_id:
272             return {}
273         return {'value': {'currency_id': self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context).currency_id.id}}
274
275     def onchange_dest_address_id(self, cr, uid, ids, address_id):
276         if not address_id:
277             return {}
278         address = self.pool.get('res.partner')
279         values = {'warehouse_id': False}
280         supplier = address.browse(cr, uid, address_id)
281         if supplier:
282             location_id = supplier.property_stock_customer.id
283             values.update({'location_id': location_id})
284         return {'value':values}
285
286     def onchange_warehouse_id(self, cr, uid, ids, warehouse_id):
287         if not warehouse_id:
288             return {}
289         warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id)
290         return {'value':{'location_id': warehouse.lot_input_id.id, 'dest_address_id': False}}
291
292     def onchange_partner_id(self, cr, uid, ids, partner_id):
293         partner = self.pool.get('res.partner')
294         if not partner_id:
295             return {'value': {
296                 'fiscal_position': False,
297                 'payment_term_id': False,
298                 }}
299         supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
300         supplier = partner.browse(cr, uid, partner_id)
301         return {'value': {
302             'pricelist_id': supplier.property_product_pricelist_purchase.id,
303             'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
304             'payment_term_id': supplier.property_supplier_payment_term.id or False,
305             }}
306
307     def invoice_open(self, cr, uid, ids, context=None):
308         mod_obj = self.pool.get('ir.model.data')
309         act_obj = self.pool.get('ir.actions.act_window')
310
311         result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree2')
312         id = result and result[1] or False
313         result = act_obj.read(cr, uid, [id], context=context)[0]
314         inv_ids = []
315         for po in self.browse(cr, uid, ids, context=context):
316             inv_ids+= [invoice.id for invoice in po.invoice_ids]
317         if not inv_ids:
318             raise osv.except_osv(_('Error!'), _('Please create Invoices.'))
319          #choose the view_mode accordingly
320         if len(inv_ids)>1:
321             result['domain'] = "[('id','in',["+','.join(map(str, inv_ids))+"])]"
322         else:
323             res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
324             result['views'] = [(res and res[1] or False, 'form')]
325             result['res_id'] = inv_ids and inv_ids[0] or False
326         return result
327
328     def view_invoice(self, cr, uid, ids, context=None):
329         '''
330         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.
331         '''
332         mod_obj = self.pool.get('ir.model.data')
333         wizard_obj = self.pool.get('purchase.order.line_invoice')
334         #compute the number of invoices to display
335         inv_ids = []
336         for po in self.browse(cr, uid, ids, context=context):
337             if po.invoice_method == 'manual':
338                 if not po.invoice_ids:
339                     context.update({'active_ids' :  [line.id for line in po.order_line]})
340                     wizard_obj.makeInvoices(cr, uid, [], context=context)
341
342         for po in self.browse(cr, uid, ids, context=context):
343             inv_ids+= [invoice.id for invoice in po.invoice_ids]
344         res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
345         res_id = res and res[1] or False
346
347         return {
348             'name': _('Supplier Invoices'),
349             'view_type': 'form',
350             'view_mode': 'form',
351             'view_id': [res_id],
352             'res_model': 'account.invoice',
353             'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
354             'type': 'ir.actions.act_window',
355             'nodestroy': True,
356             'target': 'current',
357             'res_id': inv_ids and inv_ids[0] or False,
358         }
359
360     def view_picking(self, cr, uid, ids, context=None):
361         '''
362         This function returns an action that display existing pîcking orders of given purchase order ids.
363         '''
364         mod_obj = self.pool.get('ir.model.data')
365         pick_ids = []
366         for po in self.browse(cr, uid, ids, context=context):
367             pick_ids += [picking.id for picking in po.picking_ids]
368
369         action_model, action_id = tuple(mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree4'))
370         action = self.pool.get(action_model).read(cr, uid, action_id, context=context)
371         ctx = eval(action['context'])
372         ctx.update({
373             'search_default_purchase_id': ids[0]
374         })
375         if pick_ids and len(pick_ids) == 1:
376             form_view_ids = [view_id for view_id, view in action['views'] if view == 'form']
377             view_id = form_view_ids and form_view_ids[0] or False
378             action.update({
379                 'views': [],
380                 'view_mode': 'form',
381                 'view_id': view_id,
382                 'res_id': pick_ids[0]
383             })
384
385         action.update({
386             'context': ctx,
387         })
388         return action
389
390     def wkf_approve_order(self, cr, uid, ids, context=None):
391         self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
392         return True
393
394     def print_confirm(self,cr,uid,ids,context=None):
395         print "Confirmed"
396
397     def print_double(self,cr,uid,ids,context=None):
398         print "double Approval"
399
400     def print_router(self,cr,uid,ids,context=None):
401         print "Routed"
402
403     def wkf_send_rfq(self, cr, uid, ids, context=None):
404         '''
405         This function opens a window to compose an email, with the edi purchase template message loaded by default
406         '''
407         ir_model_data = self.pool.get('ir.model.data')
408         try:
409             template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
410         except ValueError:
411             template_id = False
412         try:
413             compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
414         except ValueError:
415             compose_form_id = False 
416         ctx = dict(context)
417         ctx.update({
418             'default_model': 'purchase.order',
419             'default_res_id': ids[0],
420             'default_use_template': bool(template_id),
421             'default_template_id': template_id,
422             'default_composition_mode': 'comment',
423         })
424         return {
425             'type': 'ir.actions.act_window',
426             'view_type': 'form',
427             'view_mode': 'form',
428             'res_model': 'mail.compose.message',
429             'views': [(compose_form_id, 'form')],
430             'view_id': compose_form_id,
431             'target': 'new',
432             'context': ctx,
433         }
434
435     def print_quotation(self, cr, uid, ids, context=None):
436         '''
437         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
438         '''
439         assert len(ids) == 1, 'This option should only be used for a single id at a time'
440         self.signal_send_rfq(cr, uid, ids)
441         datas = {
442                  'model': 'purchase.order',
443                  'ids': ids,
444                  'form': self.read(cr, uid, ids[0], context=context),
445         }
446         return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True}
447
448     #TODO: implement messages system
449     def wkf_confirm_order(self, cr, uid, ids, context=None):
450         todo = []
451         for po in self.browse(cr, uid, ids, context=context):
452             if not po.order_line:
453                 raise osv.except_osv(_('Error!'),_('You cannot confirm a purchase order without any purchase order line.'))
454             for line in po.order_line:
455                 if line.state=='draft':
456                     todo.append(line.id)
457
458         self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
459         for id in ids:
460             self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
461         return True
462
463     def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
464         """Collects require data from purchase order line that is used to create invoice line
465         for that purchase order line
466         :param account_id: Expense account of the product of PO line if any.
467         :param browse_record order_line: Purchase order line browse record
468         :return: Value for fields of invoice lines.
469         :rtype: dict
470         """
471         return {
472             'name': order_line.name,
473             'account_id': account_id,
474             'price_unit': order_line.price_unit or 0.0,
475             'quantity': order_line.product_qty,
476             'product_id': order_line.product_id.id or False,
477             'uos_id': order_line.product_uom.id or False,
478             'invoice_line_tax_id': [(6, 0, [x.id for x in order_line.taxes_id])],
479             'account_analytic_id': order_line.account_analytic_id.id or False,
480         }
481
482     def action_cancel_draft(self, cr, uid, ids, context=None):
483         if not len(ids):
484             return False
485         self.write(cr, uid, ids, {'state':'draft','shipped':0})
486         for p_id in ids:
487             # Deleting the existing instance of workflow for PO
488             self.delete_workflow(cr, uid, [p_id]) # TODO is it necessary to interleave the calls?
489             self.create_workflow(cr, uid, [p_id])
490         return True
491
492     def action_invoice_create(self, cr, uid, ids, context=None):
493         """Generates invoice for given ids of purchase orders and links that invoice ID to purchase order.
494         :param ids: list of ids of purchase orders.
495         :return: ID of created invoice.
496         :rtype: int
497         """
498         res = False
499
500         journal_obj = self.pool.get('account.journal')
501         inv_obj = self.pool.get('account.invoice')
502         inv_line_obj = self.pool.get('account.invoice.line')
503         fiscal_obj = self.pool.get('account.fiscal.position')
504         property_obj = self.pool.get('ir.property')
505
506         for order in self.browse(cr, uid, ids, context=context):
507             pay_acc_id = order.partner_id.property_account_payable.id
508             journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', order.company_id.id)], limit=1)
509             if not journal_ids:
510                 raise osv.except_osv(_('Error!'),
511                     _('Define purchase journal for this company: "%s" (id:%d).') % (order.company_id.name, order.company_id.id))
512
513             # generate invoice line correspond to PO line and link that to created invoice (inv_id) and PO line
514             inv_lines = []
515             for po_line in order.order_line:
516                 if po_line.product_id:
517                     acc_id = po_line.product_id.property_account_expense.id
518                     if not acc_id:
519                         acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
520                     if not acc_id:
521                         raise osv.except_osv(_('Error!'), _('Define expense account for this company: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
522                 else:
523                     acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category').id
524                 fpos = order.fiscal_position or False
525                 acc_id = fiscal_obj.map_account(cr, uid, fpos, acc_id)
526
527                 inv_line_data = self._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
528                 inv_line_id = inv_line_obj.create(cr, uid, inv_line_data, context=context)
529                 inv_lines.append(inv_line_id)
530
531                 po_line.write({'invoiced':True, 'invoice_lines': [(4, inv_line_id)]}, context=context)
532
533             # get invoice data and create invoice
534             inv_data = {
535                 'name': order.partner_ref or order.name,
536                 'reference': order.partner_ref or order.name,
537                 'account_id': pay_acc_id,
538                 'type': 'in_invoice',
539                 'partner_id': order.partner_id.id,
540                 'currency_id': order.pricelist_id.currency_id.id,
541                 'journal_id': len(journal_ids) and journal_ids[0] or False,
542                 'invoice_line': [(6, 0, inv_lines)],
543                 'origin': order.name,
544                 'fiscal_position': order.fiscal_position.id or False,
545                 'payment_term': order.payment_term_id.id or False,
546                 'company_id': order.company_id.id,
547             }
548             inv_id = inv_obj.create(cr, uid, inv_data, context=context)
549
550             # compute the invoice
551             inv_obj.button_compute(cr, uid, [inv_id], context=context, set_total=True)
552
553             # Link this new invoice to related purchase order
554             order.write({'invoice_ids': [(4, inv_id)]}, context=context)
555             res = inv_id
556         return res
557
558     def invoice_done(self, cr, uid, ids, context=None):
559         self.write(cr, uid, ids, {'state':'approved'}, context=context)
560         self.message_post(cr, uid, ids, body=_("Invoice <b>Paid.</b>"), context=context)
561         return True
562
563     def has_stockable_product(self, cr, uid, ids, *args):
564         for order in self.browse(cr, uid, ids):
565             for order_line in order.order_line:
566                 if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
567                     return True
568         return False
569
570     def action_cancel(self, cr, uid, ids, context=None):
571         for purchase in self.browse(cr, uid, ids, context=context):
572             for pick in purchase.picking_ids:
573                 if pick.state not in ('draft','cancel'):
574                     raise osv.except_osv(
575                         _('Unable to cancel this purchase order.'),
576                         _('First cancel all receptions related to this purchase order.'))
577             self.pool.get('stock.picking') \
578                 .signal_button_cancel(cr, uid, map(attrgetter('id'), purchase.picking_ids))
579             for inv in purchase.invoice_ids:
580                 if inv and inv.state not in ('cancel','draft'):
581                     raise osv.except_osv(
582                         _('Unable to cancel this purchase order.'),
583                         _('You must first cancel all receptions related to this purchase order.'))
584             self.pool.get('account.invoice') \
585                 .signal_invoice_cancel(cr, uid, map(attrgetter('id'), purchase.invoice_ids))
586         self.write(cr,uid,ids,{'state':'cancel'})
587
588         self.signal_purchase_cancel(cr, uid, ids)
589         return True
590
591     def _prepare_order_picking(self, cr, uid, order, context=None):
592         return {
593             'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
594             'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
595             'date': order.date_order,
596             'partner_id': order.dest_address_id.id or order.partner_id.id,
597             'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
598             'type': 'in',
599             'partner_id': order.dest_address_id.id or order.partner_id.id,
600             'purchase_id': order.id,
601             'company_id': order.company_id.id,
602             'move_lines' : [],
603         }
604
605     def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, context=None):
606         return {
607             'name': order_line.name or '',
608             'product_id': order_line.product_id.id,
609             'product_qty': order_line.product_qty,
610             'product_uos_qty': order_line.product_qty,
611             'product_uom': order_line.product_uom.id,
612             'product_uos': order_line.product_uom.id,
613             'date': order_line.date_planned,
614             'date_expected': order_line.date_planned,
615             'location_id': order.partner_id.property_stock_supplier.id,
616             'location_dest_id': order.location_id.id,
617             'picking_id': picking_id,
618             'partner_id': order.dest_address_id.id or order.partner_id.id,
619             'move_dest_id': order_line.move_dest_id.id,
620             'state': 'draft',
621             'type':'in',
622             'purchase_line_id': order_line.id,
623             'company_id': order.company_id.id,
624             'price_unit': order_line.price_unit
625         }
626
627     def _create_pickings(self, cr, uid, order, order_lines, picking_id=False, context=None):
628         """Creates pickings and appropriate stock moves for given order lines, then
629         confirms the moves, makes them available, and confirms the picking.
630
631         If ``picking_id`` is provided, the stock moves will be added to it, otherwise
632         a standard outgoing picking will be created to wrap the stock moves, as returned
633         by :meth:`~._prepare_order_picking`.
634
635         Modules that wish to customize the procurements or partition the stock moves over
636         multiple stock pickings may override this method and call ``super()`` with
637         different subsets of ``order_lines`` and/or preset ``picking_id`` values.
638
639         :param browse_record order: purchase order to which the order lines belong
640         :param list(browse_record) order_lines: purchase order line records for which picking
641                                                 and moves should be created.
642         :param int picking_id: optional ID of a stock picking to which the created stock moves
643                                will be added. A new picking will be created if omitted.
644         :return: list of IDs of pickings used/created for the given order lines (usually just one)
645         """
646         stock_picking = self.pool.get('stock.picking')
647         if not picking_id:
648             picking_id = stock_picking.create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
649         todo_moves = []
650         stock_move = self.pool.get('stock.move')
651         for order_line in order_lines:
652             if not order_line.product_id:
653                 continue
654             if order_line.product_id.type in ('product', 'consu'):
655                 move = stock_move.create(cr, uid, self._prepare_order_line_move(cr, uid, order, order_line, picking_id, context=context))
656                 if order_line.move_dest_id:
657                     order_line.move_dest_id.write({'location_id': order.location_id.id})
658                 todo_moves.append(move)
659         stock_move.action_confirm(cr, uid, todo_moves)
660         stock_move.force_assign(cr, uid, todo_moves)
661         stock_picking.signal_button_confirm(cr, uid, [picking_id])
662         return [picking_id]
663
664     def action_picking_create(self, cr, uid, ids, context=None):
665         picking_ids = []
666         for order in self.browse(cr, uid, ids):
667             picking_ids.extend(self._create_pickings(cr, uid, order, order.order_line, None, context=context))
668
669         # Must return one unique picking ID: the one to connect in the subflow of the purchase order.
670         # In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
671         # one that should trigger the advancement of the purchase workflow.
672         # By default we will consider the first one as most important, but this behavior can be overridden.
673         return picking_ids[0] if picking_ids else False
674
675     def picking_done(self, cr, uid, ids, context=None):
676         self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
677         self.message_post(cr, uid, ids, body=_("Products <b>Received.</b>"), context=context)
678         return True
679
680     def copy(self, cr, uid, id, default=None, context=None):
681         if not default:
682             default = {}
683         default.update({
684             'state':'draft',
685             'shipped':False,
686             'invoiced':False,
687             'invoice_ids': [],
688             'picking_ids': [],
689             'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
690         })
691         return super(purchase_order, self).copy(cr, uid, id, default, context)
692
693     def do_merge(self, cr, uid, ids, context=None):
694         """
695         To merge similar type of purchase orders.
696         Orders will only be merged if:
697         * Purchase Orders are in draft
698         * Purchase Orders belong to the same partner
699         * Purchase Orders are have same stock location, same pricelist
700         Lines will only be merged if:
701         * Order lines are exactly the same except for the quantity and unit
702
703          @param self: The object pointer.
704          @param cr: A database cursor
705          @param uid: ID of the user currently logged in
706          @param ids: the ID or list of IDs
707          @param context: A standard dictionary
708
709          @return: new purchase order id
710
711         """
712         #TOFIX: merged order line should be unlink
713         def make_key(br, fields):
714             list_key = []
715             for field in fields:
716                 field_val = getattr(br, field)
717                 if field in ('product_id', 'move_dest_id', 'account_analytic_id'):
718                     if not field_val:
719                         field_val = False
720                 if isinstance(field_val, browse_record):
721                     field_val = field_val.id
722                 elif isinstance(field_val, browse_null):
723                     field_val = False
724                 elif isinstance(field_val, list):
725                     field_val = ((6, 0, tuple([v.id for v in field_val])),)
726                 list_key.append((field, field_val))
727             list_key.sort()
728             return tuple(list_key)
729
730         # Compute what the new orders should contain
731
732         new_orders = {}
733
734         for porder in [order for order in self.browse(cr, uid, ids, context=context) if order.state == 'draft']:
735             order_key = make_key(porder, ('partner_id', 'location_id', 'pricelist_id'))
736             new_order = new_orders.setdefault(order_key, ({}, []))
737             new_order[1].append(porder.id)
738             order_infos = new_order[0]
739             if not order_infos:
740                 order_infos.update({
741                     'origin': porder.origin,
742                     'date_order': porder.date_order,
743                     'partner_id': porder.partner_id.id,
744                     'dest_address_id': porder.dest_address_id.id,
745                     'warehouse_id': porder.warehouse_id.id,
746                     'location_id': porder.location_id.id,
747                     'pricelist_id': porder.pricelist_id.id,
748                     'state': 'draft',
749                     'order_line': {},
750                     'notes': '%s' % (porder.notes or '',),
751                     'fiscal_position': porder.fiscal_position and porder.fiscal_position.id or False,
752                 })
753             else:
754                 if porder.date_order < order_infos['date_order']:
755                     order_infos['date_order'] = porder.date_order
756                 if porder.notes:
757                     order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
758                 if porder.origin:
759                     order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
760
761             for order_line in porder.order_line:
762                 line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'product_id', 'move_dest_id', 'account_analytic_id'))
763                 o_line = order_infos['order_line'].setdefault(line_key, {})
764                 if o_line:
765                     # merge the line with an existing line
766                     o_line['product_qty'] += order_line.product_qty * order_line.product_uom.factor / o_line['uom_factor']
767                 else:
768                     # append a new "standalone" line
769                     for field in ('product_qty', 'product_uom'):
770                         field_val = getattr(order_line, field)
771                         if isinstance(field_val, browse_record):
772                             field_val = field_val.id
773                         o_line[field] = field_val
774                     o_line['uom_factor'] = order_line.product_uom and order_line.product_uom.factor or 1.0
775
776
777
778         allorders = []
779         orders_info = {}
780         for order_key, (order_data, old_ids) in new_orders.iteritems():
781             # skip merges with only one order
782             if len(old_ids) < 2:
783                 allorders += (old_ids or [])
784                 continue
785
786             # cleanup order line data
787             for key, value in order_data['order_line'].iteritems():
788                 del value['uom_factor']
789                 value.update(dict(key))
790             order_data['order_line'] = [(0, 0, value) for value in order_data['order_line'].itervalues()]
791
792             # create the new order
793             neworder_id = self.create(cr, uid, order_data)
794             orders_info.update({neworder_id: old_ids})
795             allorders.append(neworder_id)
796
797             # make triggers pointing to the old orders point to the new order
798             for old_id in old_ids:
799                 self.redirect_workflow(cr, uid, [(old_id, neworder_id)])
800                 self.signal_purchase_cancel(cr, uid, [old_id]) # TODO Is it necessary to interleave the calls?
801         return orders_info
802
803
804 class purchase_order_line(osv.osv):
805     def _amount_line(self, cr, uid, ids, prop, arg, context=None):
806         res = {}
807         cur_obj=self.pool.get('res.currency')
808         tax_obj = self.pool.get('account.tax')
809         for line in self.browse(cr, uid, ids, context=context):
810             taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, line.order_id.partner_id)
811             cur = line.order_id.pricelist_id.currency_id
812             res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
813         return res
814
815     _columns = {
816         'name': fields.text('Description', required=True),
817         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
818         'date_planned': fields.date('Scheduled Date', required=True, select=True),
819         'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
820         'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
821         'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
822         'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
823         'move_dest_id': fields.many2one('stock.move', 'Reservation Destination', ondelete='set null'),
824         'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
825         'price_subtotal': fields.function(_amount_line, string='Subtotal', digits_compute= dp.get_precision('Account')),
826         'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
827         'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
828         'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
829         'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
830                                   help=' * The \'Draft\' status is set automatically when purchase order in draft status. \
831                                        \n* The \'Confirmed\' status is set automatically as confirm when purchase order in confirm status. \
832                                        \n* The \'Done\' status is set automatically when purchase order is set as done. \
833                                        \n* The \'Cancelled\' status is set automatically when user cancel purchase order.'),
834         'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
835         'invoiced': fields.boolean('Invoiced', readonly=True),
836         'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
837         'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date")
838
839     }
840     _defaults = {
841         'product_qty': lambda *a: 1.0,
842         'state': lambda *args: 'draft',
843         'invoiced': lambda *a: 0,
844     }
845     _table = 'purchase_order_line'
846     _name = 'purchase.order.line'
847     _description = 'Purchase Order Line'
848
849     def copy_data(self, cr, uid, id, default=None, context=None):
850         if not default:
851             default = {}
852         default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
853         return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
854
855     def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
856             partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
857             name=False, price_unit=False, context=None):
858         """
859         onchange handler of product_uom.
860         """
861         if not uom_id:
862             return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
863         return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
864             partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
865             name=name, price_unit=price_unit, context=context)
866
867     def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
868         """Return the datetime value to use as Schedule Date (``date_planned``) for
869            PO Lines that correspond to the given product.supplierinfo,
870            when ordered at `date_order_str`.
871
872            :param browse_record | False supplier_info: product.supplierinfo, used to
873                determine delivery delay (if False, default delay = 0)
874            :param str date_order_str: date of order, as a string in
875                DEFAULT_SERVER_DATE_FORMAT
876            :rtype: datetime
877            :return: desired Schedule Date for the PO line
878         """
879         supplier_delay = int(supplier_info.delay) if supplier_info else 0
880         return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
881
882     def _check_product_uom_group(self, cr, uid, context=None):
883         group_uom = self.pool.get('ir.model.data').get_object(cr, uid, 'product', 'group_uom')
884         res = [user for user in group_uom.users if user.id == uid]
885         return len(res) and True or False
886
887
888     def onchange_product_id(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
889             partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
890             name=False, price_unit=False, context=None):
891         """
892         onchange handler of product_id.
893         """
894         if context is None:
895             context = {}
896
897         res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
898         if not product_id:
899             return res
900
901         product_product = self.pool.get('product.product')
902         product_uom = self.pool.get('product.uom')
903         res_partner = self.pool.get('res.partner')
904         product_supplierinfo = self.pool.get('product.supplierinfo')
905         product_pricelist = self.pool.get('product.pricelist')
906         account_fiscal_position = self.pool.get('account.fiscal.position')
907         account_tax = self.pool.get('account.tax')
908
909         # - check for the presence of partner_id and pricelist_id
910         #if not partner_id:
911         #    raise osv.except_osv(_('No Partner!'), _('Select a partner in purchase order to choose a product.'))
912         #if not pricelist_id:
913         #    raise osv.except_osv(_('No Pricelist !'), _('Select a price list in the purchase order form before choosing a product.'))
914
915         # - determine name and notes based on product in partner lang.
916         context_partner = context.copy()
917         if partner_id:
918             lang = res_partner.browse(cr, uid, partner_id).lang
919             context_partner.update( {'lang': lang, 'partner_id': partner_id} )
920         product = product_product.browse(cr, uid, product_id, context=context_partner)
921         name = product.name
922         if product.description_purchase:
923             name += '\n' + product.description_purchase
924         res['value'].update({'name': name})
925
926         # - set a domain on product_uom
927         res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
928
929         # - check that uom and product uom belong to the same category
930         product_uom_po_id = product.uom_po_id.id
931         if not uom_id:
932             uom_id = product_uom_po_id
933
934         if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
935             if self._check_product_uom_group(cr, uid, context=context):
936                 res['warning'] = {'title': _('Warning!'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')}
937             uom_id = product_uom_po_id
938
939         res['value'].update({'product_uom': uom_id})
940
941         # - determine product_qty and date_planned based on seller info
942         if not date_order:
943             date_order = fields.date.context_today(self,cr,uid,context=context)
944
945
946         supplierinfo = False
947         for supplier in product.seller_ids:
948             if partner_id and (supplier.name.id == partner_id):
949                 supplierinfo = supplier
950                 if supplierinfo.product_uom.id != uom_id:
951                     res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
952                 min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
953                 if (qty or 0.0) < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
954                     if qty:
955                         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)}
956                     qty = min_qty
957         dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
958         qty = qty or 1.0
959         res['value'].update({'date_planned': date_planned or dt})
960         if qty:
961             res['value'].update({'product_qty': qty})
962
963         # - determine price_unit and taxes_id
964         if pricelist_id:
965             price = product_pricelist.price_get(cr, uid, [pricelist_id],
966                     product.id, qty or 1.0, partner_id or False, {'uom': uom_id, 'date': date_order})[pricelist_id]
967         else:
968             price = product.standard_price
969
970         taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
971         fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
972         taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
973         res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
974
975         return res
976
977     product_id_change = onchange_product_id
978     product_uom_change = onchange_product_uom
979
980     def action_confirm(self, cr, uid, ids, context=None):
981         self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
982         return True
983
984 purchase_order_line()
985
986 class procurement_order(osv.osv):
987     _inherit = 'procurement.order'
988     _columns = {
989         'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
990     }
991
992     def check_buy(self, cr, uid, ids, context=None):
993         ''' return True if the supply method of the mto product is 'buy'
994         '''
995         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
996         for procurement in self.browse(cr, uid, ids, context=context):
997             if procurement.product_id.supply_method <> 'buy':
998                 return False
999         return True
1000
1001     def check_supplier_info(self, cr, uid, ids, context=None):
1002         partner_obj = self.pool.get('res.partner')
1003         user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
1004         for procurement in self.browse(cr, uid, ids, context=context):
1005             if not procurement.product_id.seller_ids:
1006                 message = _('No supplier defined for this product !')
1007                 self.message_post(cr, uid, [procurement.id], body=message)
1008                 cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
1009                 return False
1010             partner = procurement.product_id.seller_id #Taken Main Supplier of Product of Procurement.
1011
1012             if not partner:
1013                 message = _('No default supplier defined for this product')
1014                 self.message_post(cr, uid, [procurement.id], body=message)
1015                 cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
1016                 return False
1017             if user.company_id and user.company_id.partner_id:
1018                 if partner.id == user.company_id.partner_id.id:
1019                     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))
1020
1021             address_id = partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']
1022             if not address_id:
1023                 message = _('No address defined for the supplier')
1024                 self.message_post(cr, uid, [procurement.id], body=message)
1025                 cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
1026                 return False
1027         return True
1028
1029
1030     def action_po_assign(self, cr, uid, ids, context=None):
1031         """ This is action which call from workflow to assign purchase order to procurements
1032         @return: True
1033         """
1034         res = self.make_po(cr, uid, ids, context=context)
1035         res = res.values()
1036         return len(res) and res[0] or 0 #TO CHECK: why workflow is generated error if return not integer value
1037
1038     def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
1039         """Create the purchase order from the procurement, using
1040            the provided field values, after adding the given purchase
1041            order line in the purchase order.
1042
1043            :params procurement: the procurement object generating the purchase order
1044            :params dict po_vals: field values for the new purchase order (the
1045                                  ``order_line`` field will be overwritten with one
1046                                  single line, as passed in ``line_vals``).
1047            :params dict line_vals: field values of the single purchase order line that
1048                                    the purchase order will contain.
1049            :return: id of the newly created purchase order
1050            :rtype: int
1051         """
1052         po_vals.update({'order_line': [(0,0,line_vals)]})
1053         return self.pool.get('purchase.order').create(cr, uid, po_vals, context=context)
1054
1055     def _get_purchase_schedule_date(self, cr, uid, procurement, company, context=None):
1056         """Return the datetime value to use as Schedule Date (``date_planned``) for the
1057            Purchase Order Lines created to satisfy the given procurement.
1058
1059            :param browse_record procurement: the procurement for which a PO will be created.
1060            :param browse_report company: the company to which the new PO will belong to.
1061            :rtype: datetime
1062            :return: the desired Schedule Date for the PO lines
1063         """
1064         procurement_date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
1065         schedule_date = (procurement_date_planned - relativedelta(days=company.po_lead))
1066         return schedule_date
1067
1068     def _get_purchase_order_date(self, cr, uid, procurement, company, schedule_date, context=None):
1069         """Return the datetime value to use as Order Date (``date_order``) for the
1070            Purchase Order created to satisfy the given procurement.
1071
1072            :param browse_record procurement: the procurement for which a PO will be created.
1073            :param browse_report company: the company to which the new PO will belong to.
1074            :param datetime schedule_date: desired Scheduled Date for the Purchase Order lines.
1075            :rtype: datetime
1076            :return: the desired Order Date for the PO
1077         """
1078         seller_delay = int(procurement.product_id.seller_delay)
1079         return schedule_date - relativedelta(days=seller_delay)
1080
1081     def make_po(self, cr, uid, ids, context=None):
1082         """ Make purchase order from procurement
1083         @return: New created Purchase Orders procurement wise
1084         """
1085         res = {}
1086         if context is None:
1087             context = {}
1088         company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id
1089         partner_obj = self.pool.get('res.partner')
1090         uom_obj = self.pool.get('product.uom')
1091         pricelist_obj = self.pool.get('product.pricelist')
1092         prod_obj = self.pool.get('product.product')
1093         acc_pos_obj = self.pool.get('account.fiscal.position')
1094         seq_obj = self.pool.get('ir.sequence')
1095         warehouse_obj = self.pool.get('stock.warehouse')
1096         for procurement in self.browse(cr, uid, ids, context=context):
1097             res_id = procurement.move_id.id
1098             partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
1099             seller_qty = procurement.product_id.seller_qty
1100             partner_id = partner.id
1101             address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
1102             pricelist_id = partner.property_product_pricelist_purchase.id
1103             warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context)
1104             uom_id = procurement.product_id.uom_po_id.id
1105
1106             qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
1107             if seller_qty:
1108                 qty = max(qty,seller_qty)
1109
1110             price = pricelist_obj.price_get(cr, uid, [pricelist_id], procurement.product_id.id, qty, partner_id, {'uom': uom_id})[pricelist_id]
1111
1112             schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
1113             purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
1114
1115             #Passing partner_id to context for purchase order line integrity of Line name
1116             new_context = context.copy()
1117             new_context.update({'lang': partner.lang, 'partner_id': partner_id})
1118
1119             product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context)
1120             taxes_ids = procurement.product_id.supplier_taxes_id
1121             taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
1122
1123             name = product.partner_ref
1124             if product.description_purchase:
1125                 name += '\n'+ product.description_purchase
1126             line_vals = {
1127                 'name': name,
1128                 'product_qty': qty,
1129                 'product_id': procurement.product_id.id,
1130                 'product_uom': uom_id,
1131                 'price_unit': price or 0.0,
1132                 'date_planned': schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1133                 'move_dest_id': res_id,
1134                 'taxes_id': [(6,0,taxes)],
1135             }
1136             name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
1137             po_vals = {
1138                 'name': name,
1139                 'origin': procurement.origin,
1140                 'partner_id': partner_id,
1141                 'location_id': procurement.location_id.id,
1142                 'warehouse_id': warehouse_id and warehouse_id[0] or False,
1143                 'pricelist_id': pricelist_id,
1144                 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
1145                 'company_id': procurement.company_id.id,
1146                 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False,
1147                 'payment_term_id': partner.property_supplier_payment_term.id or False,
1148             }
1149             res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=new_context)
1150             self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
1151         self.message_post(cr, uid, ids, body=_("Draft Purchase Order created"), context=context)
1152         return res
1153
1154     def _product_virtual_get(self, cr, uid, order_point):
1155         procurement = order_point.procurement_id
1156         if procurement and procurement.state != 'exception' and procurement.purchase_id and procurement.purchase_id.state in ('draft', 'confirmed'):
1157             return None
1158         return super(procurement_order, self)._product_virtual_get(cr, uid, order_point)
1159
1160
1161 class mail_mail(osv.Model):
1162     _name = 'mail.mail'
1163     _inherit = 'mail.mail'
1164
1165     def _postprocess_sent_message(self, cr, uid, mail, context=None):
1166         if mail.model == 'purchase.order':
1167             self.pool.get('purchase.order').signal_send_rfq(cr, uid, [mail.res_id])
1168         return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context)
1169
1170
1171 class product_template(osv.Model):
1172     _name = 'product.template'
1173     _inherit = 'product.template'
1174     _columns = {
1175         'purchase_ok': fields.boolean('Can be Purchased', help="Specify if the product can be selected in a purchase order line."),
1176     }
1177     _defaults = {
1178         'purchase_ok': 1,
1179     }
1180
1181
1182 class mail_compose_message(osv.Model):
1183     _inherit = 'mail.compose.message'
1184
1185     def send_mail(self, cr, uid, ids, context=None):
1186         context = context or {}
1187         if context.get('default_model') == 'purchase.order' and context.get('default_res_id'):
1188             context = dict(context, mail_post_autofollow=True)
1189             self.pool.get('purchase.order').signal_send_rfq(cr, uid, [context['default_res_id']])
1190         return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
1191
1192 class account_invoice(osv.Model):
1193     _inherit = 'account.invoice'
1194     
1195     def invoice_validate(self, cr, uid, ids, context=None):
1196         po_ids = self.pool.get('purchase.order').search(cr,uid,[('invoice_ids','in',ids)],context)
1197         res = super(account_invoice, self).invoice_validate(cr, uid, ids, context=None)
1198         self.pool.get('purchase.order').message_post(cr, uid, po_ids, body=_("Invoice <b>Received.</b>"), context=context)
1199         return res 
1200
1201 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: