##############################################################################
import time
+import pytz
+from openerp import SUPERUSER_ID
from datetime import datetime
from dateutil.relativedelta import relativedelta
-from osv import osv, fields
-import netsvc
-import pooler
-from tools.translate import _
-import decimal_precision as dp
-from osv.orm import browse_record, browse_null
-from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
+from openerp.osv import fields, osv
+from openerp import netsvc
+from openerp import pooler
+from openerp.tools.translate import _
+import openerp.addons.decimal_precision as dp
+from openerp.osv.orm import browse_record, browse_null
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP
+from openerp.tools.float_utils import float_compare
class purchase_order(osv.osv):
cur = order.pricelist_id.currency_id
for line in order.order_line:
val1 += line.price_subtotal
- for c in self.pool.get('account.tax').compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id.id, order.partner_id)['taxes']:
+ 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']:
val += c.get('amount', 0.0)
res[order.id]['amount_tax']=cur_obj.round(cr, uid, cur, val)
res[order.id]['amount_untaxed']=cur_obj.round(cr, uid, cur, val1)
def _invoiced(self, cursor, user, ids, name, arg, context=None):
res = {}
for purchase in self.browse(cursor, user, ids, context=context):
- invoiced = False
- if purchase.invoiced_rate == 100.00:
- invoiced = True
- res[purchase.id] = invoiced
+ res[purchase.id] = all(line.invoiced for line in purchase.order_line)
return res
def _get_journal(self, cr, uid, context=None):
('done', 'Done'),
('cancel', 'Cancelled')
]
-
+ _track = {
+ 'state': {
+ 'purchase.mt_rfq_confirmed': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'confirmed',
+ 'purchase.mt_rfq_approved': lambda self, cr, uid, obj, ctx=None: obj['state'] == 'approved',
+ },
+ }
_columns = {
'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."),
'origin': fields.char('Source Document', size=64,
- help="Reference of the document that generated this purchase order request; a sale order or an internal procurement request."
+ help="Reference of the document that generated this purchase order request; a sales order or an internal procurement request."
),
'partner_ref': fields.char('Supplier Reference', states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, size=64,
- 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."),
+ 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."),
'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."),
'date_approve':fields.date('Date Approved', readonly=1, select=True, help="Date on which purchase order has been approved"),
- 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]}, change_default=True),
+ 'partner_id':fields.many2one('res.partner', 'Supplier', required=True, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
+ change_default=True, track_visibility='always'),
'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},
help="Put an address if you want to deliver directly from the supplier to the customer. " \
'warehouse_id': fields.many2one('stock.warehouse', 'Destination Warehouse'),
'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]} ),
'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."),
- 'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", readonly=True, required=True),
+ 'currency_id': fields.related('pricelist_id', 'currency_id', type="many2one", relation="res.currency", string="Currency",readonly=True, required=True),
'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),
'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines', states={'approved':[('readonly',True)],'done':[('readonly',True)]}),
'validator' : fields.many2one('res.users', 'Validated by', readonly=True),
'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
'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."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
- 'shipped_rate': fields.function(_shipped_rate, string='Received', type='float'),
- 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
+ 'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
+ 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been validated"),
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
- '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,states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]},
+ '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,
+ readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
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" \
"Based on generated invoice: create a draft invoice you can validate later.\n" \
"Bases on incoming shipments: let you create an invoice when receptions are validated."
'amount_untaxed': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Untaxed Amount',
store={
'purchase.order.line': (_get_order, None, 10),
- }, multi="sums", help="The amount without tax"),
+ }, multi="sums", help="The amount without tax", track_visibility='always'),
'amount_tax': fields.function(_amount_all, digits_compute= dp.get_precision('Account'), string='Taxes',
store={
'purchase.order.line': (_get_order, None, 10),
'purchase.order.line': (_get_order, None, 10),
}, multi="sums",help="The total amount"),
'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'),
+ 'payment_term_id': fields.many2one('account.payment.term', 'Payment Term'),
'product_id': fields.related('order_line','product_id', type='many2one', relation='product.product', string='Product'),
'create_uid': fields.many2one('res.users', 'Responsible'),
'company_id': fields.many2one('res.company','Company',required=True,select=1, states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)]}),
_name = "purchase.order"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_description = "Purchase Order"
- _order = "name desc"
+ _order = 'date_order desc, id desc'
def create(self, cr, uid, vals, context=None):
- if ('name' not in vals) or (vals.get('name')=='/'):
- seq_obj_name = self._name
- vals['name'] = self.pool.get('ir.sequence').get(cr, uid, seq_obj_name)
+ if vals.get('name','/')=='/':
+ vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
order = super(purchase_order, self).create(cr, uid, vals, context=context)
- if order:
- self.create_send_note(cr, uid, [order], context=context)
return order
def unlink(self, cr, uid, ids, context=None):
def onchange_partner_id(self, cr, uid, ids, partner_id):
partner = self.pool.get('res.partner')
if not partner_id:
- return {'value':{'fiscal_position': False}}
+ return {'value': {
+ 'fiscal_position': False,
+ 'payment_term_id': False,
+ }}
supplier_address = partner.address_get(cr, uid, [partner_id], ['default'])
supplier = partner.browse(cr, uid, partner_id)
- pricelist = supplier.property_product_pricelist_purchase.id
- fiscal_position = supplier.property_account_position and supplier.property_account_position.id or False
- return {'value':{'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
+ return {'value': {
+ 'pricelist_id': supplier.property_product_pricelist_purchase.id,
+ 'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
+ 'payment_term_id': supplier.property_supplier_payment_term.id or False,
+ }}
+
+ def invoice_open(self, cr, uid, ids, context=None):
+ mod_obj = self.pool.get('ir.model.data')
+ act_obj = self.pool.get('ir.actions.act_window')
+
+ result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree2')
+ id = result and result[1] or False
+ result = act_obj.read(cr, uid, [id], context=context)[0]
+ inv_ids = []
+ for po in self.browse(cr, uid, ids, context=context):
+ inv_ids+= [invoice.id for invoice in po.invoice_ids]
+ if not inv_ids:
+ raise osv.except_osv(_('Error!'), _('Please create Invoices.'))
+ #choose the view_mode accordingly
+ if len(inv_ids)>1:
+ result['domain'] = "[('id','in',["+','.join(map(str, inv_ids))+"])]"
+ else:
+ res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
+ result['views'] = [(res and res[1] or False, 'form')]
+ result['res_id'] = inv_ids and inv_ids[0] or False
+ return result
def view_invoice(self, cr, uid, ids, context=None):
'''
- 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.
+ 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.
'''
mod_obj = self.pool.get('ir.model.data')
wizard_obj = self.pool.get('purchase.order.line_invoice')
self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
return True
+ def print_confirm(self,cr,uid,ids,context=None):
+ print "Confirmed"
+
+ def print_double(self,cr,uid,ids,context=None):
+ print "double Approval"
+
+ def print_router(self,cr,uid,ids,context=None):
+ print "Routed"
+
def wkf_send_rfq(self, cr, uid, ids, context=None):
'''
This function opens a window to compose an email, with the edi purchase template message loaded by default
'''
- mod_obj = self.pool.get('ir.model.data')
- template = mod_obj.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')
- template_id = template and template[1] or False
- res = mod_obj.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')
- res_id = res and res[1] or False
+ ir_model_data = self.pool.get('ir.model.data')
+ try:
+ template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
+ except ValueError:
+ template_id = False
+ try:
+ compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
+ except ValueError:
+ compose_form_id = False
ctx = dict(context)
ctx.update({
'default_model': 'purchase.order',
'default_res_id': ids[0],
- 'default_use_template': True,
+ 'default_use_template': bool(template_id),
'default_template_id': template_id,
- })
+ 'default_composition_mode': 'comment',
+ })
return {
+ 'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
- 'views': [(res_id, 'form')],
- 'view_id': res_id,
- 'type': 'ir.actions.act_window',
+ 'views': [(compose_form_id, 'form')],
+ 'view_id': compose_form_id,
'target': 'new',
'context': ctx,
- 'nodestroy': True,
}
+ def print_quotation(self, cr, uid, ids, context=None):
+ '''
+ 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
+ '''
+ assert len(ids) == 1, 'This option should only be used for a single id at a time'
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_validate(uid, 'purchase.order', ids[0], 'send_rfq', cr)
+ datas = {
+ 'model': 'purchase.order',
+ 'ids': ids,
+ 'form': self.read(cr, uid, ids[0], context=context),
+ }
+ return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True}
+
#TODO: implement messages system
def wkf_confirm_order(self, cr, uid, ids, context=None):
todo = []
self.pool.get('purchase.order.line').action_confirm(cr, uid, todo, context)
for id in ids:
self.write(cr, uid, [id], {'state' : 'confirmed', 'validator' : uid})
- self.confirm_send_note(cr, uid, ids, context)
return True
+ def _choose_account_from_po_line(self, cr, uid, po_line, context=None):
+ fiscal_obj = self.pool.get('account.fiscal.position')
+ property_obj = self.pool.get('ir.property')
+ if po_line.product_id:
+ acc_id = po_line.product_id.property_account_expense.id
+ if not acc_id:
+ acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
+ if not acc_id:
+ raise osv.except_osv(_('Error!'), _('Define expense account for this product: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
+ else:
+ acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category', context=context).id
+ fpos = po_line.order_id.fiscal_position or False
+ return fiscal_obj.map_account(cr, uid, fpos, acc_id)
+
def _prepare_inv_line(self, cr, uid, account_id, order_line, context=None):
"""Collects require data from purchase order line that is used to create invoice line
for that purchase order line
if not len(ids):
return False
self.write(cr, uid, ids, {'state':'draft','shipped':0})
+ for purchase in self.browse(cr, uid, ids, context=context):
+ self.pool['purchase.order.line'].write(cr, uid, [l.id for l in purchase.order_line], {'state': 'draft'})
wf_service = netsvc.LocalService("workflow")
for p_id in ids:
# Deleting the existing instance of workflow for PO
wf_service.trg_delete(uid, 'purchase.order', p_id, cr)
wf_service.trg_create(uid, 'purchase.order', p_id, cr)
- self.draft_send_note(cr, uid, ids, context=context)
return True
def action_invoice_create(self, cr, uid, ids, context=None):
:return: ID of created invoice.
:rtype: int
"""
- res = False
-
+ if context is None:
+ context = {}
journal_obj = self.pool.get('account.journal')
inv_obj = self.pool.get('account.invoice')
inv_line_obj = self.pool.get('account.invoice.line')
- fiscal_obj = self.pool.get('account.fiscal.position')
- property_obj = self.pool.get('ir.property')
+ res = False
+ uid_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
for order in self.browse(cr, uid, ids, context=context):
+ context.pop('force_company', None)
+ if order.company_id.id != uid_company_id:
+ #if the company of the document is different than the current user company, force the company in the context
+ #then re-do a browse to read the property fields for the good company.
+ context['force_company'] = order.company_id.id
+ order = self.browse(cr, uid, order.id, context=context)
pay_acc_id = order.partner_id.property_account_payable.id
- journal_ids = journal_obj.search(cr, uid, [('type', '=','purchase'),('company_id', '=', order.company_id.id)], limit=1)
+ journal_ids = journal_obj.search(cr, uid, [('type', '=', 'purchase'), ('company_id', '=', order.company_id.id)], limit=1)
if not journal_ids:
raise osv.except_osv(_('Error!'),
_('Define purchase journal for this company: "%s" (id:%d).') % (order.company_id.name, order.company_id.id))
# generate invoice line correspond to PO line and link that to created invoice (inv_id) and PO line
inv_lines = []
for po_line in order.order_line:
- if po_line.product_id:
- acc_id = po_line.product_id.product_tmpl_id.property_account_expense.id
- if not acc_id:
- acc_id = po_line.product_id.categ_id.property_account_expense_categ.id
- if not acc_id:
- raise osv.except_osv(_('Error!'), _('Define expense account for this company: "%s" (id:%d).') % (po_line.product_id.name, po_line.product_id.id,))
- else:
- acc_id = property_obj.get(cr, uid, 'property_account_expense_categ', 'product.category').id
- fpos = order.fiscal_position or False
- acc_id = fiscal_obj.map_account(cr, uid, fpos, acc_id)
-
+ acc_id = self._choose_account_from_po_line(cr, uid, po_line, context=context)
inv_line_data = self._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
inv_line_id = inv_line_obj.create(cr, uid, inv_line_data, context=context)
inv_lines.append(inv_line_id)
- po_line.write({'invoiced':True, 'invoice_lines': [(4, inv_line_id)]}, context=context)
+ po_line.write({'invoice_lines': [(4, inv_line_id)]}, context=context)
# get invoice data and create invoice
inv_data = {
'journal_id': len(journal_ids) and journal_ids[0] or False,
'invoice_line': [(6, 0, inv_lines)],
'origin': order.name,
- 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id,
- 'payment_term': order.partner_id.property_payment_term and order.partner_id.property_payment_term.id or False,
+ 'fiscal_position': order.fiscal_position.id or False,
+ 'payment_term': order.payment_term_id.id or False,
'company_id': order.company_id.id,
}
inv_id = inv_obj.create(cr, uid, inv_data, context=context)
# Link this new invoice to related purchase order
order.write({'invoice_ids': [(4, inv_id)]}, context=context)
res = inv_id
- if res:
- self.invoice_send_note(cr, uid, ids, res, context)
return res
- def action_view_invoice(self, cr, uid, ids, context=None):
- '''
- This function returns an action that display existing invoices of given purchase order ids. It can either be a in a list or in a form view, if there is only one invoice to show.
- '''
- mod_obj = self.pool.get('ir.model.data')
- act_obj = self.pool.get('ir.actions.act_window')
- result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree1')
- id = result and result[1] or False
- result = act_obj.read(cr, uid, [id], context=context)[0]
- #compute the number of invoices to display
- inv_ids = []
- for po in self.browse(cr, uid, ids, context=context):
- inv_ids += [invoice.id for invoice in po.invoice_ids]
- #choose the view_mode accordingly
- if len(inv_ids)>1:
- result['domain'] = "[('id','in',["+','.join(map(str, inv_ids))+"])]"
- else:
- res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_form')
- result['views'] = [(res and res[1] or False, 'form')]
- result['res_id'] = inv_ids and inv_ids[0] or False
- return result
def invoice_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state':'approved'}, context=context)
- self.invoice_done_send_note(cr, uid, ids, context=context)
return True
def has_stockable_product(self, cr, uid, ids, *args):
for order in self.browse(cr, uid, ids):
for order_line in order.order_line:
- if order_line.product_id and order_line.product_id.product_tmpl_id.type in ('product', 'consu'):
+ if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
return True
return False
_('You must first cancel all receptions related to this purchase order.'))
if inv:
wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
+ self.pool['purchase.order.line'].write(cr, uid, [l.id for l in purchase.order_line],
+ {'state': 'cancel'})
self.write(cr,uid,ids,{'state':'cancel'})
for (id, name) in self.name_get(cr, uid, ids):
wf_service.trg_validate(uid, 'purchase.order', id, 'purchase_cancel', cr)
- self.cancel_send_note(cr, uid, ids, context)
return True
+ def date_to_datetime(self, cr, uid, userdate, context=None):
+ """ Convert date values expressed in user's timezone to
+ server-side UTC timestamp, assuming a default arbitrary
+ time of 12:00 AM - because a time is needed.
+
+ :param str userdate: date string in in user time zone
+ :return: UTC datetime string for server-side use
+ """
+ # TODO: move to fields.datetime in server after 7.0
+ user_date = datetime.strptime(userdate, DEFAULT_SERVER_DATE_FORMAT)
+ if context and context.get('tz'):
+ tz_name = context['tz']
+ else:
+ tz_name = self.pool.get('res.users').read(cr, SUPERUSER_ID, uid, ['tz'])['tz']
+ if tz_name:
+ utc = pytz.timezone('UTC')
+ context_tz = pytz.timezone(tz_name)
+ user_datetime = user_date + relativedelta(hours=12.0)
+ local_timestamp = context_tz.localize(user_datetime, is_dst=False)
+ user_datetime = local_timestamp.astimezone(utc)
+ return user_datetime.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ return user_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+
def _prepare_order_picking(self, cr, uid, order, context=None):
return {
'name': self.pool.get('ir.sequence').get(cr, uid, 'stock.picking.in'),
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
- 'date': order.date_order,
- 'partner_id': order.dest_address_id.id or order.partner_id.id,
+ 'date': self.date_to_datetime(cr, uid, order.date_order, context),
+ 'partner_id': order.partner_id.id,
'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
'type': 'in',
- 'partner_id': order.dest_address_id.id or order.partner_id.id,
- 'invoice_state': '2binvoiced' if order.invoice_method == 'picking' else 'none',
'purchase_id': order.id,
'company_id': order.company_id.id,
'move_lines' : [],
}
def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, context=None):
+ price_unit = order_line.price_unit
+ if order.currency_id.id != order.company_id.currency_id.id:
+ #we don't round the price_unit, as we may want to store the standard price with more digits than allowed by the currency
+ price_unit = self.pool.get('res.currency').compute(cr, uid, order.currency_id.id, order.company_id.currency_id.id, price_unit, round=False, context=context)
return {
- 'name': order.name + ': ' + (order_line.name or ''),
+ 'name': order_line.name or '',
'product_id': order_line.product_id.id,
'product_qty': order_line.product_qty,
'product_uos_qty': order_line.product_qty,
'product_uom': order_line.product_uom.id,
'product_uos': order_line.product_uom.id,
- 'date': order_line.date_planned,
- 'date_expected': order_line.date_planned,
+ 'date': self.date_to_datetime(cr, uid, order.date_order, context),
+ 'date_expected': self.date_to_datetime(cr, uid, order_line.date_planned, context),
'location_id': order.partner_id.property_stock_supplier.id,
'location_dest_id': order.location_id.id,
'picking_id': picking_id,
'type':'in',
'purchase_line_id': order_line.id,
'company_id': order.company_id.id,
- 'price_unit': order_line.price_unit
+ 'price_unit': price_unit
}
def _create_pickings(self, cr, uid, order, order_lines, picking_id=False, context=None):
continue
if order_line.product_id.type in ('product', 'consu'):
move = stock_move.create(cr, uid, self._prepare_order_line_move(cr, uid, order, order_line, picking_id, context=context))
- if order_line.move_dest_id:
+ if order_line.move_dest_id and order_line.move_dest_id.state != 'done':
order_line.move_dest_id.write({'location_id': order.location_id.id})
todo_moves.append(move)
stock_move.action_confirm(cr, uid, todo_moves)
# In case of multiple (split) pickings, we should return the ID of the critical one, i.e. the
# one that should trigger the advancement of the purchase workflow.
# By default we will consider the first one as most important, but this behavior can be overridden.
- if picking_ids:
- self.shipment_send_note(cr, uid, ids, picking_ids[0], context=context)
return picking_ids[0] if picking_ids else False
def picking_done(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
- self.shipment_done_send_note(cr, uid, ids, context=context)
return True
def copy(self, cr, uid, id, default=None, context=None):
'invoiced':False,
'invoice_ids': [],
'picking_ids': [],
+ 'partner_ref': '',
'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
})
return super(purchase_order, self).copy(cr, uid, id, default, context)
if porder.notes:
order_infos['notes'] = (order_infos['notes'] or '') + ('\n%s' % (porder.notes,))
if porder.origin:
- order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
+ if not porder.origin in order_infos['origin'] and not order_infos['origin'] in porder.origin:
+ order_infos['origin'] = (order_infos['origin'] or '') + ' ' + porder.origin
for order_line in porder.order_line:
line_key = make_key(order_line, ('name', 'date_planned', 'taxes_id', 'price_unit', 'product_id', 'move_dest_id', 'account_analytic_id'))
wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
return orders_info
- # --------------------------------------
- # OpenChatter methods and notifications
- # --------------------------------------
-
- def needaction_domain_get(self, cr, uid, ids, context=None):
- return [('state', '=', 'draft')]
-
- def create_send_note(self, cr, uid, ids, context=None):
- return self.message_post(cr, uid, ids, body=_("Request for quotation <b>created</b>."), context=context)
-
- def confirm_send_note(self, cr, uid, ids, context=None):
- for obj in self.browse(cr, uid, ids, context=context):
- 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)
-
- def shipment_send_note(self, cr, uid, ids, picking_id, context=None):
- for order in self.browse(cr, uid, ids, context=context):
- for picking in (pck for pck in order.picking_ids if pck.id == picking_id):
- # convert datetime field to a datetime, using server format, then
- # convert it to the user TZ and re-render it with %Z to add the timezone
- picking_datetime = fields.DT.datetime.strptime(picking.min_date, DEFAULT_SERVER_DATETIME_FORMAT)
- picking_date_str = fields.datetime.context_timestamp(cr, uid, picking_datetime, context=context).strftime(DATETIME_FORMATS_MAP['%+'] + " (%Z)")
- self.message_post(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, picking_date_str), context=context)
-
- def invoice_send_note(self, cr, uid, ids, invoice_id, context=None):
- for order in self.browse(cr, uid, ids, context=context):
- for invoice in (inv for inv in order.invoice_ids if inv.id == invoice_id):
- 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)
-
- def shipment_done_send_note(self, cr, uid, ids, context=None):
- self.message_post(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
-
- def invoice_done_send_note(self, cr, uid, ids, context=None):
- self.message_post(cr, uid, ids, body=_("Invoice <b>paid</b>."), context=context)
-
- def draft_send_note(self, cr, uid, ids, context=None):
- return self.message_post(cr, uid, ids, body=_("Purchase Order has been set to <b>draft</b>."), context=context)
-
- def cancel_send_note(self, cr, uid, ids, context=None):
- for obj in self.browse(cr, uid, ids, context=context):
- self.message_post(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
-
-purchase_order()
class purchase_order_line(osv.osv):
def _amount_line(self, cr, uid, ids, prop, arg, context=None):
cur_obj=self.pool.get('res.currency')
tax_obj = self.pool.get('account.tax')
for line in self.browse(cr, uid, ids, context=context):
- taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty)
+ taxes = tax_obj.compute_all(cr, uid, line.taxes_id, line.price_unit, line.product_qty, line.product_id, line.order_id.partner_id)
cur = line.order_id.pricelist_id.currency_id
res[line.id] = cur_obj.round(cr, uid, cur, taxes['total'])
return res
+ def _get_uom_id(self, cr, uid, context=None):
+ try:
+ proxy = self.pool.get('ir.model.data')
+ result = proxy.get_object_reference(cr, uid, 'product', 'product_uom_unit')
+ return result[1]
+ except Exception, ex:
+ return False
+
_columns = {
'name': fields.text('Description', required=True),
'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
}
_defaults = {
+ 'product_uom' : _get_uom_id,
'product_qty': lambda *a: 1.0,
'state': lambda *args: 'draft',
'invoiced': lambda *a: 0,
default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
+ def unlink(self, cr, uid, ids, context=None):
+ procurement_ids_to_cancel = []
+ for line in self.browse(cr, uid, ids, context=context):
+ if line.state not in ['draft', 'cancel']:
+ raise osv.except_osv(_('Invalid Action!'), _('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
+ if line.move_dest_id:
+ procurement_ids_to_cancel.extend(procurement.id for procurement in line.move_dest_id.procurements)
+ if procurement_ids_to_cancel:
+ self.pool['procurement.order'].action_cancel(cr, uid, procurement_ids_to_cancel)
+ return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
+
def onchange_product_uom(self, cr, uid, ids, pricelist_id, product_id, qty, uom_id,
partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
name=False, price_unit=False, context=None):
"""
onchange handler of product_uom.
"""
+ if context is None:
+ context = {}
if not uom_id:
return {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
+ context = dict(context, purchase_uom_check=True)
return self.onchange_product_id(cr, uid, ids, pricelist_id, product_id, qty, uom_id,
partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
name=name, price_unit=price_unit, context=context)
lang = res_partner.browse(cr, uid, partner_id).lang
context_partner.update( {'lang': lang, 'partner_id': partner_id} )
product = product_product.browse(cr, uid, product_id, context=context_partner)
- name = product.name
+ #call name_get() with partner in the context to eventually match name and description in the seller_ids field
+ dummy, name = product_product.name_get(cr, uid, product_id, context=context_partner)[0]
if product.description_purchase:
name += '\n' + product.description_purchase
res['value'].update({'name': name})
uom_id = product_uom_po_id
if product.uom_id.category_id.id != product_uom.browse(cr, uid, uom_id, context=context).category_id.id:
- if self._check_product_uom_group(cr, uid, context=context):
+ if context.get('purchase_uom_check') and self._check_product_uom_group(cr, uid, context=context):
res['warning'] = {'title': _('Warning!'), 'message': _('Selected Unit of Measure does not belong to the same category as the product Unit of Measure.')}
uom_id = product_uom_po_id
if not date_order:
date_order = fields.date.context_today(self,cr,uid,context=context)
- qty = qty or 1.0
+
supplierinfo = False
+ precision = self.pool.get('decimal.precision').precision_get(cr, uid, 'Product Unit of Measure')
for supplier in product.seller_ids:
if partner_id and (supplier.name.id == partner_id):
supplierinfo = supplier
if supplierinfo.product_uom.id != uom_id:
res['warning'] = {'title': _('Warning!'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }
min_qty = product_uom._compute_qty(cr, uid, supplierinfo.product_uom.id, supplierinfo.min_qty, to_uom_id=uom_id)
- if qty < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
- 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)}
+ if float_compare(min_qty , qty, precision_digits=precision) == 1: # If the supplier quantity is greater than entered from user, set minimal.
+ if qty:
+ 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)}
qty = min_qty
-
dt = self._get_date_planned(cr, uid, supplierinfo, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-
- res['value'].update({'date_planned': date_planned or dt, 'product_qty': qty})
+ qty = qty or 1.0
+ res['value'].update({'date_planned': date_planned or dt})
+ if qty:
+ res['value'].update({'product_qty': qty})
# - determine price_unit and taxes_id
if pricelist_id:
'purchase_id': fields.many2one('purchase.order', 'Purchase Order'),
}
+ def check_buy(self, cr, uid, ids, context=None):
+ ''' return True if the supply method of the mto product is 'buy'
+ '''
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ for procurement in self.browse(cr, uid, ids, context=context):
+ if procurement.product_id.supply_method <> 'buy':
+ return False
+ return True
+
+ def check_supplier_info(self, cr, uid, ids, context=None):
+ partner_obj = self.pool.get('res.partner')
+ user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
+ for procurement in self.browse(cr, uid, ids, context=context):
+ message = ''
+ partner = procurement.product_id.seller_id #Taken Main Supplier of Product of Procurement.
+
+ if not procurement.product_id.seller_ids:
+ message = _('No supplier defined for this product !')
+ elif not partner:
+ message = _('No default supplier defined for this product')
+ elif not partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']:
+ message = _('No address defined for the supplier')
+
+ if message:
+ if procurement.message != message:
+ cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
+ return False
+
+ if user.company_id and user.company_id.partner_id:
+ if partner.id == user.company_id.partner_id.id:
+ 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))
+
+ return True
+
+
def action_po_assign(self, cr, uid, ids, context=None):
""" This is action which call from workflow to assign purchase order to procurements
@return: True
seller_delay = int(procurement.product_id.seller_delay)
return schedule_date - relativedelta(days=seller_delay)
+ def _get_warehouse(self, procurement, user_company):
+ """
+ Return the warehouse containing the procurment stock location (or one of it ancestors)
+ If none match, returns then first warehouse of the company
+ """
+ # TODO refactor the domain once we implement the "parent_of" domain operator
+ # NOTE This method has been copied in the `purchase_requisition` module to ensure
+ # retro-compatibility. This code duplication will be deleted in next stable version.
+ # Do not forget to update both version in case of modification.
+ company_id = (procurement.company_id or user_company).id
+ domains = [
+ [
+ '&', ('company_id', '=', company_id),
+ '|', '&', ('lot_stock_id.parent_left', '<', procurement.location_id.parent_left),
+ ('lot_stock_id.parent_right', '>', procurement.location_id.parent_right),
+ ('lot_stock_id', '=', procurement.location_id.id)
+ ],
+ [('company_id', '=', company_id)]
+ ]
+
+ cr, uid = procurement._cr, procurement._uid
+ context = procurement._context
+ Warehouse = self.pool['stock.warehouse']
+ for domain in domains:
+ ids = Warehouse.search(cr, uid, domain, context=context)
+ if ids:
+ return ids[0]
+ return False
+
def make_po(self, cr, uid, ids, context=None):
""" Make purchase order from procurement
@return: New created Purchase Orders procurement wise
prod_obj = self.pool.get('product.product')
acc_pos_obj = self.pool.get('account.fiscal.position')
seq_obj = self.pool.get('ir.sequence')
- warehouse_obj = self.pool.get('stock.warehouse')
for procurement in self.browse(cr, uid, ids, context=context):
res_id = procurement.move_id.id
partner = procurement.product_id.seller_id # Taken Main Supplier of Product of Procurement.
partner_id = partner.id
address_id = partner_obj.address_get(cr, uid, [partner_id], ['delivery'])['delivery']
pricelist_id = partner.property_product_pricelist_purchase.id
- warehouse_id = warehouse_obj.search(cr, uid, [('company_id', '=', procurement.company_id.id or company.id)], context=context)
uom_id = procurement.product_id.uom_po_id.id
qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, uom_id)
new_context.update({'lang': partner.lang, 'partner_id': partner_id})
product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context)
- taxes_ids = procurement.product_id.product_tmpl_id.supplier_taxes_id
+ taxes_ids = procurement.product_id.supplier_taxes_id
taxes = acc_pos_obj.map_tax(cr, uid, partner.property_account_position, taxes_ids)
name = product.partner_ref
'origin': procurement.origin,
'partner_id': partner_id,
'location_id': procurement.location_id.id,
- 'warehouse_id': warehouse_id and warehouse_id[0] or False,
+ 'warehouse_id': self._get_warehouse(procurement, company),
'pricelist_id': pricelist_id,
'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'company_id': procurement.company_id.id,
- 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False
+ 'fiscal_position': partner.property_account_position and partner.property_account_position.id or False,
+ 'payment_term_id': partner.property_supplier_payment_term.id or False,
}
res[procurement.id] = self.create_procurement_purchase_order(cr, uid, procurement, po_vals, line_vals, context=new_context)
self.write(cr, uid, [procurement.id], {'state': 'running', 'purchase_id': res[procurement.id]})
- self.purchase_order_create_note(cr, uid, ids, context=context)
+ self.message_post(cr, uid, ids, body=_("Draft Purchase Order created"), context=context)
return res
-
+
def _product_virtual_get(self, cr, uid, order_point):
procurement = order_point.procurement_id
if procurement and procurement.state != 'exception' and procurement.purchase_id and procurement.purchase_id.state in ('draft', 'confirmed'):
return None
return super(procurement_order, self)._product_virtual_get(cr, uid, order_point)
- def purchase_order_create_note(self, cr, uid, ids, context=None):
- for procurement in self.browse(cr, uid, ids, context=context):
- body = _("Draft Purchase Order created")
- self.message_post(cr, uid, [procurement.id], body=body, context=context)
-
-procurement_order()
-class mail_mail(osv.osv):
+class mail_mail(osv.Model):
_name = 'mail.mail'
_inherit = 'mail.mail'
wf_service.trg_validate(uid, 'purchase.order', mail.res_id, 'send_rfq', cr)
return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context)
-mail_mail()
-class product_template(osv.osv):
+class product_template(osv.Model):
_name = 'product.template'
_inherit = 'product.template'
_columns = {
'purchase_ok': 1,
}
-product_template()
+
+class mail_compose_message(osv.Model):
+ _inherit = 'mail.compose.message'
+
+ def send_mail(self, cr, uid, ids, context=None):
+ context = context or {}
+ if context.get('default_model') == 'purchase.order' and context.get('default_res_id'):
+ context = dict(context, mail_post_autofollow=True)
+ wf_service = netsvc.LocalService("workflow")
+ wf_service.trg_validate(uid, 'purchase.order', context['default_res_id'], 'send_rfq', cr)
+ return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
+
+class account_invoice(osv.Model):
+ _inherit = 'account.invoice'
+
+ def invoice_validate(self, cr, uid, ids, context=None):
+ res = super(account_invoice, self).invoice_validate(cr, uid, ids, context=context)
+ purchase_order_obj = self.pool.get('purchase.order')
+ # read access on purchase.order object is not required
+ if not purchase_order_obj.check_access_rights(cr, uid, 'read', raise_exception=False):
+ user_id = SUPERUSER_ID
+ else:
+ user_id = uid
+ po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context)
+ wf_service = netsvc.LocalService("workflow")
+ for order in purchase_order_obj.browse(cr, uid, po_ids, context=context):
+ # Signal purchase order workflow that an invoice has been validated.
+ invoiced = []
+ for po_line in order.order_line:
+ if any(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines):
+ invoiced.append(po_line.id)
+ if invoiced:
+ self.pool['purchase.order.line'].write(cr, uid, invoiced, {'invoiced': True})
+ wf_service.trg_write(uid, 'purchase.order', order.id, cr)
+ return res
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: