from openerp.osv import fields, osv
from openerp.tools.translate import _
import openerp.addons.decimal_precision as dp
-from openerp.osv.orm import browse_record, browse_null
+from openerp.osv.orm import browse_record_list, 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):
(date_planned=%s or date_planned<%s)""", (value,po.id,po.minimum_planned_date,value))
cr.execute("""update purchase_order set
minimum_planned_date=%s where id=%s""", (value, po.id))
+ self.invalidate_cache(cr, uid, context=context)
return True
def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
def _get_picking_in(self, cr, uid, context=None):
obj_data = self.pool.get('ir.model.data')
- return obj_data.get_object_reference(cr, uid, 'stock','picking_type_in') and obj_data.get_object_reference(cr, uid, 'stock','picking_type_in')[1] or False
+ type_obj = self.pool.get('stock.picking.type')
+ user_obj = self.pool.get('res.users')
+ company_id = user_obj.browse(cr, uid, uid, context=context).company_id.id
+ types = type_obj.search(cr, uid, [('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)], context=context)
+ if not types:
+ types = type_obj.search(cr, uid, [('code', '=', 'incoming'), ('warehouse_id', '=', False)], context=context)
+ if not types:
+ raise osv.except_osv(_('Error!'), _("Make sure you have at least an incoming picking type defined"))
+ return types[0]
def _get_picking_ids(self, cr, uid, ids, field_names, args, context=None):
res = {}
res[po_id].append(pick_id)
return res
+ def _count_all(self, cr, uid, ids, field_name, arg, context=None):
+ return {
+ purchase.id: {
+ 'shipment_count': len(purchase.picking_ids),
+ 'invoice_count': len(purchase.invoice_ids),
+ }
+ for purchase in self.browse(cr, uid, ids, context=context)
+ }
+
STATE_SELECTION = [
('draft', 'Draft PO'),
('sent', 'RFQ'),
},
}
_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 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 sales order or bid 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"),
+ 'name': fields.char('Order Reference', required=True, select=True, copy=False,
+ help="Unique number of the purchase order, "
+ "computed automatically when the purchase order is created."),
+ 'origin': fields.char('Source Document', copy=False,
+ 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)]},
+ copy=False,
+ help="Reference of the sales order or bid 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.datetime('Order Date', required=True, states={'confirmed':[('readonly',True)],
+ 'approved':[('readonly',True)]},
+ select=True, help="Depicts the date where the Quotation should be validated and converted into a Purchase Order, by default it's the creation date.",
+ copy=False),
+ 'date_approve':fields.date('Date Approved', readonly=1, select=True, copy=False,
+ 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, track_visibility='always'),
'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
'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.many2one('res.currency','Currency', readonly=True, required=True,states={'draft': [('readonly', False)],'sent': [('readonly', False)]}),
- 'state': fields.selection(STATE_SELECTION, 'Status', readonly=True, help="The status of the purchase order or the quotation request. A request for 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),
+ 'state': fields.selection(STATE_SELECTION, 'Status', readonly=True,
+ help="The status of the purchase order or the quotation request. "
+ "A request for 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 receipt of goods, the status becomes "
+ "in exception.",
+ select=True, copy=False),
+ 'order_line': fields.one2many('purchase.order.line', 'order_id', 'Order Lines',
+ states={'approved':[('readonly',True)],
+ 'done':[('readonly',True)]},
+ copy=True),
+ 'validator' : fields.many2one('res.users', 'Validated by', readonly=True, copy=False),
'notes': fields.text('Terms and Conditions'),
- 'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id', 'invoice_id', 'Invoices', help="Invoices generated for a purchase order"),
- 'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of reception operations 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"),
+ 'invoice_ids': fields.many2many('account.invoice', 'purchase_invoice_rel', 'purchase_id',
+ 'invoice_id', 'Invoices', copy=False,
+ help="Invoices generated for a purchase order"),
+ 'picking_ids': fields.function(_get_picking_ids, method=True, type='one2many', relation='stock.picking', string='Picking List', help="This is the list of receipts that have been generated for this purchase order."),
+ 'shipped':fields.boolean('Received', readonly=True, select=True, copy=False,
+ help="It indicates that a picking has been done"),
'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 paid"),
+ 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', copy=False,
+ 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,
- readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
+ readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)],'bid':[('readonly',False)]},
help="Based on Purchase Order lines: place individual lines in 'Invoice Control / On Purchase Order lines' from where you can selectively create an invoice.\n" \
"Based on generated invoice: create a draft invoice you can validate later.\n" \
- "Based on incoming shipments: let you create an invoice when receptions are validated."
+ "Based on incoming shipments: let you create an invoice when receipts are validated."
),
'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.",
store = {
'picking_type_id': fields.many2one('stock.picking.type', 'Deliver To', help="This will determine picking type of incoming shipment", required=True,
states={'confirmed': [('readonly', True)], 'approved': [('readonly', True)], 'done': [('readonly', True)]}),
'related_location_id': fields.related('picking_type_id', 'default_location_dest_id', type='many2one', relation='stock.location', string="Related location", store=True),
+ 'shipment_count': fields.function(_count_all, type='integer', string='Incoming Shipments', multi=True),
+ 'invoice_count': fields.function(_count_all, type='integer', string='Invoices', multi=True)
}
_defaults = {
- 'date_order': fields.date.context_today,
+ 'date_order': fields.datetime.now,
'state': 'draft',
'name': lambda obj, cr, uid, context: '/',
'shipped': 0,
def create(self, cr, uid, vals, context=None):
if vals.get('name','/')=='/':
- vals['name'] = self.pool.get('ir.sequence').get(cr, uid, 'purchase.order') or '/'
- if context is None:
- context = {}
- context.update({'mail_create_nolog': True})
+ vals['name'] = self.pool.get('ir.sequence').next_by_code(cr, uid, 'purchase.order') or '/'
+ context = dict(context or {}, mail_create_nolog=True)
order = super(purchase_order, self).create(cr, uid, vals, context=context)
self.message_post(cr, uid, [order], body=_("RFQ created"), context=context)
return order
raise osv.except_osv(_('Invalid Action!'), _('In order to delete a purchase order, you must cancel it first.'))
# automatically sending subflow.delete upon deletion
- self.signal_purchase_cancel(cr, uid, unlink_ids)
+ self.signal_workflow(cr, uid, unlink_ids, 'purchase_cancel')
return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
def set_order_line_status(self, cr, uid, ids, status, context=None):
line = self.pool.get('purchase.order.line')
- lines = []
+ order_line_ids = []
proc_obj = self.pool.get('procurement.order')
for order in self.browse(cr, uid, ids, context=context):
- order_line_ids = [order_line.id for order_line in order.order_line]
- lines += order_line_ids
+ order_line_ids += [po_line.id for po_line in order.order_line]
+ if order_line_ids:
line.write(cr, uid, order_line_ids, {'state': status}, context=context)
- if lines:
- procs = proc_obj.search(cr, uid, [('purchase_line_id', 'in', lines)], context=context)
+ if order_line_ids and status == 'cancel':
+ procs = proc_obj.search(cr, uid, [('purchase_line_id', 'in', order_line_ids)], context=context)
if procs:
- proc_obj.write(cr, uid, procs, {'state': 'cancel'}, context=context)
+ proc_obj.write(cr, uid, procs, {'state': 'exception'}, context=context)
return True
def button_dummy(self, cr, uid, ids, context=None):
return {}
return {'value': {'currency_id': self.pool.get('product.pricelist').browse(cr, uid, pricelist_id, context=context).currency_id.id}}
- #Destination address is used when dropshipping
- def onchange_dest_address_id(self, cr, uid, ids, address_id):
+ #Destination address is used when dropshipping
+ def onchange_dest_address_id(self, cr, uid, ids, address_id, context=None):
if not address_id:
return {}
address = self.pool.get('res.partner')
values = {}
- supplier = address.browse(cr, uid, address_id)
+ supplier = address.browse(cr, uid, address_id, context=context)
if supplier:
location_id = supplier.property_stock_customer.id
values.update({'location_id': location_id})
value.update({'related_location_id': picktype.default_location_dest_id and picktype.default_location_dest_id.id or False})
return {'value': value}
- def onchange_partner_id(self, cr, uid, ids, partner_id):
+ def onchange_partner_id(self, cr, uid, ids, partner_id, context=None):
partner = self.pool.get('res.partner')
if not partner_id:
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)
+ supplier_address = partner.address_get(cr, uid, [partner_id], ['default'], context=context)
+ supplier = partner.browse(cr, uid, partner_id, context=context)
return {'value': {
'pricelist_id': supplier.property_product_pricelist_purchase.id,
'fiscal_position': supplier.property_account_position and supplier.property_account_position.id or False,
'''
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.
'''
+ context = dict(context or {})
mod_obj = self.pool.get('ir.model.data')
wizard_obj = self.pool.get('purchase.order.line_invoice')
#compute the number of invoices to display
action = self.pool.get('ir.actions.act_window').read(cr, uid, action_id, context=context)
pick_ids = []
- #TODO: might need to change this function in order to return the whole set of operations and not only the reception(s) to input
for po in self.browse(cr, uid, ids, context=context):
pick_ids += [picking.id for picking in po.picking_ids]
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'
- self.signal_send_rfq(cr, uid, ids)
+ self.signal_workflow(cr, uid, ids, 'send_rfq')
return self.pool['report'].get_action(cr, uid, ids, 'purchase.report_purchasequotation', context=context)
- #TODO: implement messages system
def wkf_confirm_order(self, cr, uid, ids, context=None):
todo = []
for po in self.browse(cr, uid, ids, context=context):
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,))
+ raise osv.except_osv(_('Error!'), _('Define an 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
'purchase_line_id': order_line.id,
}
+ def _prepare_invoice(self, cr, uid, order, line_ids, context=None):
+ """Prepare the dict of values to create the new invoice for a
+ purchase order. This method may be overridden to implement custom
+ invoice generation (making sure to call super() to establish
+ a clean extension chain).
+
+ :param browse_record order: purchase.order record to invoice
+ :param list(int) line_ids: list of invoice line IDs that must be
+ attached to the invoice
+ :return: dict of value to create() the invoice
+ """
+ journal_ids = self.pool['account.journal'].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))
+ return {
+ 'name': order.partner_ref or order.name,
+ 'reference': order.partner_ref or order.name,
+ 'account_id': order.partner_id.property_account_payable.id,
+ 'type': 'in_invoice',
+ 'partner_id': order.partner_id.id,
+ 'currency_id': order.currency_id.id,
+ 'journal_id': len(journal_ids) and journal_ids[0] or False,
+ 'invoice_line': [(6, 0, line_ids)],
+ 'origin': order.name,
+ 'fiscal_position': order.fiscal_position.id or False,
+ 'payment_term': order.payment_term_id.id or False,
+ 'company_id': order.company_id.id,
+ }
+
def action_cancel_draft(self, cr, uid, ids, context=None):
if not len(ids):
return False
:return: ID of created invoice.
:rtype: int
"""
- if context is None:
- context = {}
- journal_obj = self.pool.get('account.journal')
+ context = dict(context or {})
+
inv_obj = self.pool.get('account.invoice')
inv_line_obj = self.pool.get('account.invoice.line')
#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)
- 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:
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({'invoice_lines': [(4, inv_line_id)]}, context=context)
+ po_line.write({'invoice_lines': [(4, inv_line_id)]})
# get invoice data and create invoice
- inv_data = {
- 'name': order.partner_ref or order.name,
- 'reference': order.partner_ref or order.name,
- 'account_id': pay_acc_id,
- 'type': 'in_invoice',
- 'partner_id': order.partner_id.id,
- 'currency_id': order.currency_id.id,
- '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 False,
- 'payment_term': order.payment_term_id.id or False,
- 'company_id': order.company_id.id,
- }
+ inv_data = self._prepare_invoice(cr, uid, order, inv_lines, context=context)
inv_id = inv_obj.create(cr, uid, inv_data, context=context)
# compute the invoice
inv_obj.button_compute(cr, uid, [inv_id], context=context, set_total=True)
# Link this new invoice to related purchase order
- order.write({'invoice_ids': [(4, inv_id)]}, context=context)
+ order.write({'invoice_ids': [(4, inv_id)]})
res = inv_id
return res
return False
def wkf_action_cancel(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'cancel'})
+ self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
self.set_order_line_status(cr, uid, ids, 'cancel', context=context)
-
def action_cancel(self, cr, uid, ids, context=None):
for purchase in self.browse(cr, uid, ids, context=context):
for pick in purchase.picking_ids:
- if pick.state not in ('draft', 'cancel'):
- raise osv.except_osv(
- _('Unable to cancel the purchase order %s.') % (purchase.name),
- _('First cancel all receptions related to this purchase order.'))
- self.pool.get('stock.picking') \
- .signal_button_cancel(cr, uid, map(attrgetter('id'), purchase.picking_ids))
+ for move in pick.move_lines:
+ if pick.state == 'done':
+ raise osv.except_osv(
+ _('Unable to cancel the purchase order %s.') % (purchase.name),
+ _('You have already received some goods for it. '))
+ self.pool.get('stock.picking').action_cancel(cr, uid, [x.id for x in purchase.picking_ids if x.state != 'cancel'], context=context)
for inv in purchase.invoice_ids:
if inv and inv.state not in ('cancel', 'draft'):
raise osv.except_osv(
_('Unable to cancel this purchase order.'),
- _('You must first cancel all receptions related to this purchase order.'))
+ _('You must first cancel all invoices related to this purchase order.'))
self.pool.get('account.invoice') \
- .signal_invoice_cancel(cr, uid, map(attrgetter('id'), purchase.invoice_ids))
- self.write(cr, uid, ids, {'state': 'cancel'})
- self.set_order_line_status(cr, uid, ids, 'cancel', context=context)
- self.signal_purchase_cancel(cr, uid, ids)
+ .signal_workflow(cr, uid, map(attrgetter('id'), purchase.invoice_ids), 'invoice_cancel')
+ self.signal_workflow(cr, uid, ids, 'purchase_cancel')
return True
def _prepare_order_line_move(self, cr, uid, order, order_line, picking_id, group_id, context=None):
product_uom = self.pool.get('product.uom')
price_unit = order_line.price_unit
if order_line.product_uom.id != order_line.product_id.uom_id.id:
- price_unit *= order_line.product_uom.factor
+ price_unit *= order_line.product_uom.factor / order_line.product_id.uom_id.factor
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)
'product_id': order_line.product_id.id,
'product_uom': order_line.product_uom.id,
'product_uos': order_line.product_uom.id,
- 'date': fields.date.date_to_datetime(self, cr, uid, order.date_order, context),
+ 'date': order.date_order,
'date_expected': fields.date.date_to_datetime(self, cr, uid, order_line.date_planned, context),
'location_id': order.partner_id.property_stock_supplier.id,
'location_dest_id': order.location_id.id,
'origin': order.name,
'route_ids': order.picking_type_id.warehouse_id and [(6, 0, [x.id for x in order.picking_type_id.warehouse_id.route_ids])] or [],
'warehouse_id':order.picking_type_id.warehouse_id.id,
+ 'invoice_state': order.invoice_method == 'picking' and '2binvoiced' or 'none',
}
diff_quantity = order_line.product_qty
tmp.update({
'product_uom_qty': min(procurement_qty, diff_quantity),
'product_uos_qty': min(procurement_qty, diff_quantity),
- 'move_dest_id': procurement.move_dest_id.id, # blabla
- 'group_id': procurement.group_id.id or group_id, # blabla to check ca devrait etre bon et groupé dans le meme picking qd meme
+ 'move_dest_id': procurement.move_dest_id.id, #move destination is same as procurement destination
+ 'group_id': procurement.group_id.id or group_id, #move group is same as group of procurements if it exists, otherwise take another group
'procurement_id': procurement.id,
+ 'invoice_state': procurement.rule_id.invoice_state or (procurement.location_id and procurement.location_id.usage == 'customer' and procurement.invoice_state=='picking' and '2binvoiced') or (order.invoice_method == 'picking' and '2binvoiced') or 'none', #dropship case takes from sale
+ 'propagate': procurement.rule_id.propagate,
})
diff_quantity -= min(procurement_qty, diff_quantity)
res.append(tmp)
def action_picking_create(self, cr, uid, ids, context=None):
for order in self.browse(cr, uid, ids):
- picking_id = self.pool.get('stock.picking').create(cr, uid, {'picking_type_id': order.picking_type_id.id, 'partner_id': order.dest_address_id.id or order.partner_id.id}, context=context)
+ picking_vals = {
+ 'picking_type_id': order.picking_type_id.id,
+ 'partner_id': order.dest_address_id.id or order.partner_id.id,
+ 'date': max([l.date_planned for l in order.order_line]),
+ 'origin': order.name
+ }
+ picking_id = self.pool.get('stock.picking').create(cr, uid, picking_vals, context=context)
self._create_stock_moves(cr, uid, order, order.order_line, picking_id, context=context)
def picking_done(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, body=_("Products received"), context=context)
return True
- def copy(self, cr, uid, id, default=None, context=None):
- if not default:
- default = {}
- default.update({
- 'state':'draft',
- 'shipped':False,
- 'invoiced':False,
- 'invoice_ids': [],
- 'origin': '',
- 'partner_ref': '',
- 'name': self.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
- })
- return super(purchase_order, self).copy(cr, uid, id, default, context)
-
def do_merge(self, cr, uid, ids, context=None):
"""
To merge similar type of purchase orders.
field_val = field_val.id
elif isinstance(field_val, browse_null):
field_val = False
- elif isinstance(field_val, list):
+ elif isinstance(field_val, browse_record_list):
field_val = ((6, 0, tuple([v.id for v in field_val])),)
list_key.append((field, field_val))
list_key.sort()
return tuple(list_key)
- if context is None:
- context = {}
+ context = dict(context or {})
# Compute what the new orders should contain
new_orders = {}
# make triggers pointing to the old orders point to the new order
for old_id in old_ids:
self.redirect_workflow(cr, uid, [(old_id, neworder_id)])
- self.signal_purchase_cancel(cr, uid, [old_id])
+ self.signal_workflow(cr, uid, [old_id], 'purchase_cancel')
return orders_info
'order_id': fields.many2one('purchase.order', 'Order Reference', select=True, required=True, ondelete='cascade'),
'account_analytic_id':fields.many2one('account.analytic.account', 'Analytic Account',),
'company_id': fields.related('order_id','company_id',type='many2one',relation='res.company',string='Company', store=True, readonly=True),
- 'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')], 'Status', required=True, readonly=True,
+ 'state': fields.selection([('draft', 'Draft'), ('confirmed', 'Confirmed'), ('done', 'Done'), ('cancel', 'Cancelled')],
+ 'Status', required=True, readonly=True, copy=False,
help=' * The \'Draft\' status is set automatically when purchase order in draft status. \
\n* The \'Confirmed\' status is set automatically as confirm when purchase order in confirm status. \
\n* The \'Done\' status is set automatically when purchase order is set as done. \
\n* The \'Cancelled\' status is set automatically when user cancel purchase order.'),
- 'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel', 'order_line_id', 'invoice_id', 'Invoice Lines', readonly=True),
- 'invoiced': fields.boolean('Invoiced', readonly=True),
- 'partner_id': fields.related('order_id','partner_id',string='Partner',readonly=True,type="many2one", relation="res.partner", store=True),
- 'date_order': fields.related('order_id','date_order',string='Order Date',readonly=True,type="date"),
+ 'invoice_lines': fields.many2many('account.invoice.line', 'purchase_order_line_invoice_rel',
+ 'order_line_id', 'invoice_id', 'Invoice Lines',
+ readonly=True, copy=False),
+ 'invoiced': fields.boolean('Invoiced', readonly=True, copy=False),
+ 'partner_id': fields.related('order_id', 'partner_id', string='Partner', readonly=True, type="many2one", relation="res.partner", store=True),
+ 'date_order': fields.related('order_id', 'date_order', string='Order Date', readonly=True, type="datetime"),
'procurement_ids': fields.one2many('procurement.order', 'purchase_line_id', string='Associated procurements'),
}
_defaults = {
_name = 'purchase.order.line'
_description = 'Purchase Order Line'
- def copy_data(self, cr, uid, id, default=None, context=None):
- if not default:
- default = {}
- default.update({'state':'draft', 'move_ids':[], 'invoiced':0, 'invoice_lines':[], 'procurement_ids': False})
- return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
-
def unlink(self, cr, uid, ids, context=None):
+ 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,))
procurement_obj = self.pool.get('procurement.order')
procurement_ids_to_cancel = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
if procurement_ids_to_cancel:
:param browse_record | False supplier_info: product.supplierinfo, used to
determine delivery delay (if False, default delay = 0)
- :param str date_order_str: date of order, as a string in
- DEFAULT_SERVER_DATE_FORMAT
+ :param str date_order_str: date of order field, as a string in
+ DEFAULT_SERVER_DATETIME_FORMAT
:rtype: datetime
:return: desired Schedule Date for the PO line
"""
supplier_delay = int(supplier_info.delay) if supplier_info else 0
- return datetime.strptime(date_order_str, DEFAULT_SERVER_DATE_FORMAT) + relativedelta(days=supplier_delay)
+ return datetime.strptime(date_order_str, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=supplier_delay)
def action_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
# - determine product_qty and date_planned based on seller info
if not date_order:
- date_order = fields.date.context_today(self,cr,uid,context=context)
+ date_order = fields.datetime.now()
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 or 0.0) < min_qty: # If the supplier quantity is greater than entered from user, set minimal.
+ 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
res['value'].update({'product_qty': qty})
price = price_unit
- if state not in ('sent','bid'):
+ if price_unit is False or price_unit is None:
# - determine price_unit and taxes_id
if pricelist_id:
+ date_order_str = datetime.strptime(date_order, DEFAULT_SERVER_DATETIME_FORMAT).strftime(DEFAULT_SERVER_DATE_FORMAT)
price = product_pricelist.price_get(cr, uid, [pricelist_id],
- product.id, qty or 1.0, partner_id or False, {'uom': uom_id, 'date': date_order})[pricelist_id]
+ product.id, qty or 1.0, partner_id or False, {'uom': uom_id, 'date': date_order_str})[pricelist_id]
else:
price = product.standard_price
self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
return True
+
class procurement_rule(osv.osv):
_inherit = 'procurement.rule'
def _get_action(self, cr, uid, context=None):
- return [('buy', 'Buy')] + super(procurement_rule, self)._get_action(cr, uid, context=context)
+ return [('buy', _('Buy'))] + super(procurement_rule, self)._get_action(cr, uid, context=context)
class procurement_order(osv.osv):
product = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context)
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
+ name = product.display_name
if product.description_purchase:
name += '\n' + product.description_purchase
res[procurement.id] = False
else:
schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, company, context=context)
+ purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
line_vals = self._get_po_line_values_from_proc(cr, uid, procurement, partner, company, schedule_date, context=context)
#look for any other draft PO for the same supplier, to attach the new line on instead of creating a new draft one
available_draft_po_ids = po_obj.search(cr, uid, [
('location_id', '=', procurement.location_id.id), ('company_id', '=', procurement.company_id.id), ('dest_address_id', '=', procurement.partner_dest_id.id)], context=context)
if available_draft_po_ids:
po_id = available_draft_po_ids[0]
+ po_rec = po_obj.browse(cr, uid, po_id, context=context)
+ #if the product has to be ordered earlier those in the existing PO, we replace the purchase date on the order to avoid ordering it too late
+ if datetime.strptime(po_rec.date_order, DEFAULT_SERVER_DATETIME_FORMAT) > purchase_date:
+ po_obj.write(cr, uid, [po_id], {'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
#look for any other PO line in the selected PO with same product and UoM to sum quantities instead of creating a new po line
available_po_line_ids = po_line_obj.search(cr, uid, [('order_id', '=', po_id), ('product_id', '=', line_vals['product_id']), ('product_uom', '=', line_vals['product_uom'])], context=context)
if available_po_line_ids:
po_line_id = po_line_obj.create(cr, SUPERUSER_ID, line_vals, context=context)
linked_po_ids.append(procurement.id)
else:
- purchase_date = self._get_purchase_order_date(cr, uid, procurement, company, schedule_date, context=context)
- name = seq_obj.get(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
+ name = seq_obj.next_by_code(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
po_vals = {
'name': name,
'origin': procurement.origin,
'location_id': procurement.location_id.id,
'picking_type_id': procurement.rule_id.picking_type_id.id,
'pricelist_id': partner.property_product_pricelist_purchase.id,
+ 'currency_id': partner.property_product_pricelist_purchase and partner.property_product_pricelist_purchase.currency_id.id or procurement.company_id.currency_id.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,
if mail_sent and mail.model == 'purchase.order':
obj = self.pool.get('purchase.order').browse(cr, uid, mail.res_id, context=context)
if obj.state == 'draft':
- self.pool.get('purchase.order').signal_send_rfq(cr, uid, [mail.res_id])
+ self.pool.get('purchase.order').signal_workflow(cr, uid, [mail.res_id], 'send_rfq')
return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context, mail_sent=mail_sent)
class product_template(osv.Model):
_name = 'product.template'
_inherit = 'product.template'
+
+ def _get_buy_route(self, cr, uid, context=None):
+
+ buy_route = self.pool.get('ir.model.data').xmlid_to_res_id(cr, uid, 'purchase.route_warehouse0_buy')
+ if buy_route:
+ return [buy_route]
+ return []
+
+ def _purchase_count(self, cr, uid, ids, field_name, arg, context=None):
+ res = dict.fromkeys(ids, 0)
+ for template in self.browse(cr, uid, ids, context=context):
+ res[template.id] = sum([p.purchase_count for p in template.product_variant_ids])
+ return res
+
_columns = {
'purchase_ok': fields.boolean('Can be Purchased', help="Specify if the product can be selected in a purchase order line."),
+ 'purchase_count': fields.function(_purchase_count, string='# Purchases', type='integer'),
}
+
_defaults = {
'purchase_ok': 1,
+ 'route_ids': _get_buy_route,
+ }
+
+ def action_view_purchases(self, cr, uid, ids, context=None):
+ products = self._get_products(cr, uid, ids, context=context)
+ result = self._get_act_window_dict(cr, uid, 'purchase.action_purchase_line_product_tree', context=context)
+ result['domain'] = "[('product_id','in',[" + ','.join(map(str, products)) + "])]"
+ return result
+
+class product_product(osv.Model):
+ _name = 'product.product'
+ _inherit = 'product.product'
+
+ def _purchase_count(self, cr, uid, ids, field_name, arg, context=None):
+ Purchase = self.pool['purchase.order']
+ return {
+ product_id: Purchase.search_count(cr,uid, [('order_line.product_id', '=', product_id)], context=context)
+ for product_id in ids
+ }
+
+ _columns = {
+ 'purchase_count': fields.function(_purchase_count, string='# Purchases', type='integer'),
}
+
class mail_compose_message(osv.Model):
_inherit = 'mail.compose.message'
context = context or {}
if context.get('default_model') == 'purchase.order' and context.get('default_res_id'):
context = dict(context, mail_post_autofollow=True)
- self.pool.get('purchase.order').signal_send_rfq(cr, uid, [context['default_res_id']])
+ self.pool.get('purchase.order').signal_workflow(cr, uid, [context['default_res_id']], 'send_rfq')
return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context)
class account_invoice(osv.Model):
""" Override account_invoice to add Chatter messages on the related purchase
- orders, logging the invoice reception or payment. """
+ orders, logging the invoice receipt or payment. """
_inherit = 'account.invoice'
def invoice_validate(self, cr, uid, ids, context=None):
readonly=True),
}
-class product_product(osv.osv):
- _inherit = "product.product"
-
- def _get_buy_route(self, cr, uid, context=None):
- buy_route = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'purchase', 'route_warehouse0_buy')[1]
- return [buy_route]
-
- _defaults = {
- 'route_ids': _get_buy_route,
- }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: