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, order.partner_address_id.id, 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.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)
('done', 'Done'),
('cancel', 'Cancelled')
]
-
_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,
'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_address_id':fields.many2one('res.partner.address', 'Address', required=True,
- states={'confirmed':[('readonly',True)], 'approved':[('readonly',True)],'done':[('readonly',True)]},domain="[('partner_id', '=', partner_id)]"),
- 'dest_address_id':fields.many2one('res.partner.address', 'Destination Address', domain="[('partner_id', '!=', False)]",
+ '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." \
"In this case, it will remove the warehouse link and set the customer location."
'company_id': fields.many2one('res.company','Company',required=True,select=1),
}
_defaults = {
- 'date_order': lambda *a: time.strftime('%Y-%m-%d'),
+ 'date_order': fields.date.context_today,
'state': 'draft',
'name': lambda obj, cr, uid, context: obj.pool.get('ir.sequence').get(cr, uid, 'purchase.order'),
'shipped': 0,
'invoice_method': 'order',
'invoiced': 0,
- 'partner_address_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['default'])['default'],
'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,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
}
('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
]
_name = "purchase.order"
+ _inherit = ['ir.needaction_mixin', 'mail.thread']
_description = "Purchase Order"
_order = "name desc"
-
+
+ def create(self, cr, uid, vals, context=None):
+ 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):
purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
unlink_ids = []
def onchange_dest_address_id(self, cr, uid, ids, address_id):
if not address_id:
return {}
- address = self.pool.get('res.partner.address')
+ address = self.pool.get('res.partner')
values = {'warehouse_id': False}
- supplier = address.browse(cr, uid, address_id).partner_id
+ supplier = address.browse(cr, uid, address_id)
if supplier:
location_id = supplier.property_stock_customer.id
values.update({'location_id': location_id})
def onchange_partner_id(self, cr, uid, ids, partner_id):
partner = self.pool.get('res.partner')
if not partner_id:
- return {'value':{'partner_address_id': False, 'fiscal_position': False}}
+ return {'value':{'fiscal_position': 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':{'partner_address_id': supplier_address['default'], 'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
+ return {'value':{'pricelist_id': pricelist, 'fiscal_position': fiscal_position}}
def wkf_approve_order(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'approved', 'date_approve': time.strftime('%Y-%m-%d')})
+ self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
return True
#TODO: implement messages system
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 _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
+ """Collects require data from purchase order line that is used to create invoice line
for that purchase order line
:param account_id: Expense account of the product of PO line if any.
:param browse_record order_line: Purchase order line browse record
'type': 'in_invoice',
'partner_id': order.partner_id.id,
'currency_id': order.pricelist_id.currency_id.id,
- 'address_invoice_id': order.partner_address_id.id,
- 'address_contact_id': order.partner_address_id.id,
'journal_id': len(journal_ids) and journal_ids[0] or False,
- 'invoice_line': [(6, 0, inv_lines)],
+ '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,
# 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 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 inv:
wf_service.trg_validate(uid, 'account.invoice', inv.id, 'invoice_cancel', cr)
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)
message = _("Purchase order '%s' is cancelled.") % name
'origin': order.name + ((order.origin and (':' + order.origin)) or ''),
'date': order.date_order,
'type': 'in',
- 'address_id': order.dest_address_id.id or order.partner_address_id.id,
+ '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):
return {
'name': order.name + ': ' + (order_line.name or ''),
'location_id': order.partner_id.property_stock_supplier.id,
'location_dest_id': order.location_id.id,
'picking_id': picking_id,
- 'address_id': order.dest_address_id.id or order.partner_address_id.id,
+ 'partner_id': order.dest_address_id.id or order.partner_id.id,
'move_dest_id': order_line.move_dest_id.id,
'state': 'draft',
'purchase_line_id': order_line.id,
will be added. A new picking will be created if omitted.
:return: list of IDs of pickings used/created for the given order lines (usually just one)
"""
- if not picking_id:
+ if not picking_id:
picking_id = self.pool.get('stock.picking').create(cr, uid, self._prepare_order_picking(cr, uid, order, context=context))
todo_moves = []
stock_move = self.pool.get('stock.move')
stock_move.force_assign(cr, uid, todo_moves)
wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
return [picking_id]
-
+
def action_picking_create(self,cr, uid, ids, context=None):
picking_ids = []
for order in self.browse(cr, uid, ids):
# 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):
if not default:
default = {}
'origin': porder.origin,
'date_order': porder.date_order,
'partner_id': porder.partner_id.id,
- 'partner_address_id': porder.partner_address_id.id,
'dest_address_id': porder.dest_address_id.id,
'warehouse_id': porder.warehouse_id.id,
'location_id': porder.location_id.id,
wf_service.trg_redirect(uid, 'purchase.order', old_id, neworder_id, cr)
wf_service.trg_validate(uid, 'purchase.order', old_id, 'purchase_cancel', cr)
return orders_info
+
+ # --------------------------------------
+ # OpenChatter methods and notifications
+ # --------------------------------------
+
+ def get_needaction_user_ids(self, cr, uid, ids, context=None):
+ result = dict.fromkeys(ids, [])
+ for obj in self.browse(cr, uid, ids, context=context):
+ if obj.state == 'approved':
+ result[obj.id] = [obj.validator.id]
+ return result
+
+ def create_send_note(self, cr, uid, ids, context=None):
+ return self.message_append_note(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_subscribe(cr, uid, [obj.id], [obj.validator.id], context=context)
+ self.message_append_note(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):
+ pck_date = datetime.strptime(picking.min_date, '%Y-%m-%d %H:%M:%S').strftime('%m/%d/%Y')
+ self.message_append_note(cr, uid, [order.id], body=_("Shipment <em>%s</em> <b>scheduled</b> for %s.") % (picking.name, pck_date), 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_append_note(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_append_note(cr, uid, ids, body=_("""Shipment <b>received</b>."""), context=context)
+
+ def invoice_done_send_note(self, cr, uid, ids, context=None):
+ self.message_append_note(cr, uid, ids, body=_("Invoice <b>paid</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_append_note(cr, uid, [obj.id], body=_("Purchase Order for <em>%s</em> <b>cancelled</b>.") % (obj.partner_id.name), context=context)
purchase_order()
default.update({'state':'draft', 'move_ids':[],'invoiced':0,'invoice_lines':[]})
return super(purchase_order_line, self).copy_data(cr, uid, id, default, context)
-
- def _onchange_check_partner_pricelist(self, pricelist_id, partner_id):
- return True
-
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, notes=False, context=None):
partner_id, date_order=date_order, fiscal_position_id=fiscal_position_id, date_planned=date_planned,
name=name, price_unit=price_unit, notes=notes, context=context)
+ def _get_date_planned(self, cr, uid, supplier_info, date_order_str, context=None):
+ """Return the datetime value to use as Schedule Date (``date_planned``) for
+ PO Lines that correspond to the given product.supplierinfo,
+ when ordered at `date_order_str`.
+
+ :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
+ :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)
+
def onchange_product_id(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, notes=False, context=None):
"""
onchange handler of product_id.
"""
- res = {}
if context is None:
context = {}
-
- res_value = {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or'', 'product_uom' : uom_id or False}
+
+ res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'notes': notes or '', 'product_uom' : uom_id or False}}
if not product_id:
- return {'value': res_value}
+ return res
product_product = self.pool.get('product.product')
product_uom = self.pool.get('product.uom')
# - determine name and notes based on product in partner lang.
lang = res_partner.browse(cr, uid, partner_id).lang
- if lang:
- context['lang'] = lang
- context['partner_id'] = partner_id
- product = product_product.browse(cr, uid, product_id, context=context)
- res_value.update({'name': product.name, 'notes': notes or product.description_purchase})
-
+ context_partner = {'lang': lang, 'partner_id': partner_id}
+ product = product_product.browse(cr, uid, product_id, context=context_partner)
+ res['value'].update({'name': product.name, 'notes': notes or product.description_purchase})
+
# - set a domain on product_uom
- domain = {'product_uom':[('category_id','=',product.uom_id.category_id.id)]}
- res.update({'domain': domain})
+ res['domain'] = {'product_uom': [('category_id','=',product.uom_id.category_id.id)]}
# - check that uom and product uom belong to the same category
product_uom_po_id = product.uom_po_id.id
if not uom_id:
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:
- res.update({'warning': {'title': _('Warning'), 'message': _('Selected UOM does not have same category of default UOM')}})
+ res['warning'] = {'title': _('Warning'), 'message': _('Selected UOM does not belong to the same category as the product UOM')}
uom_id = product_uom_po_id
- res_value.update({'product_uom': uom_id})
+ res['value'].update({'product_uom': uom_id})
# - determine product_qty and date_planned based on seller info
if not date_order:
- date_order = time.strftime('%Y-%m-%d')
+ date_order = fields.date.context_today(cr,uid,context=context)
qty = qty or 1.0
- seller_delay = 0
-
+ supplierinfo = False
supplierinfo_ids = product_supplierinfo.search(cr, uid, [('name','=',partner_id),('product_id','=',product.id)])
- for supplierinfo in product_supplierinfo.browse(cr, uid, supplierinfo_ids, context=context):
- seller_delay = supplierinfo.delay
+ if supplierinfo_ids:
+ supplierinfo = product_supplierinfo.browse(cr, uid, supplierinfo_ids[0], context=context)
if supplierinfo.product_uom.id != uom_id:
- res.update({'warning': {'title': _('Warning'), 'message': _('The selected supplier only sells this product by %s') % supplierinfo.product_uom.name }})
+ 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.update({'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)}})
+ 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 = (datetime.strptime(date_order, '%Y-%m-%d') + relativedelta(days=int(seller_delay) or 0.0)).strftime('%Y-%m-%d %H:%M:%S')
- res_value.update({'date_planned': date_planned or dt, 'product_qty': 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})
# - determine price_unit and taxes_id
price = product_pricelist.price_get(cr, uid, [pricelist_id],
- product.id, qty or 1.0, partner_id, {
- 'uom': uom_id,
- 'date': date_order,
- })[pricelist_id]
-
+ product.id, qty or 1.0, partner_id, {'uom': uom_id, 'date': date_order})[pricelist_id]
+
taxes = account_tax.browse(cr, uid, map(lambda x: x.id, product.supplier_taxes_id))
fpos = fiscal_position_id and account_fiscal_position.browse(cr, uid, fiscal_position_id, context=context) or False
taxes_ids = account_fiscal_position.map_tax(cr, uid, fpos, taxes)
- res_value.update({'price_unit': price, 'taxes_id': taxes_ids})
-
+ res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
- res.update({'value': res_value})
return res
product_id_change = onchange_product_id
'name': name,
'origin': procurement.origin,
'partner_id': partner_id,
- 'partner_address_id': address_id,
'location_id': procurement.location_id.id,
'warehouse_id': warehouse_id and warehouse_id[0] or False,
'pricelist_id': pricelist_id,