summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorNicolas Martinelli <nim@odoo.com>2015-09-02 08:19:56 +0200
committerNicolas Martinelli <nim@odoo.com>2015-09-02 08:19:56 +0200
commitcb01be235e73cf948418e41642d4906ff84906a6 (patch)
tree99679759dff4d78ad4ab89110fc0822d30841cd3 /addons
parent826661d53ab67ee1dd7228ccc41b04c9cc7b2a98 (diff)
[IMP] purchase: adaptation due to the new Purchase module
Major changes: - No generation of invoice from PO - Remove workflow Reason: complete rewrite of the Purchase module. Responsible: fp, nim
Diffstat (limited to 'addons')
-rw-r--r--addons/purchase/__init__.py2
-rw-r--r--addons/purchase/__openerp__.py10
-rw-r--r--addons/purchase/company.py21
-rw-r--r--addons/purchase/invoice.py132
-rw-r--r--addons/purchase/invoice_view.xml68
-rw-r--r--addons/purchase/partner.py39
-rw-r--r--addons/purchase/purchase.py2403
-rw-r--r--addons/purchase/purchase_order_demo.yml87
-rw-r--r--addons/purchase/purchase_view.xml356
-rw-r--r--addons/purchase/purchase_workflow.xml231
-rw-r--r--addons/purchase/report/purchase_report.py27
-rw-r--r--addons/purchase/report/purchase_report_view.xml3
-rw-r--r--addons/purchase/res_config.py9
-rw-r--r--addons/purchase/res_config_view.xml6
-rw-r--r--addons/purchase/stock.py149
-rw-r--r--addons/purchase/stock_view.xml49
-rw-r--r--addons/purchase/test/anglo_saxon.yml247
-rw-r--r--addons/purchase/test/anglo_saxon_avg_fifo.yml247
-rw-r--r--addons/purchase/test/average_price.yml14
-rw-r--r--addons/purchase/test/costmethodchange.yml14
-rw-r--r--addons/purchase/test/fifo_price.yml28
-rw-r--r--addons/purchase/test/fifo_returns.yml8
-rw-r--r--addons/purchase/test/process/cancel_order.yml34
-rw-r--r--addons/purchase/test/process/generate_invoice_from_reception.yml29
-rw-r--r--addons/purchase/test/process/invoice_on_poline.yml25
-rw-r--r--addons/purchase/test/process/merge_order.yml44
-rw-r--r--addons/purchase/test/process/rfq2order2done.yml70
-rw-r--r--addons/purchase/tests/__init__.py3
-rw-r--r--addons/purchase/tests/test_onchange_product_id.py44
-rw-r--r--addons/purchase/tests/test_purchase_to_invoice.py60
-rw-r--r--addons/purchase/views/report_purchaseorder.xml4
-rw-r--r--addons/purchase/wizard/__init__.py5
-rw-r--r--addons/purchase/wizard/purchase_line_invoice.py99
-rw-r--r--addons/purchase/wizard/purchase_line_invoice_view.xml32
-rw-r--r--addons/purchase/wizard/purchase_order_group.py61
-rw-r--r--addons/purchase/wizard/purchase_order_group_view.xml42
36 files changed, 1130 insertions, 3572 deletions
diff --git a/addons/purchase/__init__.py b/addons/purchase/__init__.py
index e53ec6f2f7b..c771f67af74 100644
--- a/addons/purchase/__init__.py
+++ b/addons/purchase/__init__.py
@@ -5,8 +5,8 @@ import mail_mail
import purchase
import partner
import stock
-import wizard
import report
import stock
import company
import res_config
+import invoice
diff --git a/addons/purchase/__openerp__.py b/addons/purchase/__openerp__.py
index 061b60a1d80..30df961e09d 100644
--- a/addons/purchase/__openerp__.py
+++ b/addons/purchase/__openerp__.py
@@ -30,13 +30,11 @@ Dashboard / Reports for Purchase Management will include:
'data': [
'security/purchase_security.xml',
'security/ir.model.access.csv',
- 'purchase_workflow.xml',
+ 'invoice_view.xml',
'purchase_sequence.xml',
'company_view.xml',
'purchase_data.xml',
'purchase_data.yml',
- 'wizard/purchase_order_group_view.xml',
- 'wizard/purchase_line_invoice_view.xml',
'purchase_report.xml',
'purchase_view.xml',
'stock_view.xml',
@@ -57,15 +55,9 @@ Dashboard / Reports for Purchase Management will include:
'test/fifo_returns.yml',
# 'test/costmethodchange.yml',
'test/process/cancel_order.yml',
- 'test/process/rfq2order2done.yml',
- 'test/process/generate_invoice_from_reception.yml',
- 'test/process/merge_order.yml',
- 'test/process/invoice_on_poline.yml',
'test/ui/duplicate_order.yml',
'test/ui/delete_order.yml',
'test/average_price.yml',
- 'test/anglo_saxon.yml',
- 'test/anglo_saxon_avg_fifo.yml',
],
'demo': [
'purchase_order_demo.yml',
diff --git a/addons/purchase/company.py b/addons/purchase/company.py
index 3bee858be50..c0f15294f03 100644
--- a/addons/purchase/company.py
+++ b/addons/purchase/company.py
@@ -1,18 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.osv import osv,fields
+from openerp import api, fields, models, _
-class company(osv.osv):
+class company(models.Model):
_inherit = 'res.company'
- _columns = {
- 'po_lead': fields.float(
- 'Purchase Lead Time', required=True,
- help="Margin of error for vendor lead times. When the system "\
- "generates Purchase Orders for procuring products, "\
- "they will be scheduled that many days earlier "\
- "to cope with unexpected vendor delays."),
- }
- _defaults = {
- 'po_lead': lambda *a: 1.0,
- }
+ po_lead = fields.Float(string='Purchase Lead Time', required=True,
+ help="Margin of error for vendor lead times. When the system "\
+ "generates Purchase Orders for procuring products, "\
+ "they will be scheduled that many days earlier "\
+ "to cope with unexpected vendor delays.", default=1.0)
+
diff --git a/addons/purchase/invoice.py b/addons/purchase/invoice.py
new file mode 100644
index 00000000000..24e0d0a815d
--- /dev/null
+++ b/addons/purchase/invoice.py
@@ -0,0 +1,132 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from openerp import api, fields, models, _
+from openerp.tools.float_utils import float_compare
+
+
+class AccountInvoice(models.Model):
+ _inherit = 'account.invoice'
+
+ purchase_id = fields.Many2one('purchase.order', string='Purchase Order', domain=[('invoice_status', '=', 'to invoice')])
+
+ # Load all unsold PO lines
+ @api.onchange('purchase_id')
+ def purchase_order_change(self):
+ result = []
+ if not self.purchase_id:
+ return {}
+ if not self.partner_id:
+ self.partner_id = self.purchase_id.partner_id.id
+ done = False
+ for line in self.purchase_id.order_line:
+ if line.product_id.purchase_method == 'purchase':
+ qty = line.product_qty - line.qty_invoiced
+ else:
+ qty = line.qty_received - line.qty_invoiced
+ if float_compare(qty, 0.0, precision_rounding=line.product_uom.rounding) <= 0:
+ continue
+ done = True
+ account = self.env['account.invoice.line'].get_invoice_line_account('in_invoice', line.product_id, self.purchase_id.fiscal_position_id, self.env.user.company_id)
+ taxes = line.taxes_id or line.product_id.supplier_taxes_id
+ invoice_line_tax_ids = self.purchase_id.fiscal_position_id.map_tax(taxes)
+ result.append({
+ 'purchase_line_id': line.id,
+ 'name': line.name,
+ 'origin': self.purchase_id.origin,
+ 'uom_id': line.product_uom.id,
+ 'product_id': line.product_id.id,
+ 'account_id': account,
+ 'price_unit': line.price_unit,
+ 'quantity': qty,
+ 'discount': 0.0,
+ 'account_analytic_id': line.account_analytic_id.id,
+ 'invoice_line_tax_ids': invoice_line_tax_ids
+ })
+ self.invoice_line_ids = result
+ if not done:
+ warning = {
+ 'title': _('Bill Control Warning!'),
+ 'message' : _('None of the products from this purchase order can be billed. You should control the purchase order.')
+ }
+ return {'warning': warning}
+
+
+class AccountInvoiceLine(models.Model):
+ """ Override AccountInvoice_line to add the link to the purchase order line it is related to"""
+ _inherit = 'account.invoice.line'
+ purchase_line_id = fields.Many2one('purchase.order.line', 'Purchase Order Line', ondelete='set null', select=True, readonly=True)
+
+ @api.model
+ def move_line_get(self, invoice_id):
+ res = super(AccountInvoiceLine, self).move_line_get(invoice_id)
+ invoice = self.browse(invoice_id)
+ if self.env.user.company_id.anglo_saxon_accounting:
+ if invoice.type in ['in_invoice', 'in_refund']:
+ for i_line in invoice.invoice_line_ids:
+ res.extend(self._anglo_saxon_purchase_move_lines(i_line, res))
+ return res
+
+ @api.model
+ def _anglo_saxon_purchase_move_lines(self, i_line, res):
+ """Return the additional move lines for purchase invoices and refunds.
+
+ i_line: An account.invoice.line object.
+ res: The move line entries produced so far by the parent move_line_get.
+ """
+ inv = i_line.invoice_id
+ company_currency = inv.company_id.currency_id
+ if i_line.product_id and i_line.product_id.valuation == 'real_time':
+ if i_line.product_id.type != 'service':
+ # get the price difference account at the product
+ acc = i_line.product_id.property_account_creditor_price_difference and i_line.product_id.property_account_creditor_price_difference.id
+ if not acc:
+ # if not found on the product get the price difference account at the category
+ acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ and i_line.product_id.categ_id.property_account_creditor_price_difference_categ.id
+ a = None
+
+ # oa will be the stock input account
+ # first check the product, if empty check the category
+ oa = i_line.product_id.property_stock_account_input and i_line.product_id.property_stock_account_input.id
+ if not oa:
+ oa = i_line.product_id.categ_id.property_stock_account_input_categ_id and i_line.product_id.categ_id.property_stock_account_input_categ_id.id
+ if oa:
+ # get the fiscal position
+ fpos = i_line.invoice_id.fiscal_position_id or False
+ a = fpos.map_account(oa)
+ diff_res = []
+ account_prec = inv.company_id.currency_id.decimal_places
+ # calculate and write down the possible price difference between invoice price and product price
+ for line in res:
+ if line.get('invl_id', 0) == i_line.id and a == line['account_id']:
+ uom = i_line.product_id.uom_id
+ valuation_price_unit = uom._compute_price(i_line.product_id.standard_price, i_line.uom_id.id)
+ if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id:
+ #for average/fifo/lifo costing method, fetch real cost price from incomming moves
+ stock_move_obj = self.env['stock.move']
+ valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id)], limit=1)
+ if valuation_stock_move:
+ valuation_price_unit = valuation_stock_move[0].price_unit
+ if inv.currency_id.id != company_currency.id:
+ valuation_price_unit = company_currency.compute(inv.currency_id, valuation_price_unit, context={'date': inv.date_invoice})
+ if valuation_price_unit != i_line.price_unit and line['price_unit'] == i_line.price_unit and acc:
+ # price with discount and without tax included
+ price_unit = self.env['account.tax'].compute_all(line['taxes'], i_line.price_unit * (1-(i_line.discount or 0.0)/100.0),\
+ inv.currency_id.id, line['quantity'])['total_excluded']
+ price_line = round(valuation_price_unit * line['quantity'], account_prec)
+ price_diff = round(price_unit - price_line, account_prec)
+ line.update({'price': price_line})
+ diff_res.append({
+ 'type': 'src',
+ 'name': i_line.name[:64],
+ 'price_unit': round(price_diff / line['quantity'], account_prec),
+ 'quantity': line['quantity'],
+ 'price': price_diff,
+ 'account_id': acc,
+ 'product_id': line['product_id'],
+ 'uom_id': line['uom_id'],
+ 'account_analytic_id': line['account_analytic_id'],
+ 'taxes': line.get('taxes', []),
+ })
+ return diff_res
+ return []
diff --git a/addons/purchase/invoice_view.xml b/addons/purchase/invoice_view.xml
new file mode 100644
index 00000000000..4f5eba1d9eb
--- /dev/null
+++ b/addons/purchase/invoice_view.xml
@@ -0,0 +1,68 @@
+<odoo>
+ <record id="action_invoice_pending" model="ir.actions.act_window">
+ <field name="name">Vendor Bills</field>
+ <field name="res_model">account.invoice</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form,calendar,pivot,graph</field>
+ <field eval="False" name="view_id"/>
+ <field name="domain">[('type','=','in_invoice')]</field>
+ <field name="context">{'default_type':'in_invoice', 'type':'in_invoice', 'journal_type': 'purchase'}</field>
+ <field name="search_view_id" ref="account.view_account_invoice_filter"/>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ Click to create a draft invoice.
+ </p><p>
+ Use this menu to control the invoices to be received from your
+ vendor. When registering a new bill, set the purchase order
+ and Odoo will fill the bill automatically according to ordered
+ or received quantities.
+ </p>
+ </field>
+ </record>
+
+ <record id="purchase_open_invoice" model="ir.actions.act_window">
+ <field name="name">Vendor Bills</field>
+ <field name="res_model">account.invoice</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,kanban,form,calendar,pivot,graph</field>
+ <field eval="False" name="view_id"/>
+ <field name="domain">[('type','in',('in_invoice', 'in_refund')),('purchase_id','=',active_id)]</field>
+ <field name="context">{'default_type': 'in_invoice', 'type': 'in_invoice', 'journal_type': 'purchase', 'default_purchase_id': active_id}</field>
+ <field name="search_view_id" ref="account.view_account_invoice_filter"/>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ Click to record a vendor bill related to this purchase.
+ </p>
+ <p>
+ You can control the invoice from your vendor according to
+ what you purchased (services) or received (products).
+ </p>
+ </field>
+ </record>
+ <record id="action_invoice_supplier_tree1_view1" model="ir.actions.act_window.view">
+ <field eval="1" name="sequence"/>
+ <field name="view_mode">tree</field>
+ <field name="view_id" ref="account.invoice_supplier_tree"/>
+ <field name="act_window_id" ref="purchase_open_invoice"/>
+ </record>
+ <record id="action_invoice__supplier_tree1_view2" model="ir.actions.act_window.view">
+ <field eval="2" name="sequence"/>
+ <field name="view_mode">form</field>
+ <field name="view_id" ref="account.invoice_supplier_form"/>
+ <field name="act_window_id" ref="purchase_open_invoice"/>
+ </record>
+
+ <record id="view_invoice_supplier_purchase_form" model="ir.ui.view">
+ <field name="name">account.invoice.supplier.purchase</field>
+ <field name="model">account.invoice</field>
+ <field name="inherit_id" ref="account.invoice_supplier_form"/>
+ <field name="arch" type="xml">
+ <field name="reference" position="after" >
+ <field name="purchase_id" domain="[('invoice_status', '=', 'to invoice'), ('partner_id', '=', partner_id)]" attrs="{'invisible': [('state', '&lt;&gt;', 'draft')]}" class="oe_edit_only"/>
+ </field>
+ <xpath expr="//field[@name='invoice_line_ids']/tree/field[@name='company_id']" position="after">
+ <field name="purchase_line_id" invisible="1"/>
+ </xpath>
+ </field>
+ </record>
+</odoo>
diff --git a/addons/purchase/partner.py b/addons/purchase/partner.py
index 86aa671dd5b..9b71ff824b0 100644
--- a/addons/purchase/partner.py
+++ b/addons/purchase/partner.py
@@ -1,32 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp.osv import fields, osv
+from openerp import api, fields, models
-class res_partner(osv.osv):
+class res_partner(models.Model):
_name = 'res.partner'
_inherit = 'res.partner'
- def _purchase_invoice_count(self, cr, uid, ids, field_name, arg, context=None):
- PurchaseOrder = self.pool['purchase.order']
- Invoice = self.pool['account.invoice']
- return {
- partner_id: {
- 'purchase_order_count': PurchaseOrder.search_count(cr,uid, [('partner_id', 'child_of', partner_id)], context=context),
- 'supplier_invoice_count': Invoice.search_count(cr,uid, [('partner_id', 'child_of', partner_id), ('type','=','in_invoice')], context=context)
- }
- for partner_id in ids
- }
+ @api.multi
+ def _purchase_invoice_count(self):
+ PurchaseOrder = self.env['purchase.order']
+ Invoice = self.env['account.invoice']
+ for partner in self:
+ partner.purchase_order_count = PurchaseOrder.search_count([('partner_id', 'child_of', partner.id)])
+ partner.supplier_invoice_count = Invoice.search_count([('partner_id', 'child_of', partner.id), ('type', '=', 'in_invoice')])
- def _commercial_fields(self, cr, uid, context=None):
- return super(res_partner, self)._commercial_fields(cr, uid, context=context)
+ @api.model
+ def _commercial_fields(self):
+ return super(res_partner, self)._commercial_fields()
- _columns = {
- 'property_purchase_currency_id': fields.property(
- type='many2one',
- relation='res.currency',
- string="Supplier Currency",
- help="This currency will be used, instead of the default one, for purchases from the current partner"),
- 'purchase_order_count': fields.function(_purchase_invoice_count, string='# of Purchase Order', type='integer', multi="count"),
- 'supplier_invoice_count': fields.function(_purchase_invoice_count, string='# Vendor Bills', type='integer', multi="count"),
- }
+ property_purchase_currency_id = fields.Many2one('res.currency', string="Supplier Currency",\
+ help="This currency will be used, instead of the default one, for purchases from the current partner")
+ purchase_order_count = fields.Integer(compute='_purchase_invoice_count', string='# of Purchase Order')
+ supplier_invoice_count = fields.Integer(compute='_purchase_invoice_count', string='# Vendor Bills')
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index b523a4593f3..453b3c7c423 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -1,518 +1,213 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from openerp import SUPERUSER_ID, workflow
from datetime import datetime
-import time
from dateutil.relativedelta import relativedelta
-from operator import attrgetter
-from openerp.osv import fields, osv
+from openerp import api, fields, models, _
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools.translate import _
+from openerp.tools.float_utils import float_is_zero, float_compare
import openerp.addons.decimal_precision as dp
-from openerp.osv.orm import browse_record_list, browse_record, browse_null
-from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
-from openerp.tools.float_utils import float_compare
from openerp.exceptions import UserError
-class purchase_order(osv.osv):
-
- def _amount_all(self, cr, uid, ids, field_name, arg, context=None):
- res = {}
- cur_obj = self.pool.get('res.currency')
- line_obj = self.pool['purchase.order.line']
- for order in self.browse(cr, uid, ids, context=context):
- res[order.id] = {
- 'amount_untaxed': 0.0,
- 'amount_tax': 0.0,
- 'amount_total': 0.0,
- }
- val = val1 = 0.0
- cur = order.currency_id
- for line in order.order_line:
- line_price = line_obj._calc_line_base_price(cr, uid, line,
- context=context)
- line_qty = line_obj._calc_line_quantity(cr, uid, line,
- context=context)
- taxes = line.taxes_id.compute_all(line_price, cur, line_qty,
- line.product_id, order.partner_id)
- val1 += line.price_subtotal
- for c in taxes['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)
- res[order.id]['amount_total'] = res[order.id]['amount_untaxed'] + res[order.id]['amount_tax']
- return res
-
- def _set_minimum_planned_date(self, cr, uid, ids, name, value, arg, context=None):
- if not value: return False
- if type(ids)!=type([]):
- ids=[ids]
- pol_obj = self.pool.get('purchase.order.line')
- for po in self.browse(cr, uid, ids, context=context):
- if po.order_line:
- pol_ids = pol_obj.search(cr, uid, [
- ('order_id', '=', po.id), '|', ('date_planned', '=', po.minimum_planned_date), ('date_planned', '<', value)
- ], context=context)
- pol_obj.write(cr, uid, pol_ids, {'date_planned': value}, context=context)
- self.invalidate_cache(cr, uid, context=context)
- return True
-
- def _minimum_planned_date(self, cr, uid, ids, field_name, arg, context=None):
- res={}
- purchase_obj=self.browse(cr, uid, ids, context=context)
- for purchase in purchase_obj:
- res[purchase.id] = False
- if purchase.order_line:
- min_date=purchase.order_line[0].date_planned
- for line in purchase.order_line:
- if line.state == 'cancel':
- continue
- if line.date_planned < min_date:
- min_date=line.date_planned
- res[purchase.id]=min_date
- return res
+class PurchaseOrder(models.Model):
+ _name = "purchase.order"
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+ _description = "Purchase Order"
+ _order = 'date_order desc, id desc'
+ @api.depends('order_line.product_qty', 'order_line.price_unit', 'order_line.taxes_id')
+ def _amount_all(self):
+ amount_untaxed = amount_tax = 0.0
+ for line in self.order_line:
+ amount_untaxed += line.price_subtotal
+ amount_tax += line.price_tax
+ self.update({
+ 'amount_untaxed': self.currency_id.round(amount_untaxed),
+ 'amount_tax': self.currency_id.round(amount_tax),
+ 'amount_total': amount_untaxed + amount_tax,
+ })
- def _invoiced_rate(self, cursor, user, ids, name, arg, context=None):
- res = {}
- for purchase in self.browse(cursor, user, ids, context=context):
- tot = 0.0
- for invoice in purchase.invoice_ids:
- if invoice.state not in ('draft','cancel'):
- tot += invoice.amount_untaxed
- if purchase.amount_untaxed:
- res[purchase.id] = tot * 100.0 / purchase.amount_untaxed
- else:
- res[purchase.id] = 0.0
- return res
+ @api.multi
+ def _inverse_date_planned(self):
+ for order in self:
+ order.order_line.write({'date_planned': self.date_planned})
- def _shipped_rate(self, cr, uid, ids, name, arg, context=None):
- if not ids: return {}
- res = {}
- for id in ids:
- res[id] = [0.0,0.0]
- cr.execute('''SELECT
- p.order_id, sum(m.product_qty), m.state
- FROM
- stock_move m
- LEFT JOIN
- purchase_order_line p on (p.id=m.purchase_line_id)
- WHERE
- p.order_id IN %s GROUP BY m.state, p.order_id''',(tuple(ids),))
- for oid,nbr,state in cr.fetchall():
- if state=='cancel':
+ @api.depends('order_line.date_planned')
+ def _compute_date_planned(self):
+ for order in self:
+ min_date = False
+ for line in order.order_line:
+ if not min_date or line.date_planned < min_date:
+ min_date = line.date_planned
+ if min_date:
+ order.date_planned = min_date
+
+ @api.depends('state', 'order_line.qty_invoiced', 'order_line.product_qty')
+ def _get_invoiced(self):
+ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
+ for order in self:
+ if order.state != 'purchase':
+ order.invoice_status = 'no'
continue
- if state=='done':
- res[oid][0] += nbr or 0.0
- res[oid][1] += nbr or 0.0
- else:
- res[oid][1] += nbr or 0.0
- for r in res:
- if not res[r][1]:
- res[r] = 0.0
- else:
- res[r] = 100.0 * res[r][0] / res[r][1]
- return res
- def _get_order(self, cr, uid, ids, context=None):
- result = {}
- for line in self.pool.get('purchase.order.line').browse(cr, uid, ids, context=context):
- result[line.order_id.id] = True
- return result.keys()
+ if any(float_compare(line.qty_invoiced, line.product_qty, precision_digits=precision) == -1 for line in order.order_line):
+ order.invoice_status = 'to invoice'
+ elif all(float_compare(line.qty_invoiced, line.product_qty, precision_digits=precision) >= 0 for line in order.order_line):
+ order.invoice_status = 'invoiced'
+ else:
+ order.invoice_status = 'no'
- def _get_purchase_order(self, cr, uid, ids, context=None):
- result = {}
- for order in self.browse(cr, uid, ids, context=context):
- result[order.id] = True
- return result.keys()
-
- def _invoiced(self, cursor, user, ids, name, arg, context=None):
- res = {}
- for purchase in self.browse(cursor, user, ids, context=context):
- res[purchase.id] = all(line.invoiced for line in purchase.order_line if line.state != 'cancel')
- return res
-
- def _get_journal(self, cr, uid, context=None):
- if context is None:
- context = {}
- user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
- company_id = context.get('company_id', user.company_id.id)
- journal_obj = self.pool.get('account.journal')
- res = journal_obj.search(cr, uid, [('type', '=', 'purchase'),
- ('company_id', '=', company_id)],
- limit=1)
- return res and res[0] or False
-
- def _get_picking_in(self, cr, uid, context=None):
- obj_data = self.pool.get('ir.model.data')
- 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)
+ @api.depends('order_line.invoice_lines.invoice_id.state')
+ def _compute_invoice(self):
+ for order in self:
+ invoices = self.env['account.invoice']
+ for line in order.order_line:
+ invoices |= line.invoice_lines.mapped('invoice_id')
+ order.invoice_ids = invoices
+ order.invoice_count = len(invoices)
+
+ @api.model
+ def _default_picking_type(self):
+ type_obj = self.env['stock.picking.type']
+ company_id = self.env.user.company_id.id
+ types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id.company_id', '=', company_id)])
if not types:
- types = type_obj.search(cr, uid, [('code', '=', 'incoming'), ('warehouse_id', '=', False)], context=context)
- if not types:
- raise UserError(_("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 = {}
- for po_id in ids:
- res[po_id] = []
- query = """
- SELECT picking_id, po.id FROM stock_picking p, stock_move m, purchase_order_line pol, purchase_order po
- WHERE po.id in %s and po.id = pol.order_id and pol.id = m.purchase_line_id and m.picking_id = p.id
- GROUP BY picking_id, po.id
-
- """
- cr.execute(query, (tuple(ids), ))
- picks = cr.fetchall()
- for pick_id, po_id in picks:
- res[po_id].append(pick_id)
- return res
+ types = type_obj.search([('code', '=', 'incoming'), ('warehouse_id', '=', False)])
+ return types[0].id if types else False
- 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)
- }
+ @api.depends('order_line.move_ids.picking_id')
+ def _compute_picking(self):
+ for order in self:
+ pickings = self.env['stock.picking']
+ for line in order.order_line:
+ moves = line.move_ids.filtered(lambda r: r.state != 'cancel')
+ pickings |= moves.mapped('picking_id')
+ order.picking_ids = pickings
+ order.picking_count = len(pickings)
+
+ READONLY_STATES = {
+ 'purchase': [('readonly', True)],
+ 'done': [('readonly', True)],
+ 'cancel': [('readonly', True)],
+ }
- STATE_SELECTION = [
+ name = fields.Char('Order Reference', required=True, select=True, copy=False, default='New')
+ origin = fields.Char('Source Document', copy=False,\
+ help="Reference of the document that generated this purchase order "
+ "request (e.g. a sale order or an internal procurement request)")
+ partner_ref = fields.Char('Vendor Reference', copy=False,\
+ help="Reference of the sales order or bid sent by the vendor. "
+ "It's used to do the matching when you receive the "
+ "products as this reference is usually written on the "
+ "delivery order sent by your vendor.")
+ date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, select=True, copy=False, default=fields.Date.context_today,\
+ help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
+ date_approve = fields.Date('Approval Date', readonly=1, select=True, copy=False)
+ partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
+ dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,\
+ help="Put an address if you want to deliver directly from the vendor to the customer. "\
+ "Otherwise, keep empty to deliver to your own company.")
+ currency_id = fields.Many2one('res.currency', 'Currency', required=True, states=READONLY_STATES,\
+ default=lambda self: self.env.user.company_id.currency_id.id)
+ state = fields.Selection([
('draft', 'Draft RFQ'),
('sent', 'RFQ Sent'),
- ('bid', 'Bid Received'),
- ('confirmed', 'Waiting Approval'),
- ('approved', 'Purchase Confirmed'),
- ('except_picking', 'Shipping Exception'),
- ('except_invoice', 'Invoice Exception'),
+ ('to approve', 'To Approve'),
+ ('purchase', 'Purchase Order'),
('done', 'Done'),
('cancel', 'Cancelled')
- ]
-
- READONLY_STATES = {
- 'confirmed': [('readonly', True)],
- 'approved': [('readonly', True)],
- 'done': [('readonly', True)]
- }
-
- _columns = {
- '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('Vendor Reference', states={'confirmed':[('readonly',True)],
- 'approved':[('readonly',True)],
- 'done':[('readonly',True)]},
- copy=False,
- help="Reference of the sales order or bid sent by your vendor. "
- "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 vendor."),
- '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', 'Vendor', required=True, states=READONLY_STATES,
- change_default=True, track_visibility='always'),
- 'dest_address_id':fields.many2one('res.partner', 'Customer Address (Direct Delivery)',
- states=READONLY_STATES,
- help="Put an address if you want to deliver directly from the vendor to the customer. " \
- "Otherwise, keep empty to deliver to your own company."
- ),
- 'location_id': fields.many2one('stock.location', 'Destination', required=True, domain=[('usage','<>','view')], states=READONLY_STATES),
- 'currency_id': fields.many2one('res.currency','Currency', required=True, states=READONLY_STATES),
- '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 vendor 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', 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', 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)],'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 receipts are validated."
- ),
- 'minimum_planned_date':fields.function(_minimum_planned_date, fnct_inv=_set_minimum_planned_date, string='Expected Date', type='datetime', select=True, help="This is computed as the minimum scheduled date of all purchase order lines' products.",
- store = {
- 'purchase.order.line': (_get_order, ['date_planned'], 10),
- 'purchase.order': (_get_purchase_order, ['order_line'], 10),
- }
- ),
- 'amount_untaxed': fields.function(_amount_all, digits=0, string='Untaxed Amount',
- store={
- 'purchase.order.line': (_get_order, None, 10),
- }, multi="sums", help="The amount without tax", track_visibility='always'),
- 'amount_tax': fields.function(_amount_all, digits=0, string='Taxes',
- store={
- 'purchase.order.line': (_get_order, None, 10),
- }, multi="sums", help="The tax amount"),
- 'amount_total': fields.function(_amount_all, digits=0, string='Total',
- store={
- 'purchase.order.line': (_get_order, None, 10),
- }, multi="sums", help="The total amount"),
- 'fiscal_position_id': fields.many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position'),
- 'payment_term_id': fields.many2one('account.payment.term', 'Payment Term'),
- 'incoterm_id': fields.many2one('stock.incoterms', 'Incoterm', help="International Commercial Terms are a series of predefined commercial terms used in international transactions."),
- '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)]}),
- 'journal_id': fields.many2one('account.journal', 'Journal'),
- 'bid_date': fields.date('Bid Received On', readonly=True, help="Date on which the bid was received"),
- 'bid_validity': fields.date('Bid Valid Until', help="Date on which the bid expired"),
- '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),
- 'related_usage': fields.related('location_id', 'usage', type='char'),
- '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),
- 'group_id': fields.many2one('procurement.group', string="Procurement Group"),
- }
- _defaults = {
- 'date_order': fields.datetime.now,
- 'state': 'draft',
- 'name': lambda obj, cr, uid, context: '/',
- 'shipped': 0,
- 'invoice_method': 'order',
- 'invoiced': 0,
- 'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'purchase.order', context=c),
- 'journal_id': _get_journal,
- 'currency_id': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id,
- 'picking_type_id': _get_picking_in,
- }
- _sql_constraints = [
- ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
- ]
- _name = "purchase.order"
- _inherit = ['mail.thread', 'ir.needaction_mixin']
- _description = "Purchase Order"
- _order = 'date_order desc, id desc'
-
- def create(self, cr, uid, vals, context=None):
- if vals.get('name', '/') == '/':
- 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
-
- def unlink(self, cr, uid, ids, context=None):
- purchase_orders = self.read(cr, uid, ids, ['state'], context=context)
- unlink_ids = []
- for s in purchase_orders:
- if s['state'] in ['draft','cancel']:
- unlink_ids.append(s['id'])
- else:
+ ], string='Status', readonly=True, select=True, copy=False, default='draft')
+ order_line = fields.One2many('purchase.order.line', 'order_id', string='Order Lines', states=READONLY_STATES, copy=True)
+ notes = fields.Text('Terms and Conditions')
+
+ invoice_count = fields.Integer(compute="_compute_invoice", string='# of Invoices', copy=False, default=0)
+ invoice_ids = fields.Many2many('account.invoice', compute="_compute_invoice", string='Invoices', copy=False)
+ invoice_status = fields.Selection([
+ ('no', 'Not purchased'),
+ ('to invoice', 'Waiting Invoices'),
+ ('invoiced', 'Invoice Received'),
+ ], string='Invoice Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no')
+
+ picking_count = fields.Integer(compute='_compute_picking', string='Receptions', default=0)
+ picking_ids = fields.One2many('stock.picking', compute='_compute_picking', string='Receptions', copy=False)
+
+ date_planned = fields.Datetime(string='Scheduled Date', compute='_compute_date_planned', inverse='_inverse_date_planned', required=True, select=True, oldname='minimum_planned_date')
+
+ amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', track_visibility='always')
+ amount_tax = fields.Monetary(string='Taxes', store=True, readonly=True, compute='_amount_all')
+ amount_total = fields.Monetary(string='Total', store=True, readonly=True, compute='_amount_all')
+
+ fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position')
+ payment_term_id = fields.Many2one('account.payment.term', 'Payment Term')
+ incoterm_id = fields.Many2one('stock.incoterms', 'Incoterm', help="International Commercial Terms are a series of predefined commercial terms used in international transactions.")
+
+ product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
+ create_uid = fields.Many2one('res.users', 'Responsible')
+ company_id = fields.Many2one('res.company', 'Company', required=True, select=1, states=READONLY_STATES, default=lambda self: self.env.user.company_id.id)
+
+ picking_type_id = fields.Many2one('stock.picking.type', 'Deliver To', states=READONLY_STATES, required=True, default=_default_picking_type,\
+ help="This will determine picking type of incoming shipment")
+ group_id = fields.Many2one('procurement.group', string="Procurement Group")
+
+ @api.model
+ def create(self, vals):
+ if vals.get('name', 'New') == 'New':
+ vals['name'] = self.env['ir.sequence'].next_by_code('purchase.order') or '/'
+ return super(PurchaseOrder, self).create(vals)
+
+ @api.multi
+ def unlink(self):
+ for order in self:
+ if order.state not in ['draft', 'cancel']:
raise UserError(_('In order to delete a purchase order, you must cancel it first.'))
+ return super(PurchaseOrder, self).unlink()
- # automatically sending subflow.delete upon deletion
- self.signal_workflow(cr, uid, unlink_ids, 'purchase_cancel')
-
- return super(purchase_order, self).unlink(cr, uid, unlink_ids, context=context)
-
- def _track_subtype(self, cr, uid, ids, init_values, context=None):
- record = self.browse(cr, uid, ids[0], context=context)
- if 'state' in init_values and record.state == 'approved':
+ @api.multi
+ def _track_subtype(self, init_values):
+ self.ensure_one()
+ if 'state' in init_values and self.state == 'approved':
return 'purchase.mt_rfq_approved'
- elif 'state' in init_values and record.state == 'confirmed':
+ elif 'state' in init_values and self.state == 'confirmed':
return 'purchase.mt_rfq_confirmed'
- elif 'state' in init_values and record.state == 'done':
+ elif 'state' in init_values and self.state == 'done':
return 'purchase.mt_rfq_done'
- return super(purchase_order, self)._track_subtype(cr, uid, ids, init_values, context=context)
-
- def set_order_line_status(self, cr, uid, ids, status, context=None):
- line = self.pool.get('purchase.order.line')
- order_line_ids = []
- proc_obj = self.pool.get('procurement.order')
- for order in self.browse(cr, uid, ids, context=context):
- if status in ('draft', 'cancel'):
- order_line_ids += [po_line.id for po_line in order.order_line]
- else: # Do not change the status of already cancelled lines
- order_line_ids += [po_line.id for po_line in order.order_line if po_line.state != 'cancel']
- if order_line_ids:
- line.write(cr, uid, order_line_ids, {'state': status}, context=context)
- return True
-
- def button_dummy(self, cr, uid, ids, context=None):
- return True
-
- #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, context=context)
- if supplier:
- location_id = supplier.property_stock_customer.id
- values.update({'location_id': location_id})
- return {'value':values}
-
- def onchange_picking_type_id(self, cr, uid, ids, picking_type_id, context=None):
- value = {}
- if picking_type_id:
- picktype = self.pool.get("stock.picking.type").browse(cr, uid, picking_type_id, context=context)
- if picktype.default_location_dest_id:
- value.update({'location_id': picktype.default_location_dest_id.id, 'related_usage': picktype.default_location_dest_id.usage})
- value.update({'related_location_id': picktype.default_location_dest_id.id})
- return {'value': value}
-
- 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_id': False,
- 'payment_term_id': False,
- 'currency_id': self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id.id,
- }}
-
- company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
- if not company_id:
- raise osv.except_osv(_('Error!'), _('There is no default company for the current user!'))
- fp = self.pool['account.fiscal.position'].get_fiscal_position(cr, uid, company_id, partner_id, context=context)
- supplier = partner.browse(cr, uid, partner_id, context=context)
- return {'value': {
- 'fiscal_position_id': fp or supplier.property_account_position_id and supplier.property_account_position_id.id or False,
- 'payment_term_id': supplier.property_supplier_payment_term_id.id or False,
- 'currency_id': supplier.property_purchase_currency_id.id or self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id.id
- }}
-
- 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')
-
- action_id = mod_obj.xmlid_to_res_id(cr, uid, 'account.action_invoice_tree2')
- result = act_obj.read(cr, uid, action_id, context=context)
- 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 UserError(_('Please create Invoices.'))
-
- if len(inv_ids) > 1:
- result['domain'] = [('id', 'in', inv_ids)]
- else:
- res = mod_obj.xmlid_to_res_id(cr, uid, 'account.invoice_supplier_form')
- result['views'] = [(res, '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 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
- inv_ids = []
- for po in self.browse(cr, uid, ids, context=context):
- if po.invoice_method == 'manual':
- if not po.invoice_ids:
- context.update({'active_ids' : [line.id for line in po.order_line if line.state != 'cancel']})
- wizard_obj.makeInvoices(cr, uid, [], context=context)
-
- for po in self.browse(cr, uid, ids, context=context):
- inv_ids+= [invoice.id for invoice in po.invoice_ids]
- res = mod_obj.get_object_reference(cr, uid, 'account', 'invoice_supplier_form')
- res_id = res and res[1] or False
-
- return {
- 'name': _('Vendor Bills'),
- 'view_type': 'form',
- 'view_mode': 'form',
- 'view_id': [res_id],
- 'res_model': 'account.invoice',
- 'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
- 'type': 'ir.actions.act_window',
- 'target': 'current',
- 'res_id': inv_ids and inv_ids[0] or False,
- }
-
- def view_picking(self, cr, uid, ids, context=None):
- '''
- This function returns an action that display existing picking orders of given purchase order ids.
- '''
- if context is None:
- context = {}
- mod_obj = self.pool.get('ir.model.data')
- dummy, action_id = tuple(mod_obj.get_object_reference(cr, uid, 'stock', 'action_picking_tree'))
- action = self.pool.get('ir.actions.act_window').read(cr, uid, action_id, context=context)
-
- pick_ids = []
- for po in self.browse(cr, uid, ids, context=context):
- pick_ids += [picking.id for picking in po.picking_ids]
-
- #override the context to get rid of the default filtering on picking type
- action['context'] = {}
- #choose the view_mode accordingly
- if len(pick_ids) > 1:
- action['domain'] = "[('id','in',[" + ','.join(map(str, pick_ids)) + "])]"
+ return super(PurchaseOrder, self)._track_subtype(init_values)
+
+ @api.onchange('partner_id')
+ def onchange_partner_id(self):
+ if not self.partner_id:
+ self.fiscal_position_id = False
+ self.payment_term_id = False
+ self.currency_id = False
else:
- res = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_form')
- action['views'] = [(res and res[1] or False, 'form')]
- action['res_id'] = pick_ids and pick_ids[0] or False
- return action
+ self.fiscal_position_id = self.env['account.fiscal.position'].get_fiscal_position(self.partner_id.id)
+ self.payment_term_id = self.partner_id.property_supplier_payment_term_id.id
+ self.currency_id = self.partner_id.property_purchase_currency_id.id or self.env.user.company_id.currency_id.id
+ return {}
-
- def wkf_approve_order(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'approved', 'date_approve': fields.date.context_today(self,cr,uid,context=context)})
- return True
-
- def wkf_bid_received(self, cr, uid, ids, context=None):
- bid_date = fields.date.context_today(self, cr, uid, context=context)
- self.message_post(cr, uid, ids, body=_("Bid received on %s") % (bid_date), context=context)
- return self.write(cr, uid, ids, {'state':'bid', 'bid_date': bid_date})
-
- def wkf_send_rfq(self, cr, uid, ids, context=None):
+ @api.multi
+ def action_rfq_send(self):
'''
This function opens a window to compose an email, with the edi purchase template message loaded by default
'''
- if not context:
- context= {}
- ir_model_data = self.pool.get('ir.model.data')
+ self.ensure_one()
+ ir_model_data = self.env['ir.model.data']
try:
- if context.get('send_rfq', False):
- template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase')[1]
+ if self.env.context.get('send_rfq', False):
+ template_id = ir_model_data.get_object_reference('purchase', 'email_template_edi_purchase')[1]
else:
- template_id = ir_model_data.get_object_reference(cr, uid, 'purchase', 'email_template_edi_purchase_done')[1]
+ template_id = ir_model_data.get_object_reference('purchase', 'email_template_edi_purchase_done')[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]
+ compose_form_id = ir_model_data.get_object_reference('mail', 'email_compose_message_wizard_form')[1]
except ValueError:
- compose_form_id = False
- ctx = dict(context)
+ compose_form_id = False
+ ctx = dict(self.env.context or {})
ctx.update({
'default_model': 'purchase.order',
- 'default_res_id': ids[0],
+ 'default_res_id': self.ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
@@ -529,841 +224,413 @@ class purchase_order(osv.osv):
'context': ctx,
}
- 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'
- self.signal_workflow(cr, uid, ids, 'send_rfq')
- return self.pool['report'].get_action(cr, uid, ids, 'purchase.report_purchasequotation', context=context)
-
- def wkf_confirm_order(self, cr, uid, ids, context=None):
- todo = []
- for po in self.browse(cr, uid, ids, context=context):
- if not any(line.state != 'cancel' for line in po.order_line):
- raise UserError(_('You cannot confirm a purchase order without any purchase order line.'))
- if po.invoice_method == 'picking' and not any([l.product_id and l.product_id.type in ('product', 'consu') and l.state != 'cancel' for l in po.order_line]):
- raise osv.except_osv(
- _('Error!'),
- _("You cannot confirm a purchase order with Invoice Control Method 'Based on incoming shipments' that doesn't contain any stockable item."))
- for line in po.order_line:
- if line.state=='draft':
- todo.append(line.id)
- 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})
- 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.id
- if not acc_id:
- acc_id = po_line.product_id.categ_id.property_account_expense_categ_id.id
- if not acc_id:
- raise UserError(_('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_id', 'product.category', context=context).id
- fpos = po_line.order_id.fiscal_position_id or False
- #For anglo-saxon accounting
- account_id = fiscal_obj.map_account(cr, uid, fpos, acc_id)
- if po_line.company_id.anglo_saxon_accounting and po_line.product_id and po_line.product_id.type == 'product':
- acc_id = po_line.product_id.property_stock_account_input and po_line.product_id.property_stock_account_input.id
- if not acc_id:
- acc_id = po_line.product_id.categ_id.property_stock_account_input_categ_id and po_line.product_id.categ_id.property_stock_account_input_categ_id.id
- if acc_id:
- fpos = po_line.order_id.fiscal_position_id or False
- account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, acc_id)
- return account_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
- :param account_id: Expense account of the product of PO line if any.
- :param browse_record order_line: Purchase order line browse record
- :return: Value for fields of invoice lines.
- :rtype: dict
- """
- return {
- 'name': order_line.name,
- 'account_id': account_id,
- 'price_unit': order_line.price_unit or 0.0,
- 'quantity': order_line.product_qty,
- 'product_id': order_line.product_id.id or False,
- 'uom_id': order_line.product_uom.id or False,
- 'invoice_line_tax_ids': [(6, 0, [x.id for x in order_line.taxes_id])],
- 'account_analytic_id': order_line.account_analytic_id.id 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 UserError(_('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.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_ids': [(6, 0, line_ids)],
- 'origin': order.name,
- 'fiscal_position_id': order.fiscal_position_id.id or False,
- 'payment_term_id': 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
- self.write(cr, uid, ids, {'state':'draft','shipped':0})
- self.set_order_line_status(cr, uid, ids, 'draft', context=context)
- for p_id in ids:
- # Deleting the existing instance of workflow for PO
- self.delete_workflow(cr, uid, [p_id]) # TODO is it necessary to interleave the calls?
- self.create_workflow(cr, uid, [p_id])
- return True
-
- def wkf_po_done(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'done'}, context=context)
- self.set_order_line_status(cr, uid, ids, 'done', context=context)
-
- def action_invoice_create(self, cr, uid, ids, context=None):
- """Generates invoice for given ids of purchase orders and links that invoice ID to purchase order.
- :param ids: list of ids of purchase orders.
- :return: ID of created invoice.
- :rtype: int
- """
- context = dict(context or {})
-
- inv_obj = self.pool.get('account.invoice')
- inv_line_obj = self.pool.get('account.invoice.line')
-
- 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)
-
- # 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.state == 'cancel':
- continue
- 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({'invoice_lines': [(4, inv_line_id)]})
-
- # get invoice data and create invoice
- inv_data = self._prepare_invoice(cr, uid, order, inv_lines, context=context)
- 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)]})
- res = inv_id
- return res
-
- def invoice_done(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'approved'}, 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.state == 'cancel':
- continue
- if order_line.product_id and order_line.product_id.type in ('product', 'consu'):
- return True
- return False
-
- def wkf_action_cancel(self, cr, uid, ids, context=None):
- 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):
- context = context or {}
- for purchase in self.browse(cr, uid, ids, context=context):
- for pick in purchase.picking_ids:
+ @api.multi
+ def print_quotation(self):
+ self.write({'state': "sent"})
+ return self.env['report'].get_action(self, 'purchase.report_purchasequotation')
+
+ @api.multi
+ def button_approve(self):
+ self.write({'state': 'purchase'})
+ self._action_picking_create()
+ return {}
+
+ @api.multi
+ def button_draft(self):
+ self.write({'state': 'draft'})
+ return {}
+
+ @api.multi
+ def button_confirm(self):
+ return self.button_approve()
+
+ @api.multi
+ def button_cancel(self):
+ for order in self:
+ for pick in order.picking_ids:
if pick.state == 'done':
- raise UserError(_('Unable to cancel the purchase order %s.') % (purchase.name) + _('You have already received some goods for it. '))
- # Check action_cancel
- self.pool.get('stock.picking').action_cancel(cr, uid, [x.id for x in purchase.picking_ids if x.state != 'cancel'], context=context)
- # Check procurements not related to stock move yet
- if not context.get('cancel_procurement'):
- cancel_procurements = []
- cancel_moves = []
- exception_procurements = []
- for line in purchase.order_line:
- if line.procurement_ids:
- cancel_procurements += [x.id for x in line.procurement_ids if x.state not in ('cancel', 'exception') and x.rule_id.propagate]
- exception_procurements += [x.id for x in line.procurement_ids if x.state not in ('cancel', 'exception') and not x.rule_id.propagate]
- cancel_moves += [x.move_dest_id.id for x in line.procurement_ids if x.move_dest_id and x.move_dest_id.state!='cancel' and x.rule_id.propagate]
- if cancel_moves:
- cancel_moves = list(set(cancel_moves))
- self.pool['stock.move'].action_cancel(cr, uid, cancel_moves, context=context)
- if cancel_procurements:
- cancel_procurements = list(set(cancel_procurements))
- self.pool['procurement.order'].write(cr, uid, cancel_procurements, {'state': 'cancel'}, context=context)
- if exception_procurements:
- exception_procurements = list(set(exception_procurements))
- self.pool['procurement.order'].write(cr, uid, exception_procurements, {'state': 'exception'}, context=context)
-
- for inv in purchase.invoice_ids:
+ raise UserError(_('Unable to cancel purchase order %s as some receptions have already been done.') % (order.name))
+ for inv in order.invoice_ids:
if inv and inv.state not in ('cancel', 'draft'):
- raise UserError(_("Unable to cancel this purchase order.") + " " + _("You must first cancel all invoices related to this purchase order."))
- self.pool.get('account.invoice') \
- .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):
- ''' prepare the stock move data from the PO line. This function returns a list of dictionary ready to be used in stock.move's create()'''
- 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 / 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)
- res = []
- if order.location_id.usage == 'customer':
- name = order_line.product_id.with_context(dict(context or {}, lang=order.dest_address_id.lang)).name
- else:
- name = order_line.name or ''
- move_template = {
- 'name': name,
- 'product_id': order_line.product_id.id,
- 'product_uom': order_line.product_uom.id,
- 'date': order.date_order,
- 'date_expected': order_line.date_planned,
- 'location_id': order.partner_id.property_stock_supplier.id,
- 'location_dest_id': order.location_id.id,
- 'picking_id': picking_id,
- 'partner_id': order.dest_address_id.id,
- 'move_dest_id': False,
- 'state': 'draft',
- 'purchase_line_id': order_line.id,
- 'company_id': order.company_id.id,
- 'price_unit': price_unit,
- 'picking_type_id': order.picking_type_id.id,
- 'group_id': group_id,
- 'procurement_id': False,
- '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,
- }
-
- diff_quantity = order_line.product_qty
- for procurement in order_line.procurement_ids:
- procurement_qty = product_uom._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, to_uom_id=order_line.product_uom.id)
- tmp = move_template.copy()
- tmp.update({
- 'product_uom_qty': min(procurement_qty, diff_quantity),
- 'move_dest_id': procurement.move_dest_id.id, #move destination is same as procurement destination
- 'procurement_id': procurement.id,
- 'propagate': procurement.rule_id.propagate,
+ raise UserError(_("Unable to cancel this purchase order.i You must first cancel related vendor bills."))
+
+ if not self.env.context.get('cancel_procurement'):
+ procurements = order.order_line.mapped('procurement_ids')
+ procurements.filtered(lambda r: r.state not in ('cancel', 'exception') and r.rule_id.propagate).write({'state': 'cancel'})
+ procurements.filtered(lambda r: r.state not in ('cancel', 'exception') and not r.rule_id.propagate).write({'state': 'exception'})
+ moves = procurements.filtered(lambda r: r.rule_id.propagate).mapped('move_dest_id')
+ moves.filtered(lambda r: r.state != 'cancel').action_cancel()
+
+ self.write({'state': 'cancel'})
+
+ @api.multi
+ def button_done(self):
+ self.write({'state': 'done'})
+
+ @api.multi
+ def _get_destination_location(self):
+ self.ensure_one()
+ if self.dest_address_id:
+ return self.dest_address_id.property_stock_customer.id
+ return self.picking_type_id.default_location_dest_id.id
+
+ @api.model
+ def _create_picking(self):
+ if not self.group_id:
+ self.group_id = self.group_id.create({
+ 'name': self.name,
+ 'partner_id': self.partner_id.id
})
- diff_quantity -= min(procurement_qty, diff_quantity)
- res.append(tmp)
- #if the order line has a bigger quantity than the procurement it was for (manually changed or minimal quantity), then
- #split the future stock move in two because the route followed may be different.
- if float_compare(diff_quantity, 0.0, precision_rounding=order_line.product_uom.rounding) > 0:
- move_template['product_uom_qty'] = diff_quantity
- res.append(move_template)
- return res
+ return self.env['stock.picking'].create({
+ 'picking_type_id': self.picking_type_id.id,
+ 'partner_id': self.partner_id.id,
+ 'date': self.date_order,
+ 'origin': self.name,
+ 'location_dest_id': self._get_destination_location(),
+ 'location_id': self.partner_id.property_stock_supplier.id
+ })
- def _create_stock_moves(self, cr, uid, order, order_lines, picking_id=False, context=None):
- """Creates appropriate stock moves for given order lines, whose can optionally create a
- picking if none is given or no suitable is found, then confirms the moves, makes them
- available, and confirms the pickings.
+ @api.multi
+ def _action_picking_create(self):
+ for order in self:
+ ptypes = order.order_line.mapped('product_id.type')
+ if ('product' in ptypes) or ('consu' in ptypes):
+ picking = order._create_picking()
+ moves = order.order_line._create_stock_moves(picking)
+ moves.action_confirm()
+ moves.force_assign()
+ return True
- If ``picking_id`` is provided, the stock moves will be added to it, otherwise a standard
- incoming picking will be created to wrap the stock moves (default behavior of the stock.move)
- Modules that wish to customize the procurements or partition the stock moves over
- multiple stock pickings may override this method and call ``super()`` with
- different subsets of ``order_lines`` and/or preset ``picking_id`` values.
+class PurchaseOrderLine(models.Model):
+ _name = 'purchase.order.line'
+ _description = 'Purchase Order Line'
- :param browse_record order: purchase order to which the order lines belong
- :param list(browse_record) order_lines: purchase order line records for which picking
- and moves should be created.
- :param int picking_id: optional ID of a stock picking to which the created stock moves
- will be added. A new picking will be created if omitted.
- :return: None
- """
- stock_move = self.pool.get('stock.move')
- todo_moves = []
- if order.group_id:
- new_group = order.group_id.id
- else:
- new_group = self.pool.get("procurement.group").create(cr, uid, {'name': order.name, 'partner_id': order.partner_id.id}, context=context)
+ @api.depends('product_qty', 'price_unit', 'taxes_id')
+ def _compute_amount(self):
+ for line in self:
+ taxes = line.taxes_id.compute_all(line.price_unit, line.order_id.currency_id, line.product_qty, product=line.product_id, partner=line.order_id.partner_id)
+ line.update({
+ 'price_tax': taxes['total_included'] - taxes['total_excluded'],
+ 'price_total': taxes['total_included'],
+ 'price_subtotal': taxes['total_excluded'],
+ })
- for order_line in order_lines:
- if order_line.state == 'cancel':
+ @api.depends('invoice_lines.invoice_id.state')
+ def _compute_qty_invoiced(self):
+ for line in self:
+ qty = 0.0
+ for inv_line in line.invoice_lines:
+ qty += inv_line.uom_id._compute_qty_obj(inv_line.uom_id, inv_line.quantity, line.product_uom)
+ line.qty_invoiced = qty
+
+ @api.depends('move_ids.state')
+ def _compute_qty_received(self):
+ for line in self:
+ if line.order_id.state not in ['purchase', 'done']:
+ line.qty_received = 0.0
continue
- if not order_line.product_id:
+ if line.product_id.type not in ['consu', 'product']:
+ line.qty_received = line.product_qty
continue
-
- if order_line.product_id.type in ('product', 'consu'):
- for vals in self._prepare_order_line_move(cr, uid, order, order_line, picking_id, new_group, context=context):
- move = stock_move.create(cr, uid, vals, context=context)
- todo_moves.append(move)
-
- todo_moves = stock_move.action_confirm(cr, uid, todo_moves)
- stock_move.force_assign(cr, uid, todo_moves)
-
- def test_moves_done(self, cr, uid, ids, context=None):
- '''PO is done at the delivery side if all the incoming shipments are done'''
- for purchase in self.browse(cr, uid, ids, context=context):
- for picking in purchase.picking_ids:
- if picking.state != 'done':
- return False
- return True
-
- def test_moves_except(self, cr, uid, ids, context=None):
- ''' PO is in exception at the delivery side if one of the picking is canceled
- and the other pickings are completed (done or canceled)
- '''
- at_least_one_canceled = False
- alldoneorcancel = True
- for purchase in self.browse(cr, uid, ids, context=context):
- for picking in purchase.picking_ids:
- if picking.state == 'cancel':
- at_least_one_canceled = True
- if picking.state not in ['done', 'cancel']:
- alldoneorcancel = False
- return at_least_one_canceled and alldoneorcancel
-
- def move_lines_get(self, cr, uid, ids, *args):
- res = []
- for order in self.browse(cr, uid, ids, context={}):
- for line in order.order_line:
- res += [x.id for x in line.move_ids]
- return res
-
- def action_picking_create(self, cr, uid, ids, context=None):
- for order in self.browse(cr, uid, ids):
- picking_vals = {
- 'picking_type_id': order.picking_type_id.id,
- 'partner_id': order.partner_id.id,
- 'date': order.date_order,
- 'origin': order.name,
- 'location_id': order.partner_id.property_stock_supplier.id,
- 'location_dest_id': order.location_id.id,
+ total = 0.0
+ for move in line.move_ids:
+ if move.state == 'done':
+ total += move.product_qty
+ line.qty_received = total
+
+ name = fields.Text(string='Description', required=True)
+ product_qty = fields.Float(string='Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True, default=0.0)
+ date_planned = fields.Datetime(string='Scheduled Date', required=True, select=True)
+ taxes_id = fields.Many2many('account.tax', string='Taxes')
+ product_uom = fields.Many2one('product.uom', string='Product Unit of Measure', required=True)
+ product_id = fields.Many2one('product.product', string='Product', domain=[('purchase_ok', '=', True)], change_default=True, required=True)
+ move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, ondelete='set null', copy=False)
+ price_unit = fields.Float(string='Unit Price', required=True, digits_compute=dp.get_precision('Product Price'))
+
+ price_subtotal = fields.Monetary(compute='_compute_amount', string='Subtotal', store=True)
+ price_total = fields.Monetary(compute='_compute_amount', string='Total', store=True)
+ price_tax = fields.Monetary(compute='_compute_amount', string='Tax', store=True)
+
+ order_id = fields.Many2one('purchase.order', string='Order Reference', select=True, required=True, ondelete='cascade')
+ account_analytic_id = fields.Many2one('account.analytic.account', string='Analytic Account')
+ company_id = fields.Many2one('res.company', related='order_id.company_id', string='Company', store=True, readonly=True)
+
+ invoice_lines = fields.One2many('account.invoice.line', 'purchase_line_id', string="Invoice Lines", readonly=True, copy=False)
+
+ # Replace by invoiced Qty
+ qty_invoiced = fields.Float(compute='_compute_qty_invoiced', string="Billed Qty", store=True)
+ qty_received = fields.Float(compute='_compute_qty_received', string="Received Qty", store=True)
+
+ partner_id = fields.Many2one('res.partner', related='order_id.partner_id', string='Partner', readonly=True, store=True)
+ currency_id = fields.Many2one(related='order_id.currency_id', store=True, string='Currency', readonly=True)
+ date_order = fields.Datetime(related='order_id.date_order', string='Order Date', readonly=True)
+ procurement_ids = fields.One2many('procurement.order', 'purchase_line_id', string='Associated Procurements', copy=False)
+
+ @api.multi
+ def _create_stock_moves(self, picking):
+ moves = self.env['stock.move']
+ done = self.env['stock.move'].browse()
+ for line in self:
+ order = line.order_id
+ price_unit = line.price_unit
+ if line.product_uom.id != line.product_id.uom_id.id:
+ price_unit *= line.product_uom.factor / line.product_id.uom_id.factor
+ if order.currency_id != order.company_id.currency_id:
+ price_unit = order.currency_id.compute(price_unit, order.company_id.currency_id, round=False)
+
+ template = {
+ 'name': line.name or '',
+ 'product_id': line.product_id.id,
+ 'product_uom': line.product_uom.id,
+ 'date': line.order_id.date_order,
+ 'date_expected': line.date_planned,
+ 'location_id': line.order_id.partner_id.property_stock_supplier.id,
+ 'location_dest_id': line.order_id._get_destination_location(),
+ 'picking_id': picking.id,
+ 'partner_id': line.order_id.dest_address_id.id,
+ 'move_dest_id': False,
+ 'state': 'draft',
+ 'purchase_line_id': line.id,
+ 'company_id': line.order_id.company_id.id,
+ 'price_unit': price_unit,
+ 'picking_type_id': line.order_id.picking_type_id.id,
+ 'group_id': line.order_id.group_id.id,
+ 'procurement_id': False,
+ 'origin': line.order_id.name,
+ 'route_ids': line.order_id.picking_type_id.warehouse_id and [(6, 0, [x.id for x in line.order_id.picking_type_id.warehouse_id.route_ids])] or [],
+ 'warehouse_id':line.order_id.picking_type_id.warehouse_id.id,
}
- 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)
- return picking_id
-
- def picking_done(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'shipped':1,'state':'approved'}, context=context)
- # Do check on related procurements:
- proc_obj = self.pool.get("procurement.order")
- po_lines = []
- for po in self.browse(cr, uid, ids, context=context):
- po_lines += [x.id for x in po.order_line if x.state != 'cancel']
- if po_lines:
- procs = proc_obj.search(cr, uid, [('purchase_line_id', 'in', po_lines)], context=context)
- if procs:
- proc_obj.check(cr, uid, procs, context=context)
- for id in ids:
- self.message_post(cr, uid, id, body=_("Products received"), context=context)
- return True
- def do_merge(self, cr, uid, ids, context=None):
- """
- To merge similar type of purchase orders.
- Orders will only be merged if:
- * Purchase Orders are in draft
- * Purchase Orders belong to the same partner
- * Purchase Orders are have same stock location, same pricelist, same currency
- Lines will only be merged if:
- * Order lines are exactly the same except for the quantity and unit
-
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: the ID or list of IDs
- @param context: A standard dictionary
-
- @return: new purchase order id
-
- """
- #TOFIX: merged order line should be unlink
- def make_key(br, fields):
- list_key = []
- for field in fields:
- field_val = getattr(br, field)
- if field in ('product_id', 'account_analytic_id'):
- if not field_val:
- field_val = False
- if isinstance(field_val, browse_record):
- field_val = field_val.id
- elif isinstance(field_val, browse_null):
- field_val = False
- 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)
-
- context = dict(context or {})
-
- # Compute what the new orders should contain
- new_orders = {}
-
- order_lines_to_move = {}
- for porder in [order for order in self.browse(cr, uid, ids, context=context) if order.state == 'draft']:
- order_key = make_key(porder, ('partner_id', 'location_id', 'currency_id'))
- new_order = new_orders.setdefault(order_key, ({}, []))
- new_order[1].append(porder.id)
- order_infos = new_order[0]
- order_lines_to_move.setdefault(order_key, [])
-
- if not order_infos:
- order_infos.update({
- 'origin': porder.origin,
- 'date_order': porder.date_order,
- 'partner_id': porder.partner_id.id,
- 'dest_address_id': porder.dest_address_id.id,
- 'picking_type_id': porder.picking_type_id.id,
- 'location_id': porder.location_id.id,
- 'currency_id': porder.currency_id.id,
- 'state': 'draft',
- 'order_line': {},
- 'notes': '%s' % (porder.notes or '',),
- 'fiscal_position_id': porder.fiscal_position_id and porder.fiscal_position_id.id or False,
+ # Fullfill all related procurements with this po line
+ diff_quantity = line.product_qty
+ for procurement in line.procurement_ids:
+ procurement_qty = procurement.product_uom._compute_qty_obj(procurement.product_uom, procurement.product_qty, line.product_uom)
+ tmp = template.copy()
+ tmp.update({
+ 'product_uom_qty': min(procurement_qty, diff_quantity),
+ 'move_dest_id': procurement.move_dest_id.id, #move destination is same as procurement destination
+ 'procurement_id': procurement.id,
+ 'propagate': procurement.rule_id.propagate,
})
- else:
- if porder.date_order < order_infos['date_order']:
- order_infos['date_order'] = porder.date_order
- 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
-
- order_lines_to_move[order_key] += [order_line.id for order_line in porder.order_line
- if order_line.state != 'cancel']
-
- allorders = []
- orders_info = {}
- for order_key, (order_data, old_ids) in new_orders.iteritems():
- # skip merges with only one order
- if len(old_ids) < 2:
- allorders += (old_ids or [])
- continue
-
- # cleanup order line data
- for key, value in order_data['order_line'].iteritems():
- del value['uom_factor']
- value.update(dict(key))
- order_data['order_line'] = [(6, 0, order_lines_to_move[order_key])]
-
- # create the new order
- context.update({'mail_create_nolog': True})
- neworder_id = self.create(cr, uid, order_data)
- self.message_post(cr, uid, [neworder_id], body=_("RFQ created"), context=context)
- orders_info.update({neworder_id: old_ids})
- allorders.append(neworder_id)
-
- # 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_workflow(cr, uid, [old_id], 'purchase_cancel')
-
- return orders_info
-
- def _set_po_lines_invoiced(self, cr, uid, ids, context=None):
- for po in self.browse(cr, uid, ids, context=context):
- is_invoiced = []
- if po.invoice_method == 'picking':
- # We determine the invoiced state of the PO line based on the invoiced state
- # of the associated moves. This should cover all possible cases:
- # - all moves are done and invoiced
- # - a PO line is split into multiple moves (e.g. if multiple pickings): some
- # pickings are done, some are in progress, some are cancelled
- for po_line in po.order_line:
- if (po_line.move_ids and
- all(move.state in ('done', 'cancel') for move in po_line.move_ids) and
- not all(move.state == 'cancel' for move in po_line.move_ids) and
- all(move.invoice_state == 'invoiced' for move in po_line.move_ids if move.state == 'done')):
- is_invoiced.append(po_line.id)
- else:
- for po_line in po.order_line:
- if (po_line.invoice_lines and
- all(line.invoice_id.state not in ['draft', 'cancel'] for line in po_line.invoice_lines)):
- is_invoiced.append(po_line.id)
- if is_invoiced:
- self.pool['purchase.order.line'].write(cr, uid, is_invoiced, {'invoiced': True})
- workflow.trg_write(uid, 'purchase.order', po.id, cr)
-
-
-class purchase_order_line(osv.osv):
- def _calc_line_base_price(self, cr, uid, line, context=None):
- """Return the base price of the line to be used for tax calculation.
-
- This function can be extended by other modules to modify this base
- price (adding a discount, for example).
- """
- return line.price_unit
-
- def _calc_line_quantity(self, cr, uid, line, context=None):
- """Return the base quantity of the line to be used for the subtotal.
-
- This function can be extended by other modules to modify this base
- quantity (adding for example offers 3x2 and so on).
- """
- return line.product_qty
-
- def _amount_line(self, cr, uid, ids, prop, arg, context=None):
- res = {}
- for line in self.browse(cr, uid, ids, context=context):
- line_price = self._calc_line_base_price(cr, uid, line,
- context=context)
- line_qty = self._calc_line_quantity(cr, uid, line,
- context=context)
- cur = line.order_id.currency_id
- taxes = line.taxes_id.compute_all(line_price, cur,
- line_qty, line.product_id,
- line.order_id.partner_id)
-
- res[line.id] = taxes['total_excluded']
- 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),
- 'date_planned': fields.datetime('Scheduled Date', required=True, select=True),
- 'taxes_id': fields.many2many('account.tax', 'purchase_order_taxe', 'ord_id', 'tax_id', 'Taxes'),
- 'product_uom': fields.many2one('product.uom', 'Product Unit of Measure', required=True),
- 'product_id': fields.many2one('product.product', 'Product', domain=[('purchase_ok','=',True)], change_default=True),
- 'move_ids': fields.one2many('stock.move', 'purchase_line_id', 'Reservation', readonly=True, ondelete='set null'),
- 'price_unit': fields.float('Unit Price', required=True, digits_compute= dp.get_precision('Product Price')),
- 'price_subtotal': fields.function(_amount_line, string='Subtotal', digits=0),
- '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, 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, 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 = {
- 'product_uom' : _get_uom_id,
- 'product_qty': lambda *a: 1.0,
- 'state': lambda *args: 'draft',
- 'invoiced': lambda *a: 0,
- }
- _table = 'purchase_order_line'
- _name = 'purchase.order.line'
- _description = 'Purchase Order Line'
-
- def unlink(self, cr, uid, ids, context=None):
- for line in self.browse(cr, uid, ids, context=context):
- if line.order_id.state in ['approved', 'done'] and line.state not in ['draft', 'cancel']:
+ done += moves.create(tmp)
+ diff_quantity -= min(procurement_qty, diff_quantity)
+ if float_compare(diff_quantity, 0.0, precision_rounding=line.product_uom.rounding) > 0:
+ template['product_uom_qty'] = diff_quantity
+ done += moves.create(template)
+ return done
+
+ @api.multi
+ def unlink(self):
+ for line in self:
+ if line.order_id.state in ['approved', 'done']:
raise UserError(_('Cannot delete a purchase order line which is in state \'%s\'.') %(line.state,))
- procurement_obj = self.pool.get('procurement.order')
- procurement_ids_to_except = procurement_obj.search(cr, uid, [('purchase_line_id', 'in', ids)], context=context)
- if procurement_ids_to_except:
- for po_id in procurement_ids_to_except:
- procurement_obj.message_post(cr, uid, po_id, body=_('Purchase order line deleted.'), context=context)
- procurement_obj.write(cr, uid, procurement_ids_to_except, {'state': 'exception'}, context=context)
- return super(purchase_order_line, self).unlink(cr, uid, ids, context=context)
-
- def onchange_product_uom(self, cr, uid, ids, product_id, qty, uom_id,
- partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
- name=False, price_unit=False, state='draft', currency_id=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, 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, state=state, replace=False, currency_id=currency_id, context=context)
-
- def _get_date_planned(self, cr, uid, product, date_order_str, context=None):
+ line.procurement_ids.message_post(body=_('Purchase order line deleted.'))
+ line.procurement_ids.write({'state': 'exception'})
+ return super(PurchaseOrderLine, self).unlink()
+
+ @api.model
+ def _get_date_planned(self, product, po=False):
"""Return the datetime value to use as Schedule Date (``date_planned``) for
- PO Lines that correspond to the given product.supplierinfo,
+ PO Lines that correspond to the given product.seller_ids,
when ordered at `date_order_str`.
:param browse_record | False product: product.product, used to
determine delivery delay thanks to the selected seller field (if False, default delay = 0)
- :param str date_order_str: date of order field, as a string in
- DEFAULT_SERVER_DATETIME_FORMAT
+ :param browse_record | False po: purchase.order, necessary only if
+ the PO line is not yet attached to a PO.
:rtype: datetime
:return: desired Schedule Date for the PO line
"""
supplier_delay = int(product.seller_delay) if product else 0
- 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)
- # We will group by PO first, so we do the check only once for each PO
- purchase_orders = list(set([x.order_id for x in self.browse(cr, uid, ids, context=context)]))
- for purchase in purchase_orders:
- if all([l.state == 'cancel' for l in purchase.order_line]):
- self.pool.get('purchase.order').action_cancel(cr, uid, [purchase.id], context=context)
-
- def _check_product_uom_group(self, cr, uid, context=None):
- group_uom = self.pool.get('ir.model.data').get_object(cr, uid, 'product', 'group_uom')
- res = [user for user in group_uom.users if user.id == uid]
- return len(res) and True or False
-
- def onchange_product_id(self, cr, uid, ids, product_id, qty, uom_id,
- partner_id, date_order=False, fiscal_position_id=False, date_planned=False,
- name=False, price_unit=False, state='draft', replace=True, currency_id=False, context=None):
- """
- onchange handler of product_id.
+ if po:
+ date_order = po.date_order
+ else:
+ date_order = self.order_id.date_order
+ if date_order:
+ return datetime.strptime(date_order, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta(days=supplier_delay)
+ else:
+ return datetime.today() + relativedelta(days=supplier_delay)
+
+ @api.onchange('product_id', 'product_qty', 'product_uom')
+ def onchange_product_id(self):
+ result = {}
+ if not self.product_id:
+ return {}
+
+ if self.product_id.uom_id.category_id.id != self.product_uom.category_id.id:
+ self.product_uom = self.product_id.uom_po_id
+ result['domain'] = {'product_uom': [('category_id', '=', self.product_id.uom_id.category_id.id)]}
+
+ res = self._get_name_price_quantity_date(
+ self.product_id,
+ self.partner_id,
+ datetime.strptime(self.order_id.date_order, DEFAULT_SERVER_DATETIME_FORMAT) if self.order_id.date_order else datetime.today(),
+ self.product_qty,
+ self.product_uom,
+ self.order_id.currency_id)
+
+ self.name = res['name']
+ self.product_qty = res['quantity'] if not res['quantity'] else max(self.product_qty, res['quantity'])
+ self.price_unit = res['price_unit']
+ self.date_planned = res['date_planned']
+
+ taxes = self.product_id.supplier_taxes_id
+ fpos = self.order_id.fiscal_position_id
+ if fpos:
+ self.taxes_id = fpos.map_tax(taxes)
+
+ result['value'] = {
+ 'name': self.name,
+ 'product_uom': self.product_uom.id,
+ 'product_qty': self.product_qty,
+ 'date_planned': self.date_planned,
+ 'taxes_id': self.taxes_id.ids,
+ }
+
+ return result
+
+ @api.model
+ def _get_name_price_quantity_date(self, product_id, partner_id, date, quantity, product_uom, currency_id, order_id=False):
+ """Return the name of the product, the seller quantity, the unit price and
+ the date planned of a given product.
+
+ :param browse_record product_id: product.product, for which we want to
+ get the information
+ :param str lang: language in which we want the product name
+ :param browse_record partner_id: res.partner, supplier of the product
+ :param str date: order date
+ :param float quantity: quantity of product in product_uom UoM
+ :param browse_record product_uom: product.uom, product_uom UoM in which is
+ expressed the quantity
+ :param browse_record currency_id: res.currency, currency in which we want the price
+ :param browse_record | False order_id: purchase.order, purchase order in case we
+ try to get the data for a PO line not yet attached to a PO.
+ :rtype: dict
+ :return: name, seller quantity, seller price, date planned
"""
- if context is None:
- context = {}
- res = {'value': {'price_unit': price_unit or 0.0, 'name': name or '', 'product_uom' : uom_id or False}}
- if not product_id:
- if not uom_id:
- uom_id = self.default_get(cr, uid, ['product_uom'], context=context).get('product_uom', False)
- res['value']['product_uom'] = uom_id
- return res
- product_product = self.pool.get('product.product')
- product_uom = self.pool.get('product.uom')
- res_partner = self.pool.get('res.partner')
- account_fiscal_position = self.pool.get('account.fiscal.position')
- account_tax = self.pool.get('account.tax')
-
- # - check for the presence of partner_id and pricelist_id
- #if not partner_id:
- # raise UserError(_('Select a partner in purchase order to choose a product.'))
- #if not pricelist_id:
- # raise UserError(_('Select a price list in the purchase order form before choosing a product.'))
-
- # - determine name and notes based on product in partner lang.
- context_partner = context.copy()
- context_partner.update({'partner': partner_id, 'quantity': qty})
- if partner_id:
- 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)
- price = product.seller_price
- if product.selected_seller_id.currency_id.id != currency_id:
- price = self.pool['res.currency'].compute(cr, uid, product.selected_seller_id.currency_id.id, currency_id, price, context=dict(context or {}, date=date_order))
- if replace:
- #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})
-
- # - set a domain on product_uom
- 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
-
- uom = product_uom.browse(cr, uid, uom_id, context=context)
- if product.uom_id.category_id.id != uom.category_id.id:
- 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
-
- res['value'].update({'product_uom': uom_id})
-
- # - determine product_qty and date_planned based on seller info
- if not date_order:
- date_order = fields.datetime.now()
- dt = self._get_date_planned(cr, uid, product, date_order, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
- qty = qty or 1.0
- res['value'].update({'date_planned': date_planned or dt})
- if qty:
- res['value'].update({'product_qty': qty})
-
- 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)
- price = self.pool['account.tax']._fix_tax_included_price(cr, uid, price, product.supplier_taxes_id, taxes_ids)
- res['value'].update({'price_unit': price, 'taxes_id': taxes_ids})
- return res
+ # Switch quantity in uom_id
+ quantity_uom_id = quantity
+ if quantity_uom_id and product_id.uom_id != product_uom:
+ quantity_uom_id = product_uom._compute_qty_obj(product_uom, quantity_uom_id, product_id.uom_id)
+
+ product = product_id.with_context({
+ 'lang': partner_id.lang,
+ 'partner_id': partner_id.id,
+ 'date': date,
+ 'quantity': quantity_uom_id,
+ })
- product_id_change = onchange_product_id
- product_uom_change = onchange_product_uom
+ # Switch quantity back in product_uom
+ quantity = max(quantity_uom_id, product.seller_qty)
+ if quantity and product_id.uom_id != product_uom:
+ quantity = product_id.uom_id._compute_qty_obj(product_id.uom_id, quantity, product_uom)
- def action_confirm(self, cr, uid, ids, context=None):
- self.write(cr, uid, ids, {'state': 'confirmed'}, context=context)
- return True
+ price_unit = product.seller_price
+ if price_unit and partner_id.currency_id != currency_id:
+ price_unit = partner_id.currency_id.compute(price_unit, currency_id)
+ date_planned = self._get_date_planned(product, po=order_id).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
-class procurement_rule(osv.osv):
- _inherit = 'procurement.rule'
+ name = product.display_name
+ if product.description_purchase:
+ name += '\n' + product.description_purchase
+
+ return {
+ 'name': name,
+ 'quantity': quantity,
+ 'price_unit': price_unit,
+ 'date_planned': date_planned,
+ }
- def _get_action(self, cr, uid, context=None):
- return [('buy', _('Buy'))] + super(procurement_rule, self)._get_action(cr, uid, context=context)
+
+class ProcurementRule(models.Model):
+ _inherit = 'procurement.rule'
+ @api.model
+ def _get_action(self):
+ return [('buy', _('Buy'))] + super(ProcurementRule, self)._get_action()
-class procurement_order(osv.osv):
+class ProcurementOrder(models.Model):
_inherit = 'procurement.order'
- _columns = {
- 'purchase_line_id': fields.many2one('purchase.order.line', 'Purchase Order Line'),
- 'purchase_id': fields.related('purchase_line_id', 'order_id', type='many2one', relation='purchase.order', string='Purchase Order'),
- }
- def propagate_cancels(self, cr, uid, ids, context=None):
- purchase_line_obj = self.pool.get('purchase.order.line')
- lines_to_cancel = []
- for procurement in self.browse(cr, uid, ids, context=context):
+ purchase_line_id = fields.Many2one('purchase.order.line', string='Purchase Order Line')
+ purchase_id = fields.Many2one(related='purchase_line_id.order_id', string='Purchase Order')
+
+ @api.multi
+ def propagate_cancels(self):
+ result = super(ProcurementOrder, self).propagate_cancels()
+ for procurement in self:
if procurement.rule_id.action == 'buy' and procurement.purchase_line_id:
- if procurement.purchase_line_id.state not in ('draft', 'cancel'):
+ if procurement.purchase_line_id.order_id.state not in ('draft', 'cancel', 'sent', 'to validate'):
raise UserError(
- _('Can not cancel this procurement like this as the related purchase order has been confirmed already. Please cancel the purchase order first. '))
-
- new_qty, new_price = self._calc_new_qty_price(cr, uid, procurement, cancel=True, context=context)
- if new_qty != procurement.purchase_line_id.product_qty:
- purchase_line_obj.write(cr, uid, [procurement.purchase_line_id.id], {'product_qty': new_qty, 'price_unit': new_price}, context=context)
- if float_compare(new_qty, 0.0, precision_rounding=procurement.product_uom.rounding) != 1:
- if procurement.purchase_line_id.id not in lines_to_cancel:
- lines_to_cancel += [procurement.purchase_line_id.id]
- if lines_to_cancel:
- purchase_line_obj.action_cancel(cr, uid, lines_to_cancel, context=context)
- purchase_line_obj.unlink(cr, uid, lines_to_cancel, context=context)
- return super(procurement_order, self).propagate_cancels(cr, uid, ids, context=context)
-
- def _run(self, cr, uid, procurement, context=None):
- if procurement.rule_id and procurement.rule_id.action == 'buy':
- #make a purchase order for the procurement
- return self.make_po(cr, uid, [procurement.id], context=context)[procurement.id]
- return super(procurement_order, self)._run(cr, uid, procurement, context=context)
-
- #TODO: Autocommit needed?
- def run(self, cr, uid, ids, autocommit=False, context=None):
- procs = self.browse(cr, uid, ids, context=context)
- to_assign = [x for x in procs if x.state not in ('running', 'done')]
- self._assign_multi(cr, uid, to_assign, context=context)
- buy_ids = [x.id for x in to_assign if x.rule_id and x.rule_id.action == 'buy']
- if buy_ids:
- result_dict = self.make_po(cr, uid, buy_ids, context=context)
- runnings = []
- exceptions = []
- for proc in result_dict.keys():
- if result_dict[proc]:
- runnings += [proc]
- else:
- exceptions += [proc]
- if runnings:
- self.write(cr, uid, runnings, {'state': 'running'}, context=context)
- if exceptions:
- self.write(cr, uid, exceptions, {'state': 'exception'}, context=context)
- set_others = set(ids) - set(buy_ids)
- return super(procurement_order, self).run(cr, uid, list(set_others), context=context)
-
- def _check(self, cr, uid, procurement, context=None):
- if procurement.purchase_line_id:
- if procurement.purchase_line_id.order_id.shipped:
- return True
- elif procurement.move_ids:
- moves = self.pool.get('stock.move').browse(cr, uid, [x.id for x in procurement.move_ids], context=context)
- return all(move.state == 'done' for move in moves)
- return super(procurement_order, self)._check(cr, uid, procurement, context=context)
-
- def _check_supplier_info(self, cr, uid, ids, context=None):
- ''' Check the vendor info field of a product and write an error message on the procurement if needed.
- Returns True if all needed information is there, False if some configuration mistake is detected.
- '''
- 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 Vendor of Product of Procurement.
+ _('Can not cancel a procurement related to a purchase order. Please cancel the purchase order first.'))
+ if procurement.purchase_line_id:
+ price_unit = 0.0
+ product_qty = 0.0
+ others_procs = procurement.purchase_line_id.procurement_ids.filtered(lambda r: r != procurement)
+ for other_proc in others_procs:
+ if other_proc.state not in ['cancel', 'draft']:
+ product_qty += other_proc.product_uom._compute_qty_obj(other_proc.product_uom, other_proc.product_qty, procurement.purchase_line_id.product_uom)
+
+ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
+ if not float_is_zero(product_qty, precision_digits=precision):
+ res = self.env['purchase.order.line']._get_name_price_quantity_date(
+ procurement.product_id,
+ procurement.purchase_line_id.partner_id,
+ datetime.strptime(procurement.purchase_line_id.order_id.date_order, DEFAULT_SERVER_DATETIME_FORMAT),
+ product_qty,
+ procurement.product_uom,
+ procurement.purchase_line_id.order_id.currency_id)
+
+ product_qty = res['quantity']
+ price_unit = res['price_unit']
+
+ procurement.purchase_line_id.product_qty = product_qty
+ procurement.purchase_line_id.price_unit = price_unit
- if not procurement.product_id.seller_ids:
- message = _('No vendor defined for this product !')
- elif not partner:
- message = _('No default vendor defined for this product')
- elif not partner_obj.address_get(cr, uid, [partner.id], ['delivery'])['delivery']:
- message = _('No address defined for the vendor')
-
- if message:
- if procurement.message != message:
- cr.execute('update procurement_order set message=%s where id=%s', (message, procurement.id))
- return False
+ return result
- if user.company_id and user.company_id.partner_id:
- if partner.id == user.company_id.partner_id.id:
- raise UserError(_('The product "%s" has been defined with your company as reseller which seems to be a configuration error!' % procurement.product_id.name))
+ @api.model
+ def _run(self, procurement):
+ if procurement.rule_id and procurement.rule_id.action == 'buy':
+ return procurement.make_po()
+ return super(ProcurementOrder, self)._run(procurement)
- return True
+ @api.model
+ def _check(self, procurement):
+ if procurement.purchase_line_id:
+ if not procurement.move_ids:
+ return False
+ return all(move.state == 'done' for move in procurement.move_ids)
+ return super(ProcurementOrder, self)._check(procurement)
- def create_procurement_purchase_order(self, cr, uid, procurement, po_vals, line_vals, context=None):
- """Create the purchase order from the procurement, using
- the provided field values, after adding the given purchase
- order line in the purchase order.
-
- :params procurement: the procurement object generating the purchase order
- :params dict po_vals: field values for the new purchase order (the
- ``order_line`` field will be overwritten with one
- single line, as passed in ``line_vals``).
- :params dict line_vals: field values of the single purchase order line that
- the purchase order will contain.
- :return: id of the newly created purchase order
- :rtype: int
- """
- po_vals.update({'order_line': [(0,0,line_vals)]})
- return self.pool.get('purchase.order').create(cr, uid, po_vals, context=context)
+ @api.v8
+ def _get_purchase_schedule_date(self):
+ procurement_date_planned = datetime.strptime(self.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
+ schedule_date = (procurement_date_planned - relativedelta(days=self.company_id.po_lead))
+ return schedule_date
- def _get_purchase_schedule_date(self, cr, uid, procurement, company, context=None):
+ @api.v7
+ def _get_purchase_schedule_date(self, procurement):
"""Return the datetime value to use as Schedule Date (``date_planned``) for the
Purchase Order Lines created to satisfy the given procurement.
:param browse_record procurement: the procurement for which a PO will be created.
- :param browse_report company: the company to which the new PO will belong to.
:rtype: datetime
:return: the desired Schedule Date for the PO lines
"""
procurement_date_planned = datetime.strptime(procurement.date_planned, DEFAULT_SERVER_DATETIME_FORMAT)
- schedule_date = (procurement_date_planned - relativedelta(days=company.po_lead))
+ schedule_date = (procurement_date_planned - relativedelta(days=procurement.company_id.po_lead))
return schedule_date
+ @api.v8
+ def _get_purchase_order_date(self, schedule_date):
+ self.ensure_one()
+ seller_delay = int(self.product_id.seller_delay)
+ return schedule_date - relativedelta(days=seller_delay)
+
+ @api.v7
def _get_purchase_order_date(self, cr, uid, procurement, company, schedule_date, context=None):
"""Return the datetime value to use as Order Date (``date_order``) for the
Purchase Order created to satisfy the given procurement.
@@ -1377,493 +644,179 @@ class procurement_order(osv.osv):
seller_delay = int(procurement.product_id.seller_delay)
return schedule_date - relativedelta(days=seller_delay)
- def _get_product_supplier(self, cr, uid, procurement, context=None):
- ''' returns the main vendor of the procurement's product given as argument'''
- supplierinfo = self.pool['product.supplierinfo']
- company_supplier = supplierinfo.search(cr, uid,
- [('product_tmpl_id', '=', procurement.product_id.product_tmpl_id.id), ('company_id', '=', procurement.company_id.id)], limit=1, context=context)
- if company_supplier:
- return supplierinfo.browse(cr, uid, company_supplier[0], context=context).name
- return procurement.product_id.seller_id
-
- def _get_po_line_values_from_procs(self, cr, uid, procurements, partner, schedule_date, context=None):
- res = {}
- if context is None:
- context = {}
- prod_obj = self.pool.get('product.product')
- acc_pos_obj = self.pool.get('account.fiscal.position')
- #Passing partner_id to context for purchase order line integrity of Line name
- new_context = context.copy()
- new_context.update({'lang': partner.lang, 'partner_id': partner.id})
- names = prod_obj.name_get(cr, uid, [x.product_id.id for x in procurements], context=context)
- names_dict = {}
- for id, name in names:
- names_dict[id] = name
- for procurement in procurements:
- taxes_ids = procurement.product_id.supplier_taxes_id
- # It is necessary to have the appropriate fiscal position to get the right tax mapping
- fp = acc_pos_obj.get_fiscal_position(cr, uid, None, partner.id, context=context)
- if fp:
- fp = acc_pos_obj.browse(cr, uid, fp, context=context)
- taxes = acc_pos_obj.map_tax(cr, uid, fp, taxes_ids)
- name = names_dict[procurement.product_id.id]
- if procurement.product_id.description_purchase:
- name += '\n' + procurement.product_id.description_purchase
- date = schedule_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
- values = {
- 'name': name,
- 'product_id': procurement.product_id.id,
- 'product_uom': procurement.product_id.uom_po_id.id,
- 'date_planned': date,
- 'taxes_id': [(6, 0, taxes)],
- 'procurement_ids': [(4, procurement.id)]
- }
- # calculate price/qty for valid vendor
- qty = self.pool['product.uom']._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty, procurement.product_id.uom_po_id.id)
- new_context.update({'quantity': qty})
- supplier = prod_obj.browse(cr, uid, procurement.product_id.id, context=new_context).selected_seller_id
- price = 0.0
- if supplier:
- price = supplier.price
- purchase_currency = supplier.name.property_purchase_currency_id
- price = self.pool['res.currency'].compute(cr, uid, supplier.currency_id.id, purchase_currency.id, price, context=dict(context or {}, date=date))
- values.update({'product_qty': qty, 'price_unit': price})
- res[procurement.id] = values
- return res
+ @api.multi
+ def _prepare_purchase_order_line(self, po, supplier):
+ self.ensure_one()
+
+ res = self.env['purchase.order.line']._get_name_price_quantity_date(
+ self.product_id,
+ supplier.name,
+ datetime.strptime(po.date_order, DEFAULT_SERVER_DATETIME_FORMAT),
+ self.product_qty,
+ self.product_uom,
+ po.currency_id,
+ order_id=po)
+
+ name = res['name']
+ product_qty = res['quantity']
+ price_unit = res['price_unit']
+ date_planned = res['date_planned']
+
+ taxes = self.product_id.supplier_taxes_id
+ fpos = po.fiscal_position_id
+ taxes_id = fpos.map_tax(taxes) if fpos else []
+
+ return {
+ 'name': name,
+ 'product_qty': product_qty,
+ 'product_id': self.product_id.id,
+ 'product_uom': self.product_uom.id,
+ 'price_unit': price_unit,
+ 'date_planned': date_planned,
+ 'taxes_id': [(6, 0, taxes_id)],
+ 'procurement_ids': [(4, self.id)],
+ 'order_id': po.id,
+ }
+
+ @api.multi
+ def _prepare_purchase_order(self, partner):
+ self.ensure_one()
+ schedule_date = self._get_purchase_schedule_date()
+ purchase_date = self._get_purchase_order_date(schedule_date)
+ fpos = self.env['account.fiscal.position'].get_fiscal_position(partner.id)
+
+ gpo = self.rule_id.group_propagation_option
+ group = (gpo == 'fixed' and self.rule_id.group_id.id) or \
+ (gpo == 'propagate' and self.group_id.id) or False
- def _calc_new_qty_price(self, cr, uid, procurement, po_line=None, cancel=False, context=None):
- if not po_line:
- po_line = procurement.purchase_line_id
- uom_obj = self.pool.get('product.uom')
- qty = uom_obj._compute_qty(cr, uid, procurement.product_uom.id, procurement.product_qty,
- procurement.product_id.uom_po_id.id)
- if cancel:
- qty = -qty
-
- # Make sure we use the minimum quantity of the partner corresponding to the PO
- # This does not apply in case of dropshipping
- supplierinfo_min_qty = 0.0
- if po_line.order_id.location_id.usage != 'customer':
- if po_line.product_id.seller_id.id == po_line.order_id.partner_id.id:
- supplierinfo_min_qty = po_line.product_id.seller_qty
+ return {
+ 'partner_id': partner.id,
+ 'picking_type_id': self.rule_id.picking_type_id.id,
+ 'company_id': self.company_id.id,
+ 'dest_address_id': self.partner_dest_id.id,
+ 'origin': self.origin,
+ 'payment_term_id': partner.property_supplier_payment_term_id.id,
+ 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
+ 'fiscal_position_id': fpos,
+ 'group_id': group
+ }
+
+ @api.multi
+ def make_po(self):
+ cache = {}
+ res = []
+ for procurement in self:
+ if not procurement.product_id.seller_ids:
+ self.message_post([procurement.id],\
+ _('No vendor associated to product %s. Please set one to fix this procurement.') % (procurement.product_id.name))
+ continue
+ supplier = procurement.product_id.seller_ids[0]
+ partner = supplier.name
+ domain = (
+ ('partner_id', '=', partner.id),
+ ('state', '=', 'draft'),
+ ('picking_type_id', '=', procurement.rule_id.picking_type_id.id),
+ ('company_id', '=', procurement.company_id.id),
+ ('dest_address_id', '=', procurement.partner_dest_id.id))
+
+ if domain in cache:
+ po = cache[domain]
else:
- supplierinfo_obj = self.pool.get('product.supplierinfo')
- supplierinfo_ids = supplierinfo_obj.search(cr, uid, [('name', '=', po_line.order_id.partner_id.id), ('product_tmpl_id', '=', po_line.product_id.product_tmpl_id.id)])
- if supplierinfo_ids:
- supplierinfo = supplierinfo_obj.browse(cr, uid, supplierinfo_ids)
- supplierinfo_min_qty = sorted(supplierinfo, key=lambda x: x['min_qty'])[0].min_qty
- if supplierinfo_min_qty == 0.0:
- qty += po_line.product_qty
- else:
- # Recompute quantity by adding existing running procurements.
- for proc in po_line.procurement_ids:
- qty += uom_obj._compute_qty(cr, uid, proc.product_uom.id, proc.product_qty,
- proc.product_id.uom_po_id.id) if proc.state == 'running' else 0.0
- qty = max(qty, supplierinfo_min_qty) if qty > 0.0 else 0.0
-
- price = po_line.price_unit
- if qty != po_line.product_qty:
- price = po_line.product_id.seller_price
- price = self.pool['product.uom']._compute_price(cr, uid, procurement.product_uom.id, price, procurement.product_uom.id)
- return qty, price
-
- def update_origin_po(self, cr, uid, po, proc, context=None):
- pass
-
- def _get_grouping_dicts(self, cr, uid, ids, context=None):
- """
- It will group the procurements according to the pos they should go into. That way, lines going to the same
- po, can be processed at once.
- Returns two dictionaries:
- add_purchase_dicts: key: po value: procs to add to the po
- create_purchase_dicts: key: values for proc to create (not that necessary as they are in procurement => TODO),
- values: procs to add
- """
- po_obj = self.pool.get('purchase.order')
- date = time.strftime(DEFAULT_SERVER_DATE_FORMAT)
- # Regroup POs
- cr.execute("""
- SELECT psi.name, p.id, pr.id, pr.picking_type_id, p.location_id, p.partner_dest_id, p.company_id, p.group_id,
- pr.group_propagation_option, pr.group_id,psi.qty
- FROM procurement_order AS p
- LEFT JOIN procurement_rule AS pr ON pr.id = p.rule_id
- LEFT JOIN procurement_group AS pg ON p.group_id = pg.id,
- product_supplierinfo AS psi, product_product AS pp
- WHERE
- p.product_id = pp.id AND p.id in %s AND psi.product_tmpl_id = pp.product_tmpl_id
- AND (psi.company_id = p.company_id or psi.company_id IS NULL) AND ((psi.date_start IS NULL OR psi.date_start<=%s) AND (psi.date_end IS NULL OR psi.date_end>=%s))
- ORDER BY psi.sequence,
- psi.name, p.rule_id, p.location_id, p.company_id, p.partner_dest_id, p.group_id
- """, (tuple(ids), date, date))
- res = cr.fetchall()
- old = False
- # A giant dict for grouping lines, ... to do at once
- create_purchase_procs = {} # Lines to add to a newly to create po
- add_purchase_procs = {} # Lines to add/adjust in an existing po
- proc_seller = {} # To check we only process one po
- for partner, proc, rule, pick_type, location, partner_dest, company, group, group_propagation, fixed_group, qty in res:
- if not proc_seller.get(proc):
- proc_seller[proc] = partner
- new = partner, rule, pick_type, location, company, group, group_propagation, fixed_group
- if new != old:
- old = new
- dom = [
- ('partner_id', '=', partner), ('state', '=', 'draft'), ('picking_type_id', '=', pick_type),
- ('location_id', '=', location), ('company_id', '=', company), ('dest_address_id', '=', partner_dest)]
- if group_propagation == 'propagate':
- dom += [('group_id', '=', group)]
- elif group_propagation == 'fixed':
- dom += [('group_id', '=', fixed_group)]
- available_draft_po_ids = po_obj.search(cr, uid, dom, context=context)
- available_draft_po = available_draft_po_ids and available_draft_po_ids[0] or False
- # Add to dictionary
- if available_draft_po:
- if add_purchase_procs.get(available_draft_po):
- add_purchase_procs[available_draft_po] += [proc]
- else:
- add_purchase_procs[available_draft_po] = [proc]
- else:
- if create_purchase_procs.get(new):
- create_purchase_procs[new] += [proc]
- else:
- create_purchase_procs[new] = [proc]
- return add_purchase_procs, create_purchase_procs
-
- def make_po(self, cr, uid, ids, context=None):
- res = {}
- po_obj = self.pool.get('purchase.order')
- po_line_obj = self.pool.get('purchase.order.line')
- seq_obj = self.pool.get('ir.sequence')
- uom_obj = self.pool.get('product.uom')
- acc_pos_obj = self.pool.get('account.fiscal.position')
- add_purchase_procs, create_purchase_procs = self._get_grouping_dicts(cr, uid, ids, context=context)
- procs_done = []
-
- # Let us check existing purchase orders and add/adjust lines on them
- for add_purchase in add_purchase_procs.keys():
- procs_done += add_purchase_procs[add_purchase]
- po = po_obj.browse(cr, uid, add_purchase, context=context)
- lines_to_update = {}
- line_values = []
- procurements = self.browse(cr, uid, add_purchase_procs[add_purchase], context=context)
- po_line_ids = po_line_obj.search(cr, uid, [('order_id', '=', add_purchase), ('product_id', 'in', [x.product_id.id for x in procurements])], context=context)
- po_lines = po_line_obj.browse(cr, uid, po_line_ids, context=context)
- po_prod_dict = {}
- for pol in po_lines:
- po_prod_dict[pol.product_id.id] = pol
- procs_to_create = []
- #Check which procurements need a new line and which need to be added to an existing one
- for proc in procurements:
- if po_prod_dict.get(proc.product_id.id):
- po_line = po_prod_dict[proc.product_id.id]
- # FIXME: compute quantity using `_calc_new_qty_price` method.
- # new_qty, new_price = self._calc_new_qty_price(cr, uid, proc, po_line=po_line, context=context)
- uom_id = po_line.product_uom # Convert to UoM of existing line
- qty = uom_obj._compute_qty_obj(cr, uid, proc.product_uom, proc.product_qty, uom_id)
-
- if lines_to_update.get(po_line):
- lines_to_update[po_line] += [(proc, qty)]
- else:
- lines_to_update[po_line] = [(proc, qty)]
- else:
- procs_to_create.append(proc)
-
- # FIXME: these are not real tracking values, it should be fixed if tracking values for one2many
- # are managed
- def format_message(message_description, tracked_values):
- message = ''
- if message_description:
- message = '<span>%s</span>' % message_description
- for name, values in tracked_values.iteritems():
- message += '<div> &nbsp; &nbsp; &bull; <b>%s</b>: ' % name
- message += '%s</div>' % values
- return message
-
- # Update the quantities of the lines that need to
- for line in lines_to_update.keys():
- tot_qty = 0
- for proc, qty in lines_to_update[line]:
- tot_qty += qty
- self.message_post(cr, uid, proc.id, body=_("Quantity added in existing Purchase Order Line"), context=context)
- msg = format_message(_('Quantity added in existing Purchase Order Line'), {'Product': proc.product_id.name, 'Quantity': proc.product_qty, 'Procurement': proc.origin})
- po_obj.message_post(cr, uid, [add_purchase], body=msg, context=context)
- line_values += [(1, line.id, {'product_qty': line.product_qty + tot_qty, 'procurement_ids': [(4, x[0].id) for x in lines_to_update[line]]})]
-
- # Create lines for which no line exists yet
- if procs_to_create:
- partner = po.partner_id
- schedule_date = datetime.strptime(po.minimum_planned_date, DEFAULT_SERVER_DATETIME_FORMAT)
- value_lines = self._get_po_line_values_from_procs(cr, uid, procs_to_create, partner, schedule_date, context=context)
- line_values += [(0, 0, value_lines[x]) for x in value_lines.keys()]
- for proc in procs_to_create:
- self.message_post(cr, uid, [proc.id], body=_("Purchase line created and linked to an existing Purchase Order"), context=context)
- msg = format_message(_('Purchase order line added'), {'Product': proc.product_id.name, 'Quantity': proc.product_qty, 'Procurement': proc.origin})
- po_obj.message_post(cr, uid, [add_purchase], body=msg, context=context)
- po_obj.write(cr, uid, [add_purchase], {'order_line': line_values},context=context)
-
- for procurement in procurements:
- self.update_origin_po(cr, uid, po, procurement, context=context)
-
-
- # Create new purchase orders
- partner_obj = self.pool.get("res.partner")
- new_pos = []
- for create_purchase in create_purchase_procs.keys():
- procs_done += create_purchase_procs[create_purchase]
- line_values = []
- procurements = self.browse(cr, uid, create_purchase_procs[create_purchase], context=context)
- partner = partner_obj.browse(cr, uid, create_purchase[0], context=context)
-
- #Create purchase order itself:
- procurement = procurements[0]
- schedule_date = self._get_purchase_schedule_date(cr, uid, procurement, procurement.company_id, context=context)
- purchase_date = self._get_purchase_order_date(cr, uid, procurement, procurement.company_id, schedule_date, context=context)
-
- value_lines = self._get_po_line_values_from_procs(cr, uid, procurements, partner, schedule_date, context=context)
- line_values += [(0, 0, value_lines[x]) for x in value_lines.keys()]
- name = seq_obj.next_by_code(cr, uid, 'purchase.order') or _('PO: %s') % procurement.name
- gpo = procurement.rule_id.group_propagation_option
- group = (gpo == 'fixed' and procurement.rule_id.group_id.id) or (gpo == 'propagate' and procurement.group_id.id) or False
- fp = acc_pos_obj.get_fiscal_position(cr, uid, None, partner.id, context=context)
- po_vals = {
- 'name': name,
- 'origin': procurement.origin,
- 'partner_id': create_purchase[0],
- 'location_id': procurement.location_id.id,
- 'picking_type_id': procurement.rule_id.picking_type_id.id,
- 'date_order': purchase_date.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
- 'company_id': procurement.company_id.id,
- 'currency_id': partner.property_purchase_currency_id.id or self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id.id,
- 'fiscal_position_id': fp,
- 'payment_term_id': partner.property_supplier_payment_term_id.id,
- 'dest_address_id': procurement.partner_dest_id.id,
- 'group_id': group,
- 'order_line': line_values,
- }
- new_po = po_obj.create(cr, uid, po_vals, context=context)
- new_pos.append(new_po)
- for proc in create_purchase_procs[create_purchase]:
- self.message_post(cr, uid, proc, body=_("Draft Purchase Order created"), context=context)
-
- other_proc_ids = list(set(ids) - set(procs_done))
- res = dict.fromkeys(ids, True)
- if other_proc_ids:
- other_procs = self.browse(cr, uid, other_proc_ids, context=context)
- for procurement in other_procs:
- res[procurement.id] = False
- self.message_post(cr, uid, [procurement.id], _('There is no vendor associated to product %s') % (procurement.product_id.name))
+ po = self.env['purchase.order'].search([dom for dom in domain])
+ po = po[0] if po else False
+ cache[domain] = po
+ if not po:
+ vals = procurement._prepare_purchase_order(partner)
+ po = self.env['purchase.order'].create(vals)
+ cache[domain] = po
+ res += po.ids
+
+ # Create Line
+ po_line = False
+ for line in po.order_line:
+ if line.product_id == procurement.product_id and line.product_uom == procurement.product_uom:
+ res = self.env['purchase.order.line']._get_name_price_quantity_date(
+ self.product_id,
+ partner,
+ datetime.strptime(po.date_order, DEFAULT_SERVER_DATETIME_FORMAT),
+ line.product_qty + procurement.product_qty,
+ self.product_uom,
+ po.currency_id)
+
+ po_line = line.write({
+ 'product_qty': res['quantity'],
+ 'price_unit': res['price_unit'],
+ 'procurement_ids': [(4, procurement.id)]
+ })
+ if not po_line:
+ vals = procurement._prepare_purchase_order_line(po, supplier)
+ self.env['purchase.order.line'].create(vals)
return res
-class product_template(osv.Model):
+class ProductTemplate(models.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')
+
+ @api.model
+ def _get_buy_route(self):
+ buy_route = self.env.ref('purchase.route_warehouse0_buy')
if buy_route:
- return [buy_route]
+ return buy_route.ids
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 = {
- 'property_account_creditor_price_difference': fields.property(
- type='many2one',
- relation='account.account',
- string="Price Difference Account",
- help="This account will be used to value price difference between purchase price and cost price."),
- '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'),
- }
+ @api.multi
+ def _purchase_count(self):
+ for template in self:
+ template.purchase_count = sum([p.purchase_count for p in template.product_variant_ids])
+ return True
- _defaults = {
- 'purchase_ok': 1,
- 'route_ids': _get_buy_route,
- }
+ property_account_creditor_price_difference = fields.Many2one('account.account', string="Price Difference Account",\
+ help="This account will be used to value price difference between purchase price and cost price.")
+ purchase_ok = fields.Boolean('Can be Purchased', default=True)
+ purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
+ purchase_method = fields.Selection([
+ ('purchase', 'On purchased quantities'),
+ ('receive', 'On received quantities'),
+ ], string="Control Purchase Bills", default="receive")
+ route_ids = fields.Many2many(default=lambda self: self._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):
+class ProductProduct(models.Model):
_name = 'product.product'
_inherit = 'product.product'
-
- def _purchase_count(self, cr, uid, ids, field_name, arg, context=None):
- r = dict.fromkeys(ids, 0)
+
+ @api.multi
+ def _purchase_count(self):
domain = [
- ('state', 'in', ['confirmed', 'approved', 'except_picking', 'except_invoice', 'done']),
- ('product_id', 'in', ids),
+ ('state', 'in', ['confirmed', 'approved', 'done']),
+ ('product_id', 'in', self.mapped('id')),
]
- for group in self.pool['purchase.report'].read_group(cr, uid, domain, ['product_id', 'quantity'], ['product_id'], context=context):
+ r = {}
+ for group in self.env['purchase.report'].read_group(domain, ['product_id', 'quantity'], ['product_id']):
r[group['product_id'][0]] = group['quantity']
- return r
-
- def action_view_purchases(self, cr, uid, ids, context=None):
- if isinstance(ids, (int, long)):
- ids = [ids]
- result = self.pool['product.template']._get_act_window_dict(cr, uid, 'purchase.action_purchase_line_product_tree', context=context)
- result['domain'] = "[('product_id','in',[" + ','.join(map(str, ids)) + "])]"
- return result
+ for product in self:
+ product.purchase_count = r.get(product.id, 0)
+ return True
- _columns = {
- 'purchase_count': fields.function(_purchase_count, string='# Purchases', type='integer'),
- }
+ purchase_count = fields.Integer(compute='_purchase_count', string='# Purchases')
-class product_category(osv.Model):
+class ProductCategory(models.Model):
_inherit = "product.category"
- _columns = {
- 'property_account_creditor_price_difference_categ': fields.property(
- type='many2one',
- relation='account.account',
- string="Price Difference Account",
- help="This account will be used to value price difference between purchase price and cost price."),
- }
-
-
-class mail_compose_message(osv.Model):
- _inherit = 'mail.compose.message'
-
- def send_mail(self, cr, uid, ids, auto_commit=False, 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)
- 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 receipt or payment. """
- _inherit = 'account.invoice'
-
- _columns = {
- 'purchase_ids': fields.many2many('purchase.order', 'purchase_invoice_rel', 'invoice_id',
- 'purchase_id', 'Purchases', copy=False,
- help="Purchases linked to this 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)
- for po_id in po_ids:
- purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice received"), context=context)
- purchase_order_obj._set_po_lines_invoiced(cr, user_id, [po_id], context=context)
- return res
- def confirm_paid(self, cr, uid, ids, context=None):
- res = super(account_invoice, self).confirm_paid(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)
- for po_id in po_ids:
- purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice paid"), context=context)
- return res
+ property_account_creditor_price_difference_categ = fields.Many2one(
+ 'account.account', string="Price Difference Account",
+ company_dependent=True,
+ help="This account will be used to value price difference between purchase price and accounting cost.")
-class account_invoice_line(osv.Model):
- """ Override account_invoice_line to add the link to the purchase order line it is related to"""
- _inherit = 'account.invoice.line'
- _columns = {
- 'purchase_line_id': fields.many2one('purchase.order.line',
- 'Purchase Order Line', ondelete='set null', select=True,
- readonly=True),
- }
-
- def move_line_get(self, cr, uid, invoice_id, context=None):
- res = super(account_invoice_line,self).move_line_get(cr, uid, invoice_id, context=context)
- if self.company_id.anglo_saxon_accounting:
- # TDE FIXME: undefined variable
- if inv.type in ('in_invoice','in_refund'):
- for i_line in inv.invoice_line_ids:
- res.extend(self._anglo_saxon_purchase_move_lines(cr, uid, i_line, res, context=context))
- return res
-
- def _anglo_saxon_purchase_move_lines(self, cr, uid, i_line, res, context=None):
- """Return the additional move lines for purchase invoices and refunds.
-
- i_line: An account.invoice.line object.
- res: The move line entries produced so far by the parent move_line_get.
- """
- inv = i_line.invoice_id
- company_currency = inv.company_id.currency_id.id
- if i_line.product_id and i_line.product_id.valuation == 'real_time':
- if i_line.product_id.type == 'product':
- # get the price difference account at the product
- acc = i_line.product_id.property_account_creditor_price_difference and i_line.product_id.property_account_creditor_price_difference.id
- if not acc:
- # if not found on the product get the price difference account at the category
- acc = i_line.product_id.categ_id.property_account_creditor_price_difference_categ and i_line.product_id.categ_id.property_account_creditor_price_difference_categ.id
- a = None
-
- # oa will be the stock input account
- # first check the product, if empty check the category
- oa = i_line.product_id.property_stock_account_input and i_line.product_id.property_stock_account_input.id
- if not oa:
- oa = i_line.product_id.categ_id.property_stock_account_input_categ_id and i_line.product_id.categ_id.property_stock_account_input_categ_id.id
- if oa:
- # get the fiscal position
- fpos = i_line.invoice_id.fiscal_position_id or False
- a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, oa)
- diff_res = []
- account_prec = inv.company_id.currency_id.decimal_places
- # calculate and write down the possible price difference between invoice price and product price
- for line in res:
- if line.get('invl_id', 0) == i_line.id and a == line['account_id']:
- uom = i_line.product_id.uom_id
- valuation_price_unit = self.pool.get('product.uom')._compute_price(cr, uid, uom.id, i_line.product_id.standard_price, i_line.uom_id.id)
- if i_line.product_id.cost_method != 'standard' and i_line.purchase_line_id:
- #for average/fifo/lifo costing method, fetch real cost price from incomming moves
- stock_move_obj = self.pool.get('stock.move')
- valuation_stock_move = stock_move_obj.search(cr, uid, [('purchase_line_id', '=', i_line.purchase_line_id.id)], limit=1, context=context)
- if valuation_stock_move:
- valuation_price_unit = stock_move_obj.browse(cr, uid, valuation_stock_move[0], context=context).price_unit
- if inv.currency_id.id != company_currency:
- valuation_price_unit = self.pool.get('res.currency').compute(cr, uid, company_currency, inv.currency_id.id, valuation_price_unit, context={'date': inv.date_invoice})
- if valuation_price_unit != i_line.price_unit and line['price_unit'] == i_line.price_unit and acc:
- # price with discount and without tax included
- price_unit = self.pool['account.tax'].compute_all(cr, uid, line['taxes'], i_line.price_unit * (1-(i_line.discount or 0.0)/100.0),
- inv.currency_id.id, line['quantity'])['total_excluded']
- price_line = round(valuation_price_unit * line['quantity'], account_prec)
- price_diff = round(price_unit - price_line, account_prec)
- line.update({'price': price_line})
- diff_res.append({
- 'type': 'src',
- 'name': i_line.name[:64],
- 'price_unit': round(price_diff / line['quantity'], account_prec),
- 'quantity': line['quantity'],
- 'price': price_diff,
- 'account_id': acc,
- 'product_id': line['product_id'],
- 'uom_id': line['uom_id'],
- 'account_analytic_id': line['account_analytic_id'],
- 'taxes': line.get('taxes', []),
- })
- return diff_res
- return []
+class MailComposeMessage(models.Model):
+ _inherit = 'mail.compose.message'
-class account_invoice_line(osv.Model):
- """ Override account_invoice_line to add the link to the purchase order line it is related to"""
- _inherit = 'account.invoice.line'
- _columns = {
- 'purchase_line_ids': fields.many2many('purchase.order.line', 'purchase_order_line_invoice_rel', 'invoice_id','order_line_id',
- 'Purchase Order Lines', readonly=True, copy=False)
- }
+ @api.multi
+ def send_mail(self, auto_commit=False):
+ if self._context.get('default_model') == 'purchase.order' and self._context.get('default_res_id'):
+ order = self.env['purchase.order'].browse([self._context['default_res_id']])
+ if order.state == 'draft':
+ order.state = 'sent'
+ return super(MailComposeMessage, self.with_context(mail_post_autofollow=True)).send_mail()
diff --git a/addons/purchase/purchase_order_demo.yml b/addons/purchase/purchase_order_demo.yml
index 8d8625b4911..1bc6e859465 100644
--- a/addons/purchase/purchase_order_demo.yml
+++ b/addons/purchase/purchase_order_demo.yml
@@ -3,86 +3,135 @@
-
!record {model: purchase.order, id: purchase_order_1}:
partner_id: base.res_partner_1
- invoice_method: order
- order_line:
- - product_id: product.product_product_15
- price_unit: 79.80
- product_qty: 15.0
- - product_id: product.product_product_25
- price_unit: 2868.70
- product_qty: 5.0
- - product_id: product.product_product_27
- price_unit: 3297.20
- product_qty: 4.0
-
+-
+ !record {model: purchase.order.line, id: purchase_order_line_1}:
+ order_id: purchase.purchase_order_1
+ product_id: product.product_product_15
+ name: RAM SR3
+ price_unit: 79.80
+ product_qty: 15.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
+-
+ !record {model: purchase.order.line, id: purchase_order_line_2}:
+ order_id: purchase_order_1
+ product_id: product.product_product_25
+ name: Laptop E5023
+ price_unit: 2868.70
+ product_qty: 5.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
+-
+ !record {model: purchase.order.line, id: purchase_order_line_3}:
+ order_id: purchase_order_1
+ product_id: product.product_product_27
+ name: Laptop Customized
+ price_unit: 3297.20
+ product_qty: 4.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
+-
+ Creating second order
-
!record {model: purchase.order, id: purchase_order_2}:
partner_id: base.res_partner_3
- invoice_method: picking
order_line:
- product_id: product.product_product_31
+ name: Multimedia Speakers
price_unit: 132.50
product_qty: 20.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_29
+ name: Pen drive, SP-2
price_unit: 89
product_qty: 5
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
-
!record {model: purchase.order, id: purchase_order_3}:
partner_id: base.res_partner_12
order_line:
- product_id: product.product_product_2
+ name: Support Services
price_unit: 25.50
product_qty: 10.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
-
!record {model: purchase.order, id: purchase_order_4}:
partner_id: base.res_partner_4
order_line:
- product_id: product.product_product_14
+ name: RAM SR2 (kit)
price_unit: 85.50
product_qty: 6.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_20
+ name: Motherboard I9P57
price_unit: 1690
product_qty: 5.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_6
+ name: iPad Mini
price_unit: 800
product_qty: 7.0
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
-
!record {model: purchase.order, id: purchase_order_5}:
partner_id: base.res_partner_2
order_line:
- product_id: product.product_product_22
+ name: Processor Core i5 2.70 Ghz
product_qty: 3
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_24
+ name: Graphics Card
product_qty: 3
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
-
!record {model: purchase.order, id: purchase_order_6}:
partner_id: base.res_partner_1
order_line:
- product_id: product.product_product_38
+ name: Ink Cartridge
price_unit: 58
product_qty: 9
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_39
+ name: Toner Cartridge
product_qty: 3
price_unit: 65
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_44
+ name: GrapWorks Software
product_qty: 4
price_unit: 154.5
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
-
!record {model: purchase.order, id: purchase_order_7}:
partner_id: base.res_partner_4
order_line:
- product_id: product.product_product_12
+ name: Mouse, Wireless
product_qty: 5
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
- product_id: product.product_product_34
+ name: Webcam
product_qty: 15
--
- !python {model: purchase.order} : |
- account_id = self.pool.get('account.account').search(cr, uid, [('user_type_id', '=', ref('account.data_account_type_expenses'))])
- if account_id:
- self.signal_workflow(cr, uid, [ref('purchase_order_2')], 'purchase_confirm')
- self.signal_workflow(cr, uid, [ref('purchase_order_6')], 'purchase_confirm')
+ product_uom: product.product_uom_unit
+ date_planned: !eval time.strftime('%Y-%m-%d')
diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml
index 6dbd7d6ab72..ec497ca41a6 100644
--- a/addons/purchase/purchase_view.xml
+++ b/addons/purchase/purchase_view.xml
@@ -18,9 +18,10 @@
<menuitem
id="menu_purchase_config_pricelist" name="Pricelists"
parent="menu_purchase_config" sequence="20" groups="base.group_no_one"/>
+
<menuitem
- action="product.product_supplierinfo_type_action" id="menu_product_pricelist_action2_purchase"
- parent="menu_purchase_config_pricelist" sequence="1" groups="base.group_no_one"/>
+ action="product.product_supplierinfo_type_action" id="menu_product_pricelist_action2_purchase"
+ parent="menu_purchase_config_pricelist" sequence="1" groups="base.group_no_one"/>
<menuitem
id="menu_product_in_config_purchase" name="Products"
@@ -50,39 +51,16 @@
<!-- Purchase Control Menu -->
<menuitem id="menu_purchase_control" name="Control" parent="base.menu_purchase_root" sequence="4"/>
- <!--Inventory control-->
- <menuitem id="menu_procurement_management_inventory" name="Products"
- parent="menu_purchase_control" sequence="4"/>
+ <!--Inventory control-->
<menuitem action="stock.action_receipt_picking_move" id="menu_action_picking_tree_in_move"
- parent="menu_procurement_management_inventory" sequence="11"/>
+ parent="menu_purchase_control" sequence="11"/>
<!--Invoice control-->
- <menuitem id="menu_procurement_management_invoice" name="Bills"
- parent="menu_purchase_control" sequence="6"/>
-
- <record id="action_invoice_pending" model="ir.actions.act_window">
- <field name="name">Draft Bills</field>
- <field name="res_model">account.invoice</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form,calendar,pivot,graph</field>
- <field eval="False" name="view_id"/>
- <field name="domain">[('type','=','in_invoice')]</field>
- <field name="context">{'default_type':'in_invoice', 'type':'in_invoice', 'journal_type': 'purchase', 'search_default_draft': 1}</field>
- <field name="search_view_id" ref="account.view_account_invoice_filter"/>
- <field name="help" type="html">
- <p class="oe_view_nocontent_create">
- Click to create a draft invoice.
- </p><p>
- Use this menu to control the invoices to be received from your
- vendor. Odoo generates draft invoices from your purchase
- orders or receipts, according to your settings.
- </p><p>
- Once you receive a vendor bill, you can match it with the
- draft invoice and validate it.
- </p>
- </field>
- </record>
-
+ <menuitem
+ id="menu_procurement_management_pending_invoice"
+ action="action_invoice_pending"
+ parent="menu_purchase_control"
+ sequence="13"/>
<record id="product_normal_action_puchased" model="ir.actions.act_window">
<field name="name">Products</field>
@@ -108,13 +86,6 @@
</field>
</record>
-
- <menuitem
- id="menu_procurement_management_pending_invoice"
- action="action_invoice_pending"
- parent="menu_procurement_management_invoice"
- sequence="13"/>
-
<!-- Product menu-->
<menuitem name="Products" id="menu_procurement_partner_contact_form" action="product_normal_action_puchased"
parent="menu_procurement_management" sequence="20"/>
@@ -124,7 +95,7 @@
<field name="model">purchase.order</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
- <calendar string="Calendar View" date_start="minimum_planned_date" color="partner_id">
+ <calendar string="Calendar View" date_start="date_planned" color="partner_id">
<field name="name"/>
<field name="amount_total" widget="monetary"/>
<field name="partner_id"/>
@@ -152,30 +123,49 @@
</field>
</record>
+ <record id="purchase_open_picking" model="ir.actions.act_window">
+ <field name="name">Receptions</field>
+ <field name="res_model">stock.picking</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="domain">[('purchase_id', '=', active_id)]</field>
+ </record>
+
<record id="purchase_order_form" model="ir.ui.view">
<field name="name">purchase.order.form</field>
<field name="model">purchase.order</field>
<field name="arch" type="xml">
<form string="Purchase Order">
<header>
- <button name="bid_received" states="sent" string="Bid Received" class="oe_highlight"/>
- <button name="wkf_send_rfq" states="draft" string="Send RFQ by Email" type="object" context="{'send_rfq':True}" class="oe_highlight"/>
- <button name="wkf_send_rfq" states="sent" string="Re-Send RFQ by Email" type="object" context="{'send_rfq':True}"/>
+ <button name="action_rfq_send" states="draft" string="Send RFQ by Email" type="object" context="{'send_rfq':True}" class="oe_highlight"/>
+ <button name="action_rfq_send" states="sent" string="Re-Send RFQ by Email" type="object" context="{'send_rfq':True}"/>
<button name="print_quotation" string="Print RFQ" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
<button name="print_quotation" string="Re-Print RFQ" type="object" states="sent" groups="base.group_user"/>
- <button name="purchase_confirm" states="draft" string="Confirm Order" id="draft_confirm"/>
- <button name="purchase_confirm" states="bid" string="Confirm Order" class="oe_highlight" id="bid_confirm"/>
- <button name="picking_ok" states="except_picking" string="Manually Corrected"/>
- <button name="invoice_ok" states="except_invoice" string="Manually Corrected"/>
- <button name="purchase_approve" states="confirmed" string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
- <button name="wkf_send_rfq" states="approved" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
- <button name="view_picking" string="Receive Products" type="object" attrs="{'invisible': ['|', ('shipped','=',True), ('state','!=', 'approved')]}" class="oe_highlight"/>
- <button name="view_invoice" string="Receive Invoice" type="object" attrs="{'invisible': ['|', ('invoice_method','in', ['picking', 'manual']), '|', ('state','!=', 'approved'), ('invoiced','=',True) ]}" class="oe_highlight"/>
- <button name="action_cancel_draft" states="cancel,sent,confirmed" string="Set to Draft" type="object" />
- <button name="action_cancel" states="draft,confirmed,sent,bid,approved,except_picking,except_invoice" string="Cancel" type="object" />
- <field name="state" widget="statusbar" statusbar_visible="draft,sent,bid,approved,done" statusbar_colors='{"except_picking":"red","except_invoice":"red","confirmed":"blue"}' readonly="1"/>
+ <button name="button_confirm" type="object" states="draft" string="Confirm Order" id="draft_confirm"/>
+ <button name="button_confirm" type="object" states="sent" string="Confirm Order" class="oe_highlight" id="bid_confirm"/>
+ <button name="button_approve" type="object" states="to approve" string="Approve Order" class="oe_highlight" groups="purchase.group_purchase_manager"/>
+ <button name="action_rfq_send" states="approved" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
+ <button name="%(purchase_open_picking)d" string="Receive Products" type="action" attrs="{'invisible': [('state','not in', ('purchase','done'))]}"/>
+ <button name="button_draft" states="cancel" string="Set to Draft" type="object" />
+ <button name="button_cancel" states="draft,to approve,sent,purchase" string="Cancel" type="object" />
+ <button name="button_done" type="object" string="Set to Done" states="purchase"
+ help="If a purchase order is done, you can not modify it manually anymore. But you will still be able to bill or receive."/>
+ <field name="state" widget="statusbar" statusbar_visible="draft,sent,purchase,done" readonly="1"/>
</header>
<sheet>
+ <div class="oe_button_box" name="button_box">
+ <button type="action"
+ name="%(purchase_open_picking)d"
+ class="oe_stat_button"
+ icon="fa-truck">
+ <field name="picking_count" widget="statinfo" string="Shipment" help="Incoming Shipments"/>
+ </button>
+ <button type="action" name="%(purchase_open_invoice)d"
+ class="oe_stat_button"
+ icon="fa-pencil-square-o">
+ <field name="invoice_count" widget="statinfo" string="Invoices"/>
+ </button>
+ </div>
<div class="oe_title">
<label string="Request for Quotation " attrs="{'invisible': [('state','not in',('draft','sent','bid'))]}"/>
<label string="Purchase Order " attrs="{'invisible': [('state','in',('draft','sent','bid'))]}"/>
@@ -185,81 +175,63 @@
</div>
<group>
<group>
- <field name="partner_id" on_change="onchange_partner_id(partner_id, context)" context="{'search_default_supplier':1, 'default_supplier':1, 'default_customer':0}" domain="[('supplier','=',True)]"/>
+ <field name="partner_id" context="{'search_default_supplier':1, 'default_supplier':1, 'default_customer':0}" domain="[('supplier','=',True)]"/>
<field name="partner_ref"/>
<field name="currency_id" groups="base.group_multi_currency"/>
- <field name="journal_id" invisible='1'/>
</group>
<group>
<field name="date_order"/>
<field name="origin" attrs="{'invisible': [('origin','=',False)]}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
- <field name="related_usage" invisible="1"/>
- <field name="bid_date" invisible="1"/>
- <field name="bid_validity" attrs="{'invisible':[('bid_date','=',False)]}"/>
- <field name="dest_address_id" string="Customer Address" on_change="onchange_dest_address_id(dest_address_id, context)"
- attrs="{'invisible':[('related_usage','!=','customer')],
- 'required': [('related_usage','=', 'customer')]}"
- groups="stock.group_locations"/>
</group>
</group>
<notebook>
<page string="Products">
<field name="order_line">
<tree string="Purchase Order Lines" editable="bottom">
- <field name="product_id"
- on_change="onchange_product_id(product_id, 0, False,
- parent.partner_id, parent.date_order, parent.fiscal_position_id, date_planned,
- name, False, parent.state, True, parent.currency_id,context)"
- context="{'partner_id': parent.partner_id}"/>
+ <field name="product_id" context="{'partner_id': parent.partner_id}"/>
<field name="name"/>
<field name="date_planned"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="account_analytic_id" context="{'default_partner_id':parent.partner_id}" groups="purchase.group_analytic_accounting" domain="[('type','not in',('view','template'))]"/>
- <field name="product_qty"
- on_change="onchange_product_id(product_id, product_qty,
- product_uom, parent.partner_id, parent.date_order, parent.fiscal_position_id,
- date_planned, name, False, parent.state, False, parent.currency_id, context)"/>
- <field name="product_uom" groups="product.group_uom" on_change="onchange_product_uom(product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position_id,date_planned,name,False,parent.state,parent.currency_id,context)"/>
+ <field name="product_qty"/>
+ <field name="qty_received" invisible="not context.get('show_purchase', False)"/>
+ <field name="qty_invoiced" invisible="not context.get('show_purchase', False)"/>
+ <field name="product_uom" groups="product.group_uom"/>
<field name="price_unit"/>
<field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use','=','purchase')]"/>
<field name="price_subtotal" widget="monetary"/>
- <field name="state" invisible="1"/>
</tree>
- <form>
- <group>
+ <form string="Purchase Order Line">
+ <sheet>
<group>
- <field name="product_id"
- on_change="onchange_product_id(product_id, 0, False,
- parent.partner_id, parent.date_order, parent.fiscal_position_id, date_planned,
- name, False, parent.state, True, parent.currency_id, context)"
- context="{'partner_id': parent.partner_id}"/>
- <label for="product_qty"/>
- <div>
- <field name="product_qty" class="oe_inline"
- on_change="onchange_product_id(product_id, product_qty,
- product_uom, parent.partner_id, parent.date_order, parent.fiscal_position_id,
- date_planned, name, False, parent.state, False, parent.currency_id, context)"/>
- <field name="product_uom" groups="product.group_uom" class="oe_inline"
- on_change="onchange_product_uom(product_id,product_qty,product_uom,parent.partner_id, parent.date_order,parent.fiscal_position_id,date_planned,name,False,parent.state,parent.currency_id,context)"/>
- </div>
- <field name="price_unit"/>
+ <group>
+ <field name="product_id"
+ context="{'partner_id': parent.partner_id}"/>
+ <label for="product_qty"/>
+ <div>
+ <field name="product_qty"
+ class="oe_inline"/>
+ <field name="product_uom" groups="product.group_uom" class="oe_inline"/>
+ </div>
+ <field name="price_unit"/>
+ </group>
+ <group>
+ <field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use', '=', 'purchase')]"/>
+ <field name="date_planned" widget="date"/>
+ <field name="account_analytic_id" colspan="2" groups="purchase.group_analytic_accounting"/>
+ <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
+ </group>
</group>
- <group>
- <field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use', '=', 'purchase')]"/>
- <field name="date_planned" widget="date"/>
- <field name="account_analytic_id" colspan="2" groups="purchase.group_analytic_accounting"/>
- <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
- </group>
- </group>
- <notebook>
- <page string="Notes">
- <field name="name"/>
- </page><page string="Invoices and Incoming Shipments">
- <field name="invoice_lines"/>
- <field name="move_ids"/>
- </page>
- </notebook>
+ <notebook>
+ <page string="Notes">
+ <field name="name"/>
+ </page><page string="Invoices and Incoming Shipments">
+ <field name="invoice_lines"/>
+ <field name="move_ids"/>
+ </page>
+ </notebook>
+ </sheet>
</form>
</field>
<group class="oe_subtotal_footer oe_right">
@@ -267,8 +239,6 @@
<field name="amount_tax" widget="monetary" options="{'currency_field': 'currency_id'}"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total"/>
- <button name="button_dummy"
- states="draft" string="(update)" type="object" class="oe_edit_only oe_link"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</group>
@@ -278,19 +248,14 @@
<page string="Deliveries &amp; Invoices">
<group>
<group>
- <field name="minimum_planned_date"/>
- <field name="picking_type_id" on_change="onchange_picking_type_id(picking_type_id, context)" domain="[('code','=','incoming')]" options="{'no_create': True}" context="{'special_shortened_wh_name': True}" groups="stock.group_locations"/>
- <field name="location_id" groups="stock.group_locations" string="Location"/>
- <field name="shipped" groups="base.group_no_one"/>
+ <field name="date_planned"/>
+ <field name="picking_type_id" domain="[('code','=','incoming')]" options="{'no_create': True}" context="{'special_shortened_wh_name': True}" groups="stock.group_locations"/>
<field name="incoterm_id"/>
</group>
<group>
- <field name="invoice_method"/>
- <field name="invoiced" invisible="1"/>
- <field name="payment_term_id" options="{'no_open': True, 'no_create': True}"/>
- <field name="fiscal_position_id" attrs="{'readonly': [('invoiced','=', True)]}" />
- <!-- We do not need these fields anymore, the information is in open chatter -->
- <field name="validator" groups="base.group_no_one"/>
+ <field name="invoice_status"/>
+ <field name="payment_term_id" options="{'no_open': True, 'no_create': True}" attrs="{'readonly': [('invoice_status','=', 'invoiced')]}"/>
+ <field name="fiscal_position_id" attrs="{'readonly': [('invoice_status','=', 'invoiced')]}" />
<field name="date_approve" groups="base.group_no_one"/>
</group>
</group>
@@ -305,7 +270,7 @@
</field>
</record>
- <record id="view_request_for_quotation_filter" model="ir.ui.view">
+ <record id="view_purchase_order_filter" model="ir.ui.view">
<field name="name">request.quotation.select</field>
<field name="model">purchase.order</field>
<field name="arch" type="xml">
@@ -314,45 +279,21 @@
<field name="partner_id" operator="child_of"/>
<field name="product_id"/>
<field name="create_uid"/>
- <filter name="draft" string="Quotations" domain="[('state','=','draft')]"/>
- <filter name="approved" string="Approved" domain="[('state','in',('approved','done'))]"/>
- <filter name="exception" string="Exception" domain="[('state','in',('except_invoice','except_picking'))]"/>
+ <filter name="draft" string="Quotations" domain="[('state','in',('draft','sent','to approve'))]"/>
+ <filter name="approved" string="Purchase Orders" domain="[('state','in',('purchase','done'))]"/>
<separator/>
- <filter name="not_invoiced" string="Not Invoiced" domain="[('invoice_ids','=', False)]" help="Purchase orders that include lines not invoiced."/>
+ <filter name="not_invoiced" string="Not Invoiced" domain="[('invoice_status','=', 'to invoice')]" help="Purchase orders that include lines not invoiced."/>
+ <filter name="invoiced" string="Invoiced" domain="[('invoice_status','=', 'invoiced')]" help="Purchase orders that include lines not invoiced."/>
+ <filter string="New Mail" name="message_unread" domain="[('message_unread','=',True)]"/>
<group expand="0" string="Group By">
<filter string="Vendor" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Order Date" domain="[]" context="{'group_by':'date_order'}"/>
- <filter string="Expected Date" domain="[]" context="{'group_by':'minimum_planned_date'}"/>
+ <filter string="Expected Date" domain="[]" context="{'group_by':'date_planned'}"/>
</group>
</search>
</field>
</record>
- <record id="view_purchase_order_filter" model="ir.ui.view">
- <field name="name">purchase.order.list.select</field>
- <field name="model">purchase.order</field>
- <field name="arch" type="xml">
- <search string="Search Purchase Order">
- <field name="name" string="Reference"/>
- <field name="partner_id" operator="child_of"/>
- <field name="product_id"/>
- <field name="create_uid"/>
- <filter name="draft" string="Quotations" domain="[('state','in',('draft','sent'))]"/>
- <filter name="approved" string="Purchase Orders" domain="[('state','not in',('draft','cancel'))]"/>
- <filter name="todo" string="To Do" domain="[('state','not in',('cancel','done'))]"/>
- <filter name="exception" string="Exception" domain="[('state','in',('except_invoice','except_picking'))]"/>
- <separator/>
- <filter name="not_invoiced" string="Not Invoiced" domain="[('invoice_ids','=', False)]"/>
- <separator/>
- <filter string="Important Messages" name="message_needaction" domain="[('message_needaction','=',True)]"/>
- <group expand="0" string="Group By">
- <filter string="Vendor" domain="[]" context="{'group_by':'partner_id'}"/>
- <filter string="Order Month" domain="[]" context="{'group_by':'date_order'}"/>
- <filter string="Expected Month" domain="[]" context="{'group_by':'minimum_planned_date'}"/>
- </group>
- </search>
- </field>
- </record>
<!-- Purchase Orders Kanban View -->
<record model="ir.ui.view" id="view_sale_order_kanban">
@@ -381,7 +322,7 @@
<span><t t-esc="record.name.value"/> <t t-esc="record.date_order.value and record.date_order.value.split(' ')[0] or False"/></span>
</div>
<div class="col-xs-6">
- <span t-attf-class="pull-right text-right label #{['draft', 'cancel'].indexOf(record.state.raw_value) > -1 ? 'label-default' : ['except_picking', 'except_invoice'].indexOf(record.state.raw_value) > -1 ? 'label-danger' : ['approved'].indexOf(record.state.raw_value) > -1 ? 'label-warning' : ['done'].indexOf(record.state.raw_value) > -1 ? 'label-success' : 'label-primary'}"><t t-esc="record.state.value"/></span>
+ <span t-attf-class="pull-right text-right label #{['draft', 'cancel'].indexOf(record.state.raw_value) > -1 ? 'label-default' : ['approved'].indexOf(record.state.raw_value) > -1 ? 'label-warning' : ['done'].indexOf(record.state.raw_value) > -1 ? 'label-success' : 'label-primary'}"><t t-esc="record.state.value"/></span>
</div>
</div>
</div>
@@ -395,13 +336,13 @@
<field name="name">purchase.order.tree</field>
<field name="model">purchase.order</field>
<field name="arch" type="xml">
- <tree decoration-bf="message_needaction==True" decoration-muted="state=='cancel'" decoration-info="state in ('wait','confirmed')" decoration-danger="state in ('except_invoice','except_picking')" string="Purchase Order">
- <field name="message_needaction" invisible="1"/>
+ <tree decoration-bf="message_unread==True" decoration-muted="state=='cancel'" decoration-info="state in ('wait','confirmed')" string="Purchase Order">
+ <field name="message_unread" invisible="1"/>
<field name="name" string="Reference"/>
<field name="date_order" />
<field name="partner_id"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
- <field name="minimum_planned_date" invisible="context.get('quotation_only', False)"/>
+ <field name="date_planned" invisible="context.get('quotation_only', False)"/>
<field name="origin"/>
<field name="amount_untaxed" sum="Total Untaxed amount" string="Untaxed" widget="monetary"/>
<field name="amount_total" sum="Total amount" widget="monetary"/>
@@ -440,7 +381,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">purchase.order</field>
<field name="view_mode">tree,kanban,form,pivot,graph,calendar</field>
- <field name="context">{'search_default_todo':1}</field>
+ <field name="context">{'search_default_todo':1, 'show_purchase': True}</field>
<field name="domain">[('state','not in',('draft','sent','bid', 'confirmed'))]</field>
<field name="search_view_id" ref="view_purchase_order_filter"/>
<field name="help" type="html">
@@ -456,42 +397,6 @@
</record>
<menuitem action="purchase_form_action" id="menu_purchase_form_action" parent="menu_procurement_management" sequence="6"/>
- <record id="purchase_order_line_form" model="ir.ui.view">
- <field name="name">purchase.order.line.form</field>
- <field name="model">purchase.order.line</field>
- <field name="arch" type="xml">
- <form string="Purchase Order Line">
- <sheet>
- <group>
- <group>
- <field name="product_id"/>
- <label for="product_qty"/>
- <div>
- <field name="product_qty" class="oe_inline"/>
- <field name="product_uom" groups="product.group_uom" class="oe_inline"/>
- </div>
- <field name="price_unit"/>
- </group>
- <group>
- <field name="taxes_id" widget="many2many_tags" domain="[('type_tax_use', '=', 'purchase')]"/>
- <field name="date_planned" widget="date"/>
- <field name="account_analytic_id" colspan="2" groups="purchase.group_analytic_accounting"/>
- <field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
- </group>
- </group>
- <notebook>
- <page string="Notes">
- <field name="name"/>
- </page><page string="Invoices and Incoming Shipments">
- <field name="invoice_lines"/>
- <field name="move_ids"/>
- </page>
- </notebook>
- </sheet>
- </form>
- </field>
- </record>
-
<record id="purchase_order_line_tree" model="ir.ui.view">
<field name="name">purchase.order.line.tree</field>
<field name="model">purchase.order.line</field>
@@ -506,12 +411,10 @@
<field name="product_uom" groups="product.group_uom"/>
<field name="price_subtotal" widget="monetary"/>
<field name="date_planned" widget="date" width="135"/>
- <field name="state" invisible="1"/>
- <field name="invoiced" invisible="1"/>
</tree>
</field>
</record>
-
+
<record id="purchase_order_line_form2" model="ir.ui.view">
<field name="name">purchase.order.line.form2</field>
<field name="model">purchase.order.line</field>
@@ -521,7 +424,7 @@
<sheet>
<label for="order_id" class="oe_edit_only"/>
<h1>
- <field name="order_id" class="oe_inline" domain="[('invoice_method','=','manual')]" />
+ <field name="order_id" class="oe_inline"/>
<label string="," attrs="{'invisible':[('date_order','=',False)]}"/>
<field name="date_order" class="oe_inline"/>
</h1>
@@ -543,7 +446,6 @@
<field name="date_planned" widget="date" readonly="1"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"/>
<field name="account_analytic_id" colspan="4" groups="purchase.group_analytic_accounting"/>
- <field name="invoiced"/>
</group>
</group>
<field name="name"/>
@@ -574,42 +476,6 @@
</field>
</record>
- <record id="purchase_line_form_action2" model="ir.actions.act_window">
- <field name="name">Purchase Order Lines</field>
- <field name="type">ir.actions.act_window</field>
- <field name="res_model">purchase.order.line</field>
- <field name="domain">[('order_id.invoice_method','=','manual'), ('state','in',('confirmed','done')), ('invoiced', '=', False)]</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form</field>
- <field name="search_view_id" ref="purchase_order_line_search"/>
- <field name="help" type="html">
- <p>
- Here you can track all the lines of purchase orders where the
- invoicing is "Based on Purchase Order Lines", and for which you
- have not received a vendor bill yet. You can generate a
- draft vendor bill based on the lines from this list.
- </p>
- </field>
- </record>
-
- <record id="purchase_line_form_action_tree2" model="ir.actions.act_window.view">
- <field eval="1" name="sequence"/>
- <field name="view_mode">tree</field>
- <field name="view_id" ref="purchase_order_line_tree"/>
- <field name="act_window_id" ref="purchase_line_form_action2"/>
- </record>
- <record id="purchase_line_form_action_form2" model="ir.actions.act_window.view">
- <field eval="2" name="sequence"/>
- <field name="view_mode">form</field>
- <field name="view_id" ref="purchase_order_line_form2"/>
- <field name="act_window_id" ref="purchase_line_form_action2"/>
- </record>
- <menuitem
- action="purchase_line_form_action2"
- id="menu_purchase_line_order_draft"
- parent="menu_procurement_management_invoice"
- sequence="72"/>
-
<!-- Procurements -->
<record id="view_procurement_form_inherit" model="ir.ui.view">
@@ -636,6 +502,9 @@
<label for="purchase_ok"/>
</div>
</div>
+ <field name="uom_po_id" position="after">
+ <field name="purchase_method" widget="radio" groups="base.group_no_one"/>
+ </field>
</field>
</record>
@@ -664,7 +533,8 @@
</record>
<record model="ir.actions.act_window" id="action_purchase_line_product_tree">
- <field name="context">{}</field><!-- force empty -->
+ <field name="context">{}</field>
+ <field name="domain">[('product_id','in',active_ids)]</field>
<field name="name">Purchases</field>
<field name="res_model">purchase.order.line</field>
<field name="view_id" ref="purchase_order_line_tree"/>
@@ -691,25 +561,9 @@
<field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
<field name="arch" type="xml">
<div name="button_box" position="inside">
- <button class="oe_stat_button" name="action_view_purchases" type="object"
- icon="fa-shopping-cart"
- groups="base.group_no_one">
- <field string="Purchases" name="purchase_count" widget="statinfo"/>
- </button>
- </div>
- </field>
- </record>
-
- <record id="view_product_normal_purchase_buttons_from" model="ir.ui.view">
- <field name="name">product.product.purchase.button.inherit</field>
- <field name="model">product.product</field>
- <field name="inherit_id" ref="product.product_normal_form_view"/>
- <field name="groups_id" eval="[(4, ref('purchase.group_purchase_user'))]"/>
- <field name="arch" type="xml">
- <div name="button_box" position="inside">
- <button class="oe_stat_button" name="action_view_purchases" type="object"
- icon="fa-shopping-cart"
- groups="base.group_no_one">
+ <button class="oe_stat_button" name="%(purchase.action_purchase_line_product_tree)d"
+ type="action" icon="fa-shopping-cart"
+ groups="base.group_no_one">
<field string="Purchases" name="purchase_count" widget="statinfo"/>
</button>
</div>
diff --git a/addons/purchase/purchase_workflow.xml b/addons/purchase/purchase_workflow.xml
deleted file mode 100644
index 771bed2685a..00000000000
--- a/addons/purchase/purchase_workflow.xml
+++ /dev/null
@@ -1,231 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
-
- <record id="purchase_order" model="workflow">
- <field name="name">Purchase Order Basic Workflow</field>
- <field name="osv">purchase.order</field>
- <field name="on_create">True</field>
- </record>
-
- <record id="act_draft" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="flow_start">True</field>
- <field name="name">draft</field>
- </record>
- <record id="act_sent" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">sent</field>
- <field name="kind">function</field>
- <field name="action">write({'state':'sent'})</field>
- </record>
- <record id="act_bid" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">bid</field>
- <field name="kind">function</field>
- <field name="action">wkf_bid_received()</field>
- </record>
- <record id="act_confirmed" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">confirmed</field>
- <field name="split_mode">OR</field>
- <field name="kind">function</field>
- <field name="action">wkf_confirm_order()</field>
- </record>
- <record id="act_cancel" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">cancel</field>
- <field name="kind">function</field>
- <field name="flow_stop">True</field>
- <field name="action">wkf_action_cancel()</field>
- </record>
- <record id="act_except_invoice" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">except_invoice</field>
- <field name="kind">function</field>
- <field name="action">write({'state':'except_invoice'})</field>
- </record>
- <record id="act_except_picking" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">except_picking</field>
- <field name="kind">function</field>
- <field name="action">write({'state':'except_picking'})</field>
- </record>
- <record id="act_router" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">router</field>
- <field name="split_mode">OR</field>
- <field name="kind">function</field>
- <field name="join_mode">AND</field>
- <field name="action">wkf_approve_order()</field>
- </record>
- <record id="act_invoice" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">invoice</field>
- <field name="kind">subflow</field>
- <field name="subflow_id" search="[('osv','=','account.invoice')]"/>
- <field name="action">action_invoice_create()</field>
- </record>
- <record id="act_invoice_done" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">invoice_done</field>
- <field name="action">invoice_done()</field>
- <field name="kind">function</field>
- </record>
- <record id="act_invoice_end" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">invoice_end</field>
- </record>
- <record id="act_picking" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">picking</field>
- <field name="kind">function</field>
- <field name="action">action_picking_create()</field>
- </record>
-
-
-
- <record id="act_picking_done" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">picking_done</field>
- <field name="action">picking_done()</field>
- <field name="kind">function</field>
- </record>
- <record id="act_done" model="workflow.activity">
- <field name="wkf_id" ref="purchase_order"/>
- <field name="name">done</field>
- <field name="action">wkf_po_done()</field>
- <field name="kind">function</field>
- <field name="flow_stop">True</field>
- <field name="join_mode">AND</field>
- </record>
-
- <record id="trans_draft_confirmed" model="workflow.transition">
- <field name="act_from" ref="act_draft"/>
- <field name="act_to" ref="act_confirmed"/>
- <field name="signal">purchase_confirm</field>
- </record>
- <record id="trans_draft_sent" model="workflow.transition">
- <field name="act_from" ref="act_draft"/>
- <field name="act_to" ref="act_sent"/>
- <field name="signal">send_rfq</field>
- </record>
- <record id="trans_bid_confirmed" model="workflow.transition">
- <field name="act_from" ref="act_bid"/>
- <field name="act_to" ref="act_confirmed"/>
- <field name="signal">purchase_confirm</field>
- </record>
- <record id="trans_sent_bid" model="workflow.transition">
- <field name="act_from" ref="act_sent"/>
- <field name="act_to" ref="act_bid"/>
- <field name="signal">bid_received</field>
- </record>
- <record id="trans_sent_cancel" model="workflow.transition">
- <field name="act_from" ref="act_sent"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- </record>
- <record id="trans_bid_cancel" model="workflow.transition">
- <field name="act_from" ref="act_bid"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- </record>
-
- <record id="trans_confirmed_cancel" model="workflow.transition">
- <field name="act_from" ref="act_confirmed"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- </record>
- <record id="trans_draft_cancel" model="workflow.transition">
- <field name="act_from" ref="act_draft"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- </record>
- <record id="trans_confirmed_router" model="workflow.transition">
- <field name="act_from" ref="act_confirmed"/>
- <field name="act_to" ref="act_router"/>
- <!-- <field name="signal">purchase_approve</field> removed from simplification/useability -->
- </record>
- <record id="trans_router_picking" model="workflow.transition">
- <field name="act_from" ref="act_router"/>
- <field name="act_to" ref="act_picking"/>
- <field name="condition">has_stockable_product()</field>
- </record>
- <record id="trans_router_picking_done" model="workflow.transition">
- <field name="act_from" ref="act_router"/>
- <field name="act_to" ref="act_picking_done"/>
- <field name="condition">not has_stockable_product()</field>
- </record>
- <record id="trans_router_invoice" model="workflow.transition">
- <field name="act_from" ref="act_router"/>
- <field name="act_to" ref="act_invoice"/>
- <field name="condition">invoice_method=='order'</field>
- </record>
- <record id="trans_router_invoice_no_order" model="workflow.transition">
- <field name="act_from" ref="act_router"/>
- <field name="act_to" ref="act_invoice_end"/>
- <field name="condition">invoice_method&lt;&gt;'order'</field>
- </record>
- <record id="trans_except_picking_picking_done" model="workflow.transition">
- <field name="act_from" ref="act_except_picking"/>
- <field name="act_to" ref="act_picking_done"/>
- <field name="signal">picking_ok</field>
- </record>
- <record id="trans_except_invoice_invoice_done" model="workflow.transition">
- <field name="act_from" ref="act_except_invoice"/>
- <field name="act_to" ref="act_invoice_done"/>
- <field name="signal">invoice_ok</field>
- </record>
- <record id="trans_except_picking" model="workflow.transition">
- <field name="act_from" ref="act_except_picking"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- </record>
- <record id="trans_except_invoice" model="workflow.transition">
- <field name="act_from" ref="act_except_invoice"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- </record>
- <record id="trans_picking_except_picking" model="workflow.transition">
- <field name="act_from" ref="act_picking"/>
- <field name="act_to" ref="act_except_picking"/>
- <field name="signal">picking_cancel</field>
- </record>
- <record id="trans_invoice_except_invoice" model="workflow.transition">
- <field name="act_from" ref="act_invoice"/>
- <field name="act_to" ref="act_except_invoice"/>
- <field name="signal">subflow.cancel</field>
- </record>
- <record id="trans_picking_picking_done" model="workflow.transition">
- <field name="act_from" ref="act_picking"/>
- <field name="act_to" ref="act_picking_done"/>
- <field name="signal">picking_done</field>
-
- </record>
- <record id="trans_invoice_invoice_done" model="workflow.transition">
- <field name="act_from" ref="act_invoice"/>
- <field name="act_to" ref="act_invoice_done"/>
- <field name="signal">subflow.paid</field>
- </record>
- <record id="trans_picking_done_done" model="workflow.transition">
- <field name="act_from" ref="act_picking_done"/>
- <field name="act_to" ref="act_done"/>
- </record>
- <record id="trans_invoice_done_done" model="workflow.transition">
- <field name="act_from" ref="act_invoice_done"/>
- <field name="act_to" ref="act_invoice_end"/>
- </record>
- <record id="trans_invoice_end_done" model="workflow.transition">
- <field name="act_from" ref="act_invoice_end"/>
- <field name="act_to" ref="act_done"/>
- <field name="condition">invoiced</field>
- </record>
- <record id="trans_invoice_end_cancel" model="workflow.transition">
- <field name="act_from" ref="act_invoice_end"/>
- <field name="act_to" ref="act_cancel"/>
- <field name="signal">purchase_cancel</field>
- <field name="condition">invoice_method&lt;&gt;'order'</field>
- </record>
-
- </data>
-</openerp>
diff --git a/addons/purchase/report/purchase_report.py b/addons/purchase/report/purchase_report.py
index d8443d88db2..e741464340a 100644
--- a/addons/purchase/report/purchase_report.py
+++ b/addons/purchase/report/purchase_report.py
@@ -14,20 +14,17 @@ class purchase_report(osv.osv):
_auto = False
_columns = {
'date': fields.datetime('Order Date', readonly=True, help="Date on which this document has been created"), # TDE FIXME master: rename into date_order
- 'state': fields.selection([('draft', 'Request for Quotation'),
- ('confirmed', 'Waiting Vendor Ack'),
- ('approved', 'Approved'),
- ('except_picking', 'Shipping Exception'),
- ('except_invoice', 'Invoice Exception'),
- ('done', 'Done'),
- ('cancel', 'Cancelled')],'Order Status', readonly=True),
+ 'state': fields.selection([('draft', 'Draft RFQ'),
+ ('sent', 'RFQ Sent'),
+ ('to approve', 'To Approve'),
+ ('purchase', 'Purchase Order'),
+ ('done', 'Done'),
+ ('cancel', 'Cancelled')
+ ],'Order Status', readonly=True),
'product_id':fields.many2one('product.product', 'Product', readonly=True),
'picking_type_id': fields.many2one('stock.warehouse', 'Warehouse', readonly=True),
- 'location_id': fields.many2one('stock.location', 'Destination', readonly=True),
'partner_id':fields.many2one('res.partner', 'Vendor', readonly=True),
'date_approve':fields.date('Date Approved', readonly=True),
- 'expected_date':fields.date('Expected Date', readonly=True),
- 'validator' : fields.many2one('res.users', 'Validated By', readonly=True),
'product_uom' : fields.many2one('product.uom', 'Reference Unit of Measure', required=True),
'company_id':fields.many2one('res.company', 'Company', readonly=True),
'currency_id': fields.many2one('res.currency', 'Currency', readonly=True),
@@ -64,11 +61,9 @@ class purchase_report(osv.osv):
select
min(l.id) as id,
s.date_order as date,
- l.state,
+ s.state,
s.date_approve,
- s.minimum_planned_date as expected_date,
s.dest_address_id,
- s.validator,
spt.warehouse_id as picking_type_id,
s.partner_id as partner_id,
s.create_uid as user_id,
@@ -78,7 +73,6 @@ class purchase_report(osv.osv):
p.product_tmpl_id,
t.categ_id as category_id,
t.uom_id as product_uom,
- s.location_id as location_id,
sum(l.product_qty/u.factor*u2.factor) as quantity,
extract(epoch from age(s.date_approve,s.date_order))/(24*60*60)::decimal(16,2) as delay,
extract(epoch from age(l.date_planned,s.date_order))/(24*60*60)::decimal(16,2) as delay_pass,
@@ -108,20 +102,17 @@ class purchase_report(osv.osv):
s.create_uid,
s.partner_id,
u.factor,
- s.location_id,
l.price_unit,
s.date_approve,
l.date_planned,
l.product_uom,
- s.minimum_planned_date,
- s.validator,
s.dest_address_id,
s.fiscal_position_id,
l.product_id,
p.product_tmpl_id,
t.categ_id,
s.date_order,
- l.state,
+ s.state,
spt.warehouse_id,
u.uom_type,
u.category_id,
diff --git a/addons/purchase/report/purchase_report_view.xml b/addons/purchase/report/purchase_report_view.xml
index 7fa9927ce2a..9d8ec2f38bd 100644
--- a/addons/purchase/report/purchase_report_view.xml
+++ b/addons/purchase/report/purchase_report_view.xml
@@ -61,13 +61,10 @@
<field name="product_id"/>
<group expand="0" string="Extended Filters">
<field name="user_id"/>
- <field name="validator"/>
- <field name="location_id"/>
<field name="picking_type_id"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="date"/>
<field name="date_approve"/>
- <field name="expected_date"/>
</group>
<group expand="1" string="Group By">
<filter string="Vendor" name="group_partner_id" context="{'group_by':'partner_id'}"/>
diff --git a/addons/purchase/res_config.py b/addons/purchase/res_config.py
index 5732ec807b0..f903d762a70 100644
--- a/addons/purchase/res_config.py
+++ b/addons/purchase/res_config.py
@@ -15,11 +15,6 @@ class purchase_config_settings(osv.osv_memory):
], "Product Variants",
help='Work with product variant allows you to define some variant of the same products, an ease the product management in the ecommerce for example',
implied_group='product.group_product_variant'),
- 'default_invoice_method': fields.selection(
- [('manual', 'Control vendor bill on purchase order line'),
- ('picking', 'Control vendor bill on incoming shipments'),
- ('order', 'Control vendor bill on a pregenerated draft invoice'),
- ], 'Default invoicing control method', required=True, default_model='purchase.order'),
'group_uom':fields.selection([
(0, 'Products have only one unit of measure (easier)'),
(1, 'Some products may be sold/puchased in different unit of measures (advanced)')
@@ -53,10 +48,6 @@ class purchase_config_settings(osv.osv_memory):
'-This installs the module stock_dropshipping.'),
}
- _defaults = {
- 'default_invoice_method': 'order',
- }
-
class account_config_settings(osv.osv_memory):
_inherit = 'account.config.settings'
diff --git a/addons/purchase/res_config_view.xml b/addons/purchase/res_config_view.xml
index 3f767c0b349..6d6684d76f9 100644
--- a/addons/purchase/res_config_view.xml
+++ b/addons/purchase/res_config_view.xml
@@ -22,12 +22,6 @@
<button string="Apply" type="object" name="execute" class="oe_highlight"/>
<button string="Cancel" type="object" name="cancel" class="oe_link"/>
</header>
- <group string="Invoicing Process">
- <label for="id" string="Default Invoicing Settings"/>
- <div>
- <field name="default_invoice_method" widget="radio"/>
- </div>
- </group>
<group string="Purchase Order">
<field name="group_product_variant" widget="radio"/>
<field name="group_uom" widget="radio"/>
diff --git a/addons/purchase/stock.py b/addons/purchase/stock.py
index 9a774dc5ba9..582956263ad 100644
--- a/addons/purchase/stock.py
+++ b/addons/purchase/stock.py
@@ -6,6 +6,14 @@ from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.exceptions import UserError
+class stock_picking(osv.osv):
+ _inherit = 'stock.picking'
+ _columns = {
+ 'purchase_id': fields.related('move_lines', 'purchase_line_id', 'order_id', string="Purchase Orders",
+ readonly=True, relation="many2one"),
+ }
+
+
class stock_move(osv.osv):
_inherit = 'stock.move'
_columns = {
@@ -18,34 +26,8 @@ class stock_move(osv.osv):
""" Returns the unit price to store on the quant """
if move.purchase_line_id:
return move.price_unit
-
return super(stock_move, self).get_price_unit(cr, uid, move, context=context)
- def write(self, cr, uid, ids, vals, context=None):
- if isinstance(ids, (int, long)):
- ids = [ids]
- res = super(stock_move, self).write(cr, uid, ids, vals, context=context)
- from openerp import workflow
- if vals.get('state') in ['done', 'cancel']:
- po_to_check = []
- for move in self.browse(cr, uid, ids, context=context):
- if move.purchase_line_id and move.purchase_line_id.order_id:
- order = move.purchase_line_id.order_id
- order_id = order.id
- # update linked purchase order as superuser as the warehouse
- # user may not have rights to access purchase.order
- if self.pool.get('purchase.order').test_moves_done(cr, uid, [order_id], context=context):
- workflow.trg_validate(SUPERUSER_ID, 'purchase.order', order_id, 'picking_done', cr)
- if self.pool.get('purchase.order').test_moves_except(cr, uid, [order_id], context=context):
- workflow.trg_validate(SUPERUSER_ID, 'purchase.order', order_id, 'picking_cancel', cr)
- if order_id not in po_to_check and vals['state'] == 'cancel' and order.invoice_method == 'picking':
- po_to_check.append(order_id)
- # Some moves which are cancelled might be part of a PO line which is partially
- # invoiced, so we check if some PO line can be set on "invoiced = True".
- if po_to_check:
- self.pool.get('purchase.order')._set_po_lines_invoiced(cr, uid, po_to_check, context=context)
- return res
-
def copy(self, cr, uid, id, default=None, context=None):
default = default or {}
context = context or {}
@@ -54,121 +36,6 @@ class stock_move(osv.osv):
default['purchase_line_id'] = False
return super(stock_move, self).copy(cr, uid, id, default, context)
- def _get_master_data(self, cr, uid, move, inv_type, context=None):
- if inv_type == 'in_invoice' and move.purchase_line_id:
- purchase_order = move.purchase_line_id.order_id
- return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id
- if inv_type == 'in_refund' and move.origin_returned_move_id.purchase_line_id:
- purchase_order = move.origin_returned_move_id.purchase_line_id.order_id
- return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id
- elif inv_type in ('in_invoice', 'in_refund') and move.picking_id:
- # In case of an extra move, it is better to use the data from the original moves
- for purchase_move in move.picking_id.move_lines:
- if purchase_move.purchase_line_id:
- purchase_order = purchase_move.purchase_line_id.order_id
- return purchase_order.partner_id, purchase_order.create_uid.id, purchase_order.currency_id.id
-
- partner = move.picking_id and move.picking_id.partner_id or False
- code = self.get_code_from_locs(cr, uid, move, context=context)
- if partner and code == 'incoming':
- currency = purchase_order.currency_id.id
- return partner, uid, currency
- return super(stock_move, self)._get_master_data(cr, uid, move, inv_type, context=context)
-
- def _get_invoice_line_vals(self, cr, uid, move, partner, inv_type, context=None):
- res = super(stock_move, self)._get_invoice_line_vals(cr, uid, move, partner, inv_type, context=context)
- purchase_line = False
- if inv_type == 'in_invoice' and move.purchase_line_id:
- purchase_line = move.purchase_line_id
- elif inv_type == 'in_refund' and move.origin_returned_move_id.purchase_line_id:
- purchase_line = move.origin_returned_move_id.purchase_line_id
- if purchase_line:
- res['invoice_line_tax_ids'] = [(6, 0, [x.id for x in purchase_line.taxes_id])]
- res['price_unit'] = purchase_line.price_unit
- res['account_analytic_id'] = purchase_line.account_analytic_id.id
- res['purchase_line_ids'] = [(4, purchase_line.id)]
- return res
-
- def _get_moves_taxes(self, cr, uid, moves, inv_type, context=None):
- is_extra_move, extra_move_tax = super(stock_move, self)._get_moves_taxes(cr, uid, moves, inv_type, context=context)
- if inv_type == 'in_invoice':
- for move in moves:
- if move.purchase_line_id:
- is_extra_move[move.id] = False
- extra_move_tax[move.picking_id, move.product_id] = [(6, 0, [x.id for x in move.purchase_line_id.taxes_id])]
- elif move.product_id.product_tmpl_id.supplier_taxes_id:
- mov_id = self.search(cr, uid, [('purchase_line_id', '!=', False), ('picking_id', '=', move.picking_id.id)], limit=1, context=context)
- if mov_id:
- mov = self.browse(cr, uid, mov_id[0], context=context)
- fp = mov.purchase_line_id.order_id.fiscal_position
- res = self.pool.get("account.invoice.line").product_id_change(cr, uid, [], move.product_id.id, None, partner_id=move.picking_id.partner_id.id, fposition_id=(fp and fp.id), type='in_invoice', context=context)
- extra_move_tax[0, move.product_id] = [(6, 0, res['value']['invoice_line_tax_ids'])]
- return (is_extra_move, extra_move_tax)
-
- def _get_taxes(self, cr, uid, move, context=None):
- if move.origin_returned_move_id.purchase_line_id.taxes_id:
- return [tax.id for tax in move.origin_returned_move_id.purchase_line_id.taxes_id]
- return super(stock_move, self)._get_taxes(cr, uid, move, context=context)
-
-class stock_picking(osv.osv):
- _inherit = 'stock.picking'
-
- def _get_to_invoice(self, cr, uid, ids, name, args, context=None):
- res = {}
- for picking in self.browse(cr, uid, ids, context=context):
- res[picking.id] = False
- for move in picking.move_lines:
- if move.purchase_line_id and move.purchase_line_id.order_id.invoice_method == 'picking':
- if not move.move_orig_ids:
- res[picking.id] = True
- return res
-
- def _get_picking_to_recompute(self, cr, uid, ids, context=None):
- picking_ids = set()
- for move in self.pool.get('stock.move').browse(cr, uid, ids, context=context):
- if move.picking_id and move.purchase_line_id:
- picking_ids.add(move.picking_id.id)
- return list(picking_ids)
-
- _columns = {
- 'reception_to_invoice': fields.function(_get_to_invoice, type='boolean', string='Invoiceable on incoming shipment',
- help='Does the picking contain some moves related to a purchase order invoiceable on the receipt?',
- store={
- 'stock.move': (_get_picking_to_recompute, ['purchase_line_id', 'picking_id'], 10),
- }),
- }
-
- def _get_invoice_vals(self, cr, uid, key, inv_type, journal_id, moves, context=None):
- inv_vals = super(stock_picking, self)._get_invoice_vals(cr, uid, key, inv_type, journal_id, moves, context=context)
- purchases = []
- for move in moves:
- if move.purchase_line_id or move.origin_returned_move_id.purchase_line_id:
- purchase = move.purchase_line_id.order_id or move.origin_returned_move_id.purchase_line_id.order_id
- inv_vals.update({
- 'fiscal_position_id': purchase.fiscal_position_id.id,
- 'payment_term_id': purchase.payment_term_id.id,
- })
- purchases.append(purchase.id)
- if purchases:
- inv_vals['purchase_ids'] = [(6, 0, list(set(purchases)))]
- return inv_vals
-
- def get_service_line_vals(self, cr, uid, moves, partner, inv_type, context=None):
- purchase_obj = self.pool['purchase.order']
- purchase_line_obj = self.pool['purchase.order.line']
- res = super(stock_picking, self).get_service_line_vals(cr, uid, moves, partner, inv_type, context=context)
- # Add service lines for purchases
- if inv_type == 'in_invoice':
- purchase_ids = list(set([x.purchase_line_id.order_id.id for x in moves if x.purchase_line_id]))
- purchase_line_ids = purchase_line_obj.search(cr, uid, [('order_id', 'in', purchase_ids), ('invoice_lines', '=', False), '|', ('product_id', '=', False),
- ('product_id.type', '=', 'service')], context=context)
- for po_line in purchase_line_obj.browse(cr, uid, purchase_line_ids, context=context):
- acc_id = purchase_obj._choose_account_from_po_line(cr, uid, po_line, context=context)
- inv_line_data = purchase_obj._prepare_inv_line(cr, uid, acc_id, po_line, context=context)
- inv_line_data['purchase_line_ids'] = [(4, po_line.id)]
- res += [(0, 0, inv_line_data)]
- return res
-
class stock_warehouse(osv.osv):
_inherit = 'stock.warehouse'
diff --git a/addons/purchase/stock_view.xml b/addons/purchase/stock_view.xml
index 1021550734f..6f4f09497ae 100644
--- a/addons/purchase/stock_view.xml
+++ b/addons/purchase/stock_view.xml
@@ -23,32 +23,6 @@
</field>
</record>
- <!-- is it need ? we have another method "Receive Products"-->
- <record id="purchase_order_2_stock_picking" model="ir.ui.view">
- <field name="name">Purchase Picking Inherited</field>
- <field name="model">purchase.order</field>
- <field name="inherit_id" ref="purchase.purchase_order_form"/>
- <field name="arch" type="xml">
- <xpath expr="//div[contains(@class, 'oe_title')]" position="before">
- <div class="oe_button_box" name="button_box">
- <button type="object"
- name="view_picking"
- class="oe_stat_button"
- icon="fa-truck"
- attrs="{'invisible': [('shipment_count', '=', 0)]}">
- <field name="shipment_count" widget="statinfo" string="Shipment" help="Incoming Shipments"/>
- </button>
- <button type="object" name="invoice_open"
- class="oe_stat_button"
- icon="fa-pencil-square-o"
- attrs="{'invisible': [('invoice_count', '=', 0)]}">
- <field name="invoice_count" widget="statinfo" string="Invoices"/>
- </button>
- </div>
- </xpath>
- </field>
- </record>
-
<record id="view_warehouse_inherited" model="ir.ui.view">
<field name="name">Stock Warehouse Inherited</field>
<field name="model">stock.warehouse</field>
@@ -60,28 +34,5 @@
</field>
</record>
- <record id="action_picking_tree_picking_to_invoice" model="ir.actions.act_window">
- <field name="name">Incoming Shipments</field>
- <field name="res_model">stock.picking</field>
- <field name="type">ir.actions.act_window</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form,calendar</field>
- <field name="context">{"default_type": "in", "contact_display": "partner_address", "search_default_done": 1, "search_default_reception_to_invoice": 1}</field>
- <field name="search_view_id" ref="stock.view_picking_internal_search"/>
- <field name="help" type="html">
- <p class="oe_view_nocontent_create">
- Click to create a new incoming shipment.
- </p><p>
- Here you can track all the product receipts of purchase
- orders where the invoicing is "Based on Incoming Shipments",
- and for which you have not received a supplier bill yet.
- You can generate a supplier bill based on those receipts.
- </p>
- </field>
- </record>
-
- <menuitem action="action_picking_tree_picking_to_invoice"
- id="menu_action_picking_tree_picking_to_invoice"
- parent="purchase.menu_procurement_management_invoice" sequence="90"/>
</data>
</openerp>
diff --git a/addons/purchase/test/anglo_saxon.yml b/addons/purchase/test/anglo_saxon.yml
deleted file mode 100644
index 72e16a0d47f..00000000000
--- a/addons/purchase/test/anglo_saxon.yml
+++ /dev/null
@@ -1,247 +0,0 @@
--
- I first set the company to use anglo saxon accounting.
--
- !python {model: res.company}: |
- self.pool.get('res.company').write(cr, uid, ref("base.main_company"), {'anglo_saxon_accounting': True,})
--
- In order to test anglo_saxon Configure Different Accounts.
--
- !record {model: account.account, id: account_anglo_stock_valuation}:
- code: X3000
- name: Stock Valuation Account- (test)
- user_type_id: account.data_account_type_current_assets
--
- Configure Stock Interim account (Received).
--
- !record {model: account.account, id: account_anglo_stock_input}:
- code: X2800
- name: Stock Interim account (Received)
- user_type_id: account.data_account_type_expenses
--
- Configure Stock Interim account (Delivered).
--
- !record {model: account.account, id: account_anglo_stock_output}:
- code: X2801
- name: Stock Interim account (Delivered)
- user_type_id: account.data_account_type_revenue
--
- Configure Price difference creditor Account.
--
- !record {model: account.account, id: account_anglo_price_difference}:
- code: X7095
- name: Price difference creditor Account
- user_type_id: account.data_account_type_expenses
--
- Configure Cash Bank Account.
--
- !record {model: account.account, id: account_anglo_cash}:
- code: X5000
- name: Cash/Bank Account
- user_type_id: account.data_account_type_liquidity
- reconcile: True
--
- Configure Creditor Account Payable.
--
- !record {model: account.account, id: account_anglo_payable}:
- code: X440001
- name: Creditor Account Payable
- user_type_id: account.data_account_type_payable
- reconcile: True
--
- Configure Debtor Account Receivable.
--
- !record {model: account.account, id: account_anglo_receivable}:
- code: X400001
- name: Debtor Account Receivable
- user_type_id: account.data_account_type_receivable
- reconcile: True
--
- Configure Cost of Good sale Account.
--
- !record {model: account.account, id: account_anglo_cogs}:
- code: X7000
- name: Cost of goods sale account
- user_type_id: account.data_account_type_expenses
--
- Configure Income Account.
--
- !record {model: account.account, id: account_anglo_income}:
- code: X8000
- name: Income Account
- user_type_id: account.data_account_type_revenue
--
- I configure the account receivable of supplier
--
- !record {model: res.partner, id: base.res_partner_3}:
- property_account_payable_id: account_anglo_payable
- property_account_receivable_id: account_anglo_receivable
--
- I configure the account receivable of Customer.
--
- !record {model: res.partner, id: base.res_partner_2}:
- property_account_payable_id: account_anglo_payable
- property_account_receivable_id: account_anglo_receivable
--
- I configure the product category with stock valuation account.
--
- !record {model: product.category, id: product.product_category_5}:
- property_stock_valuation_account_id: account_anglo_stock_valuation
--
- I configure the product with required accounts, and cost method = standard
--
- !python {model: product.product}: |
- self.write(cr, uid, [ref('product.product_product_10')], {'list_price': 20.00,'standard_price': 9,'categ_id': ref('product.product_category_5'),'valuation': 'real_time',
- 'property_account_income_id': ref('account_anglo_income'),'property_account_expense_id': ref('account_anglo_cogs'),
- 'property_account_creditor_price_difference': ref('account_anglo_price_difference'),'property_stock_account_input': ref('account_anglo_stock_input'),
- 'property_stock_account_output': ref('account_anglo_stock_output'), 'cost_method': 'standard'})
--
- I create a draft Purchase Order.
--
- !record {model: purchase.order, id: purchase_order_001}:
- partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
- order_line:
- - product_id: product.product_product_10
- product_qty: 1
- price_unit: 10
- date_planned: !eval "'%s' % (time.strftime('%Y-%m-%d'))"
--
- I confirm the purchase order.
--
- !workflow {model: purchase.order, ref: purchase_order_001, action: purchase_confirm}
--
- Receipt is ready for process so now we do it.
--
- !python {model: stock.picking}: |
- picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001"), context=context).picking_ids[0]
- picking_id.do_transfer()
--
- I check the Stock Interim account (Received) is credited successfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_stock_input')).credit
- # float_compare(credit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not credited successfully."
--
- I check the Stock valuation account is debited sucessfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_stock_valuation')).debit
- # float_compare(debit, 9, precision_digits=2) == 0, "Stock valuation account is not debited successfully."
--
- I Validate Invoice of Purchase Order.
--
- !python {model: purchase.order}: |
- invoice_ids = [x.id for x in self.browse(cr, uid, ref("purchase_order_001")).invoice_ids]
- self.pool.get('account.invoice').signal_workflow(cr, uid, invoice_ids, 'invoice_open')
--
- I check the Stock Interim account (Received) is debited sucessfully when Invoice validated.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_stock_input')).debit
- # float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not debited successfully."
--
- I check the Price difference creditor Account is debited sucessfully when Invoice validated.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_price_difference')).debit
- # float_compare(debit, 1, precision_digits=2) == 0, "Price difference creditor Account is not debited successfully."
--
- I check Payable(creditor) Account is Credited sucessfully when Invoice validated.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_payable')).credit
- # float_compare(credit, 10, precision_digits=2) == 0, "Payable(creditor) Account is not Credited successfully."
--
- I open the Invoice.
--
- !python {model: purchase.order}: |
- po = self.browse(cr, uid, ref("purchase_order_001"))
- for invoice in po.invoice_ids:
- invoice.signal_workflow('invoice_open')
--
- I pay the invoice.
--
- !python {model: purchase.order}: |
- invoice_ids = self.browse(cr, uid, ref("purchase_order_001")).invoice_ids
- order = self.browse(cr, uid, ref("purchase_order_001"))
- journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', order.company_id.id)], limit=1)
- import time
- for invoice in invoice_ids:
- self.pool.get('account.invoice').pay_and_reconcile(cr, uid, [invoice.id], journal_ids[0])
--
- I check Payable(Creditors) Account is Debited sucessfully after invoice paid.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_payable')).debit
- # assert float_compare(debit, 10, precision_digits=2) == 0, "Payable(Creditors) Account is not Debited successfully."
--
- I check Bank/Cash account is credited sucessfully after invoice paid.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_cash')).credit
- # float_compare(credit, 10, precision_digits=2) == 0, "Bank/Cash account is not credited successfully."
--
- I create an Outgoing Picking order
--
- !record {model: stock.picking, id: stock_picking_out001}:
- partner_id: base.res_partner_2
- move_lines:
- - company_id: base.main_company
- location_id: stock.stock_location_stock
- product_id: product.product_product_10
- product_uom_qty: 1.0
- product_uom: product.product_uom_unit
- location_dest_id: stock.stock_location_customers
- move_type: direct
- picking_type_id: stock.picking_type_out
--
- I need to check the availability of the product, So I make my picking order for processing later.
--
- !python {model: stock.picking}: |
- self.action_confirm(cr, uid, [ref('stock_picking_out001')], context=context)
-
--
- I check the product availability, Product is available in the stock and ready to be sent.
--
- !python {model: stock.picking}: |
- picking = self.browse(cr, uid, ref("stock_picking_out001"))
- assert picking.state == "confirmed", "Picking should be confirmed."
- for move_line in picking.move_lines:
- assert move_line.state == "confirmed", "Move should be confirmed."
-
--
- I process the delivery.
--
- !python {model: stock.picking}: |
- picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001"), context=context)
- picking.do_transfer()
--
- I check Stock Interim account (Delivery) is debited successfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_stock_output')).debit
- # float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Delivery) is not debited successfully."
--
- I check the Stock valuation account is credited sucessfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_stock_valuation')).credit
- # float_compare(credit, 9, precision_digits=2) == 0, "Stock valuation account is not credited successfully."
diff --git a/addons/purchase/test/anglo_saxon_avg_fifo.yml b/addons/purchase/test/anglo_saxon_avg_fifo.yml
deleted file mode 100644
index 019605e0735..00000000000
--- a/addons/purchase/test/anglo_saxon_avg_fifo.yml
+++ /dev/null
@@ -1,247 +0,0 @@
--
- I first set the company to use anglo saxon accounting.
--
- !python {model: res.company}: |
- self.pool.get('res.company').write(cr, uid, ref("base.main_company"), {'anglo_saxon_accounting': True,})
--
- In order to test anglo_saxon Configure Different Accounts.
--
- !record {model: account.account, id: account_anglo_stock_valuation_fifo}:
- code: X3000f
- name: Stock Valuation Account- (test)
- user_type_id: account.data_account_type_current_assets
--
- Configure Stock Interim account (Received).
--
- !record {model: account.account, id: account_anglo_stock_input_fifo}:
- code: X2800f
- name: Stock Interim account (Received)
- user_type_id: account.data_account_type_expenses
--
- Configure Stock Interim account (Delivered).
--
- !record {model: account.account, id: account_anglo_stock_output_fifo}:
- code: X2801f
- name: Stock Interim account (Delivered)
- user_type_id: account.data_account_type_revenue
--
- Configure Price difference creditor Account.
--
- !record {model: account.account, id: account_anglo_price_difference_fifo}:
- code: X7095f
- name: Price difference creditor Account
- user_type_id: account.data_account_type_expenses
--
- Configure Cash Bank Account.
--
- !record {model: account.account, id: account_anglo_cash_fifo}:
- code: X5000f
- name: Cash/Bank Account
- user_type_id: account.data_account_type_liquidity
- reconcile: True
--
- Configure Creditor Account Payable.
--
- !record {model: account.account, id: account_anglo_payable_fifo}:
- code: X440001f
- name: Creditor Account Payable
- user_type_id: account.data_account_type_payable
- reconcile: True
--
- Configure Debtor Account Receivable.
--
- !record {model: account.account, id: account_anglo_receivable_fifo}:
- code: X400001f
- name: Debtor Account Receivable
- user_type_id: account.data_account_type_receivable
- reconcile: True
--
- Configure Cost of Good sale Account.
--
- !record {model: account.account, id: account_anglo_cogs_fifo}:
- code: X7000f
- name: Cost of goods sale account
- user_type_id: account.data_account_type_expenses
--
- Configure Income Account.
--
- !record {model: account.account, id: account_anglo_income_fifo}:
- code: X8000f
- name: Income Account
- user_type_id: account.data_account_type_revenue
--
- I configure the account receivable of supplier
--
- !record {model: res.partner, id: base.res_partner_3}:
- property_account_payable_id: account_anglo_payable_fifo
- property_account_receivable_id: account_anglo_receivable_fifo
--
- I configure the account receivable of Customer.
--
- !record {model: res.partner, id: base.res_partner_2}:
- property_account_payable_id: account_anglo_payable_fifo
- property_account_receivable_id: account_anglo_receivable_fifo
--
- I configure the product category with stock valuation account.
--
- !record {model: product.category, id: product.product_category_5}:
- property_stock_valuation_account_id: account_anglo_stock_valuation_fifo
--
- I create a product with required accounts, and cost method average (but same applies for fifo)
--
- !record {model: product.product, id: product_fifo_anglo_saxon}:
- name: 'FIFO product for anglo saxon tests'
- lst_price: 20.00
- standard_price: 0
- categ_id: product.product_category_5
- valuation: 'real_time'
- property_account_income_id: account_anglo_income_fifo
- property_account_expense_id: account_anglo_cogs_fifo
- property_account_creditor_price_difference: account_anglo_price_difference_fifo
- property_stock_account_input: account_anglo_stock_input_fifo
- property_stock_account_output: account_anglo_stock_output_fifo
- cost_method: 'average'
--
- I create a draft Purchase Order.
--
- !record {model: purchase.order, id: purchase_order_001_fifo}:
- partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
- order_line:
- - product_id: product_fifo_anglo_saxon
- product_qty: 1
- price_unit: 9
- date_planned: !eval "'%s' % (time.strftime('%Y-%m-%d'))"
- taxes_id: []
--
- I confirm the purchase order.
--
- !workflow {model: purchase.order, ref: purchase_order_001_fifo, action: purchase_confirm}
--
- Receipt is ready for process so now we do it.
--
- !python {model: stock.picking}: |
- picking_id = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_001_fifo"), context=context).picking_ids[0]
- picking_id.do_transfer()
--
- I check the Stock Interim account (Received) is credit successfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_stock_input_fifo')).credit
- # assert float_compare(credit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not credited successfully."
--
- I check the Stock valuation account is debit sucessfully.
--
- !python {model: account.account, id}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_stock_valuation_fifo')).debit
- # assert float_compare(debit, 9, precision_digits=2) == 0, "Stock valuation account is not debited successfully."
--
- I Validate Invoice of Purchase Order after having changed the price to 10.
--
- !python {model: purchase.order}: |
- invoice_ids = [x.id for x in self.browse(cr, uid, ref("purchase_order_001_fifo")).invoice_ids]
- line_ids = self.pool.get('account.invoice.line').search(cr, uid, [('invoice_id', 'in', invoice_ids)])
- self.pool.get('account.invoice.line').write(cr, uid, line_ids, {'price_unit': 10})
- self.pool.get('account.invoice').signal_workflow(cr, uid, invoice_ids, 'invoice_open')
--
- I check the Stock Interim account (Received) is debited sucessfully when Invoice validated.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_stock_input_fifo')).debit
- # assert float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Received) is not debited successfully."
--
- I check the Price difference creditor Account is debited sucessfully when Invoice validated.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_price_difference_fifo')).debit
- # assert float_compare(debit, 1, precision_digits=2) == 0, "Price difference creditor Account is not debited successfully."
--
- I check Payable(creditor) Account is Credited sucessfully when Invoice validated.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_payable_fifo')).credit
- # assert float_compare(credit, 10, precision_digits=2) == 0, "Payable(creditor) Account is not Credited successfully."
--
- I pay the invoice.
--
- !python {model: purchase.order}: |
- invoice_ids = self.browse(cr, uid, ref("purchase_order_001_fifo")).invoice_ids
- order = self.browse(cr, uid, ref("purchase_order_001_fifo"))
- journal_ids = self.pool.get('account.journal').search(cr, uid, [('type', '=', 'cash'), ('company_id', '=', order.company_id.id)], limit=1)
- import time
- for invoice in invoice_ids:
- self.pool.get('account.invoice').pay_and_reconcile(cr, uid, [invoice.id], journal_ids[0])
--
- I check Payable(Creditors) Account is Debited sucessfully after invoice paid.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_payable_fifo')).debit
- # assert float_compare(debit, 10, precision_digits=2) == 0, "Payable(Creditors) Account is not Debited successfully."
--
- I check Bank/Cash account is credited sucessfully after invoice paid.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_cash_fifo')).credit
- # assert float_compare(credit, 10, precision_digits=2) == 0, "Bank/Cash account is not credited successfully."
--
- I create an Outgoing Picking order
--
- !record {model: stock.picking, id: stock_picking_out001_fifo}:
- partner_id: base.res_partner_2
- move_lines:
- - company_id: base.main_company
- location_id: stock.stock_location_stock
- product_id: product_fifo_anglo_saxon
- product_uom_qty: 1.0
- location_dest_id: stock.stock_location_customers
- move_type: direct
- picking_type_id: stock.picking_type_out
--
- I need to check the availability of the product, So I make my picking order for processing later.
--
- !python {model: stock.picking}: |
- self.action_confirm(cr, uid, [ref('stock_picking_out001_fifo')], context=context)
--
- I check the product availability, Product is available in the stock and ready to be sent.
--
- !python {model: stock.picking}: |
- picking = self.browse(cr, uid, ref("stock_picking_out001_fifo"))
- assert picking.state == "confirmed", "Picking should be confirmed."
- for move_line in picking.move_lines:
- assert move_line.state == "confirmed", "Move should be confirmed."
--
- I process the delivery.
--
- !python {model: stock.picking}: |
- picking = self.pool.get('stock.picking').browse(cr, uid, ref("stock_picking_out001_fifo"), context=context)
- picking.do_transfer()
--
- I check Stock Interim account (Delivery) is debited successfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to debit field is removed
- # debit = self.browse(cr, uid, ref('account_anglo_stock_output_fifo')).debit
- # assert float_compare(debit, 9, precision_digits=2) == 0, "Stock Interim account (Delivery) is not debited successfully."
--
- I check the Stock valuation account is credited sucessfully.
--
- !python {model: account.account}: |
- from openerp.tools import float_compare
- # Due to credit field is removed
- # credit = self.browse(cr, uid, ref('account_anglo_stock_valuation_fifo')).credit
- # assert float_compare(credit, 9, precision_digits=2) == 0, "Stock valuation account is not credited successfully."
diff --git a/addons/purchase/test/average_price.yml b/addons/purchase/test/average_price.yml
index b816e34bcbc..3d34e17aeff 100644
--- a/addons/purchase/test/average_price.yml
+++ b/addons/purchase/test/average_price.yml
@@ -18,7 +18,6 @@
-
!record {model: purchase.order, id: purchase_order_average1}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_average_icecream
product_qty: 10.0
@@ -28,12 +27,13 @@
-
I confirm the first purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_average1}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_average1")])
-
I check the "Approved" status of purchase order 1
-
!assert {model: purchase.order, id: purchase_order_average1}:
- - state == 'approved'
+ - state == 'purchase'
-
Process the reception of purchase order 1
-
@@ -52,7 +52,6 @@
-
!record {model: purchase.order, id: purchase_order_average2}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_average_icecream
product_qty: 30.0
@@ -62,7 +61,8 @@
-
I confirm the second purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_average2}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_average2")])
-
Process the reception of purchase order 2
-
@@ -106,7 +106,6 @@
-
!record {model: purchase.order, id: purchase_order_average3}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_average_icecream
product_qty: 500.0
@@ -116,7 +115,8 @@
-
I confirm the first purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_average3}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_average3")])
-
Process the reception of purchase order 3 in grams
-
diff --git a/addons/purchase/test/costmethodchange.yml b/addons/purchase/test/costmethodchange.yml
index dccf5ee10c5..cad69ecf3b5 100644
--- a/addons/purchase/test/costmethodchange.yml
+++ b/addons/purchase/test/costmethodchange.yml
@@ -19,7 +19,6 @@
-
!record {model: purchase.order, id: purchase_order_variable1}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_variable_icecream
product_qty: 10.0
@@ -31,7 +30,6 @@
-
!record {model: purchase.order, id: purchase_order_variable2}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_variable_icecream
product_qty: 30.0
@@ -41,16 +39,18 @@
-
I confirm the first purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_variable1}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_variable1")])
-
I confirm the second purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_variable2}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_variable2")])
-
I check the "Approved" status of purchase order 1
-
!assert {model: purchase.order, id: purchase_order_variable1}:
- - state == 'approved'
+ - state == 'purchase'
-
Process the reception of purchase order 1
-
@@ -113,7 +113,6 @@
-
!record {model: purchase.order, id: purchase_order_variable3}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_variable_icecream
product_qty: 20.0
@@ -123,7 +122,8 @@
-
I confirm this third purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_variable3}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_variable3")])
-
Process the reception of purchase order 3
-
diff --git a/addons/purchase/test/fifo_price.yml b/addons/purchase/test/fifo_price.yml
index 05d87991489..2458a805862 100644
--- a/addons/purchase/test/fifo_price.yml
+++ b/addons/purchase/test/fifo_price.yml
@@ -26,7 +26,6 @@
!record {model: purchase.order, id: purchase_order_fifo1}:
partner_id: base.res_partner_3
currency_id: base.EUR
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fifo_icecream
product_qty: 10.0
@@ -36,12 +35,13 @@
-
I confirm the first purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo1}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fifo1")])
-
- I check the "Approved" status of purchase order 1
+ I check the "Purchase" status of purchase order 1
-
!assert {model: purchase.order, id: purchase_order_fifo1}:
- - state == 'approved'
+ - state == 'purchase'
-
Process the reception of purchase order 1 and set date
-
@@ -58,7 +58,6 @@
-
!record {model: purchase.order, id: purchase_order_fifo2}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fifo_icecream
product_qty: 30.0
@@ -68,7 +67,8 @@
-
I confirm the second purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo2}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fifo2")])
-
Process the reception of purchase order 2
-
@@ -160,7 +160,6 @@
!record {model: purchase.order, id: purchase_order_fifo_usd}:
partner_id: base.res_partner_3
currency_id: new_USD
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fifo_icecream
product_qty: 30000
@@ -174,7 +173,8 @@
-
I confirm the purchase order in USD
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo_usd}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fifo_usd")])
-
Process the reception of purchase order with usd
-
@@ -202,7 +202,7 @@
-
!python {model: stock.picking}: |
self.action_assign(cr, uid, [ref("outgoing_fifo_shipment_cur")])
--
+-
Process the delivery of the outgoing shipment
-
!python {model: stock.picking}: |
@@ -218,7 +218,7 @@
-
!record {model: stock.picking, id: outgoing_fifo_shipment_ret}:
picking_type_id: stock.picking_type_out
--
+-
Picking needs movement from stock
-
!record {model: stock.move, id: outgoing_shipment_fifo_icecream_ret}:
@@ -320,7 +320,6 @@
-
!record {model: purchase.order, id: purchase_order_fifo_neg}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fifo_negative
product_qty: 50.0
@@ -330,7 +329,8 @@
-
I confirm the first purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo_neg}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fifo_neg")])
-
Process the reception of purchase order 1
-
@@ -348,7 +348,6 @@
-
!record {model: purchase.order, id: purchase_order_fifo_neg2}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fifo_negative
product_qty: 60.0
@@ -358,7 +357,8 @@
-
I confirm the second negative purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fifo_neg2}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fifo_neg2")])
-
Process the reception of purchase order 2
-
diff --git a/addons/purchase/test/fifo_returns.yml b/addons/purchase/test/fifo_returns.yml
index 77ba3984715..11289188f53 100644
--- a/addons/purchase/test/fifo_returns.yml
+++ b/addons/purchase/test/fifo_returns.yml
@@ -19,7 +19,6 @@
-
!record {model: purchase.order, id: purchase_order_fiforet1}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fiforet_icecream
product_qty: 10.0
@@ -31,7 +30,6 @@
-
!record {model: purchase.order, id: purchase_order_fiforet2}:
partner_id: base.res_partner_3
- location_id: stock.stock_location_stock
order_line:
- product_id: product_fiforet_icecream
product_qty: 30.0
@@ -41,7 +39,8 @@
-
I confirm the first purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fiforet1}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fiforet1")])
-
Process the reception of purchase order 1
-
@@ -56,7 +55,8 @@
-
I confirm the second purchase order
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_fiforet2}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_fiforet2")])
-
Process the reception of purchase order 2
-
diff --git a/addons/purchase/test/process/cancel_order.yml b/addons/purchase/test/process/cancel_order.yml
index d3695cba6c8..6051f50c7fa 100644
--- a/addons/purchase/test/process/cancel_order.yml
+++ b/addons/purchase/test/process/cancel_order.yml
@@ -8,12 +8,13 @@
-
I confirm the purchase order.
-
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_5}
+ !python {model: purchase.order}: |
+ self.button_confirm(cr, uid, [ref("purchase_order_5")])
-
I check the "Approved" status after confirmed RFQ.
-
!assert {model: purchase.order, id: purchase_order_5}:
- - state == 'approved'
+ - state == 'purchase'
-
First I cancel receptions related to this order if order shipped.
-
@@ -21,37 +22,10 @@
order = self.browse(cr, uid, ref("purchase_order_5"))
self.pool.get('stock.picking').action_cancel(cr, uid, [picking.id for picking in order.picking_ids])
-
- I check order status in "Shipping Exception".
--
- !python {model: purchase.order}: |
- order = self.browse(cr, uid, ref("purchase_order_5"))
- assert order.state == "except_picking", "order should be in Ship Exception state after cancel shipment"
--
Now I am able to cancel purchase order.
-
!python {model: purchase.order}: |
- self.action_cancel(cr, uid, [ref("purchase_order_5")])
--
- I check that order is cancelled.
--
- !assert {model: purchase.order, id: purchase_order_5}:
- - state == 'cancel'
--
- After cancel the order, I check that it's related invoice cancelled.
--
- !python {model: purchase.order}: |
- order = self.browse(cr, uid, ref("purchase_order_5"))
- assert order.invoice_ids[0].state == "cancel", "order's related invoice should be cancelled"
--
- Now again set cancelled order to draft.
--
- !python {model: purchase.order}: |
- self.action_cancel_draft(cr, uid, [ref("purchase_order_5")])
--
- Now I again to cancel draft order.
--
- !python {model: purchase.order}: |
- self.action_cancel(cr, uid, [ref("purchase_order_5")])
+ self.button_cancel(cr, uid, [ref("purchase_order_5")])
-
I check that order is cancelled.
-
diff --git a/addons/purchase/test/process/generate_invoice_from_reception.yml b/addons/purchase/test/process/generate_invoice_from_reception.yml
deleted file mode 100644
index 6e3a7d3d8e0..00000000000
--- a/addons/purchase/test/process/generate_invoice_from_reception.yml
+++ /dev/null
@@ -1,29 +0,0 @@
--
- Purchase user can create an invoice for order on receptions therfore test with that user which have Purchase user rights.
--
- !context
- uid: 'res_users_purchase_user'
--
- I confirm another order where invoice control is 'Bases on incoming shipments'.
--
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_2}
--
- I check that the invoice of order.
--
- !python {model: purchase.order}: |
- purchase_order = self.browse(cr, uid, ref("purchase_order_2"))
- assert len(purchase_order.invoice_ids) == 0, "Invoice should not be generated on order confirmation."
--
- Now I create an invoice for order on reception.
--
- !python {model: purchase.order}: |
- pick_ids = [self.browse(cr, uid, ref("purchase_order_2")).id]
- self.action_invoice_create(cr, uid, pick_ids)
--
- I check that the invoice of order.
--
- !python {model: purchase.order}: |
- purchase_order = self.browse(cr, uid, ref("purchase_order_2"))
- assert len(purchase_order.invoice_ids) == 1, "Invoice should be generated."
-
-
diff --git a/addons/purchase/test/process/invoice_on_poline.yml b/addons/purchase/test/process/invoice_on_poline.yml
deleted file mode 100644
index d31b371f009..00000000000
--- a/addons/purchase/test/process/invoice_on_poline.yml
+++ /dev/null
@@ -1,25 +0,0 @@
--
- Purchase User confirm the order and create invoice based on purchase order line.
--
- !context
- uid: 'res_users_purchase_user'
--
- I confirm purchase order which has invoicing control method "Based on Purchase Order Lines".
--
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_6}
--
- In order to test create invoice for purchase order line.
--
- !python {model: purchase.order}: |
- invoice_line_obj = self.pool.get('purchase.order.line_invoice')
- purchase_order = self.browse(cr, uid, ref("purchase_order_6"))
- context.update({'active_model': 'purchase.order', 'active_ids': [ref("purchase_order_6")]})
- for purchase_line in purchase_order.order_line:
- invoice_line_obj.makeInvoices(cr, uid, purchase_line.id, context=context)
--
- I check the invoice of order.
--
- !python {model: purchase.order}: |
- purchase_order = self.browse(cr, uid, ref("purchase_order_6"))
- for purchase_line in purchase_order.order_line:
- assert len(purchase_order.invoice_ids) == 1, "Invoice should be generated." \ No newline at end of file
diff --git a/addons/purchase/test/process/merge_order.yml b/addons/purchase/test/process/merge_order.yml
deleted file mode 100644
index 591556a6981..00000000000
--- a/addons/purchase/test/process/merge_order.yml
+++ /dev/null
@@ -1,44 +0,0 @@
--
- Give access rights of Purchase user to merge two RFQ.
--
- !context
- uid: 'res_users_purchase_user'
--
- In order to merge RFQ, I merge two RFQ which has same supplier and check new merged order.
--
- !python {model: purchase.order}: |
- context.update({"mail_create_nolog" : True })
- order3 = self.browse(cr, uid, ref('purchase_order_4'))
- order7 = self.browse(cr, uid, ref('purchase_order_7'))
- total_qty = sum([x.product_qty for x in order3.order_line] + [x.product_qty for x in order7.order_line])
- new_id = self.do_merge(cr, uid, [ref('purchase_order_4'), ref('purchase_order_7')], context=context)
-
- assert order3.state == 'cancel', "Merged order should be canceled"
- assert order7.state == 'cancel', "Merged order should be canceled"
-
- def merged_data(lines):
- product_id =[]
- product_uom = []
- res = {}
- for line in lines:
- product_id.append(line.product_id.id)
- product_uom.append(line.product_uom.id)
- res.update({'product_ids': product_id,'product_uom':product_uom})
- return res
-
- for order in self.browse(cr, uid, new_id.keys()):
- assert order.state == 'draft',"New created order state should be in draft"
- assert order.partner_id == order3.partner_id ,"partner is not correspond"
- assert order.location_id == order3.location_id ,"Location is not correspond"
-
- total_new_qty = sum(x.product_qty for x in order.order_line)
- assert total_new_qty == total_qty,"product quantities are not correspond"
-
- date_order = min(order3.date_order, order7.date_order)
- assert order.date_order == date_order, "Date of order is not correspond"
-
- n_product_data = merged_data(order.order_line)
- o_product_data= merged_data(order3.order_line)
- o_pro_data = merged_data(order7.order_line)
- assert n_product_data == o_product_data or o_pro_data,"product data are not correspond"
-
diff --git a/addons/purchase/test/process/rfq2order2done.yml b/addons/purchase/test/process/rfq2order2done.yml
deleted file mode 100644
index 9098dcc43a1..00000000000
--- a/addons/purchase/test/process/rfq2order2done.yml
+++ /dev/null
@@ -1,70 +0,0 @@
--
- In order to test the purchase order flow I compute the total of the listed products
--
- I check the total untaxed amount of the RFQ is correctly computed
--
- !python {model: purchase.order}: |
- from openerp.tools import float_compare
- po = self.browse(cr, uid, ref('purchase_order_1'))
- assert float_compare(sum([l.price_subtotal for l in po.order_line]), po.amount_untaxed, precision_digits=2) == 0, "The amount of RFQ is not correctly computed"
--
- I confirm the RFQ.
--
- !workflow {model: purchase.order, action: purchase_confirm, ref: purchase_order_1}
--
- I check the "Approved" status after confirmed RFQ.
--
- !assert {model: purchase.order, id: purchase_order_1}:
- - state == 'approved'
--
- I check that the invoice details which is generated after confirmed RFQ.
--
- !python {model: purchase.order}: |
- from openerp.tools import float_compare
- purchase_order = self.browse(cr, uid, ref("purchase_order_1"))
- assert len(purchase_order.invoice_ids) >= 1, "Invoice is not generated more or less than one"
- for invoice in purchase_order.invoice_ids:
- assert invoice.state == "draft", "Invoice state should be draft"
- assert invoice.partner_id.id == purchase_order.partner_id.id, "Vendor is not correspond with purchase order"
- assert invoice.reference == purchase_order.partner_ref or purchase_order.name,"Invoice reference is not correspond with purchase order"
- assert invoice.type == 'in_invoice',"Invoice type is not correspond with purchase order"
- assert invoice.origin == purchase_order.name,"Invoice origin is not correspond with purchase order"
- assert invoice.company_id.id == purchase_order.company_id.id ,"Invoice company is not correspond with purchase order"
- assert float_compare(invoice.amount_untaxed, purchase_order.amount_untaxed, precision_digits=2) == 0, "Invoice untaxed amount is not correspond with purchase order"
- assert float_compare(invoice.amount_tax, purchase_order.amount_tax, precision_digits=2) == 0, "Invoice tax amount is not correspond with purchase order"
- assert float_compare(invoice.amount_total, purchase_order.amount_total, precision_digits=2) == 0, "Invoice total amount is not correspond with purchase order"
- assert len(invoice.invoice_line_ids) == len(purchase_order.order_line), "Lines of Invoice and Purchase Order are not correspond"
--
- I check that Reception details after confirmed RFQ.
--
- !python {model: purchase.order}: |
- purchase_order = self.browse(cr, uid, ref("purchase_order_1"))
- assert len(purchase_order.picking_ids) >= 1, "You should have only one reception order"
- for picking in purchase_order.picking_ids:
- assert picking.state == "assigned", "Reception state should be in assigned state"
- assert picking.partner_id.id == purchase_order.partner_id.id, "Delivery address of reception id is different from order"
- assert picking.company_id.id == purchase_order.company_id.id, "Company is not correspond with purchase order"
--
- Reception is ready for process so now done the reception.
--
- !python {model: stock.picking}: |
- pick_ids = self.pool.get('purchase.order').browse(cr, uid, ref("purchase_order_1"), context=context).picking_ids[0]
- pick_ids.do_transfer()
--
- I check that purchase order is shipped.
--
- !python {model: purchase.order}: |
- assert self.browse(cr, uid, ref("purchase_order_1")).shipped == True,"Purchase order should be delivered"
-
--
- I Validate Invoice of Purchase Order.
--
- !python {model: purchase.order}: |
- for invoice in self.browse(cr, uid, ref('purchase_order_1'), context=context).invoice_ids:
- invoice.signal_workflow('invoice_open')
--
- I check that purchase order is invoiced.
--
- !python {model: purchase.order}: |
- assert self.browse(cr, uid, ref("purchase_order_1")).invoiced == True,"Purchase Order should be invoiced"
-
diff --git a/addons/purchase/tests/__init__.py b/addons/purchase/tests/__init__.py
index 15ef7dc32e9..67dee8c60db 100644
--- a/addons/purchase/tests/__init__.py
+++ b/addons/purchase/tests/__init__.py
@@ -1,5 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from . import test_purchase_to_invoice
-from . import test_onchange_product_id
diff --git a/addons/purchase/tests/test_onchange_product_id.py b/addons/purchase/tests/test_onchange_product_id.py
deleted file mode 100644
index fba548b6212..00000000000
--- a/addons/purchase/tests/test_onchange_product_id.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from openerp.tests.common import TransactionCase
-
-class TestOnchangeProductId(TransactionCase):
- """Test that when an included tax is mapped by a fiscal position, the included tax must be
- subtracted to the price of the product.
- """
-
- def setUp(self):
- super(TestOnchangeProductId, self).setUp()
- self.fiscal_position_model = self.registry('account.fiscal.position')
- self.fiscal_position_tax_model = self.registry('account.fiscal.position.tax')
- self.tax_model = self.registry('account.tax')
- self.res_partner_model = self.registry('res.partner')
- self.product_model = self.registry('product.product')
- self.product_uom_model = self.registry('product.uom')
- self.po_line_model = self.registry('purchase.order.line')
-
- def test_onchange_product_id(self):
- cr, uid = self.cr, self.uid
- uom_id = self.product_uom_model.search(cr, uid, [('name', '=', 'Unit(s)')])[0]
- partner_id = self.res_partner_model.create(cr, uid, dict(name="George"))
- tax_include_id = self.tax_model.create(cr, uid, dict(name="Include tax",
- amount_type='percent',
- amount='21.0',
- type_tax_use='purchase',
- price_include=True))
- tax_exclude_id = self.tax_model.create(cr, uid, dict(name="Exclude tax",
- amount_type='percent',
- type_tax_use='purchase',
- amount='0.00'))
- product_id = self.product_model.create(cr, uid, dict(name="Voiture",
- list_price='121',
- supplier_taxes_id=[(6, 0, [tax_include_id])]))
- fp_id = self.fiscal_position_model.create(cr, uid, dict(name="fiscal position",
- sequence=1))
- self.fiscal_position_tax_model.create(cr, uid, dict(position_id=fp_id,
- tax_src_id=tax_include_id,
- tax_dest_id=tax_exclude_id))
- res = self.po_line_model.onchange_product_id(cr, uid, [], product_id, 1.0, uom_id, partner_id,
- fiscal_position_id=fp_id)
-
- self.assertEquals(0, res['value']['price_unit'], "The default price for products having no seller set is 0")
- #TODO: add back a test on this
- #self.assertEquals(100, res['value']['price_unit'], "The included tax must be subtracted to the price")
diff --git a/addons/purchase/tests/test_purchase_to_invoice.py b/addons/purchase/tests/test_purchase_to_invoice.py
deleted file mode 100644
index 5e524bfa8f7..00000000000
--- a/addons/purchase/tests/test_purchase_to_invoice.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from openerp.tests import common
-
-
-class TestPurchase(common.TransactionCase):
-
- def test_purchase_to_invoice(self):
- """ Testing for invoice create,validate and pay with invoicing and payment user."""
- group_id = self.ref('account.group_account_invoice')
- product_id = self.ref('product.product_category_3')
- company_id = self.ref('base.main_company')
- location_id = self.ref('stock.stock_location_3')
-
- # Useful accounts
- user_type_id = self.ref('account.data_account_type_expenses')
- account_exp_id = self.env['account.account'].create({'code': 'X2020', 'name': 'Purchase - Test Expense Account', 'user_type_id': user_type_id, 'reconcile': True})
- user_type_id = self.ref('account.data_account_type_payable')
- account_pay_id = self.env['account.account'].create({'code': 'X1012', 'name': 'Purchase - Test Payable Account', 'user_type_id': user_type_id, 'reconcile': True})
-
- self.env['product.product'].browse(product_id).product_tmpl_id.write({'property_account_expense_id': account_exp_id})
-
- # Create Purchase Journal
- self.env['account.journal'].create({'name': 'Purchase Journal - Test', 'code': 'PTPJ', 'type': 'purchase'})
-
- # In order to test, I create new user and applied Invoicing & Payments group.
- user = self.env['res.users'].with_context({'no_reset_password': True}).create({
- 'name': 'Test User',
- 'login': 'test@test.com',
- 'company_id': company_id,
- 'groups_id': [(6, 0, [group_id])]})
- assert user, "User will not created."
- # I create partner for purchase order.
- partner = self.env['res.partner'].create({
- 'name': 'Test Customer',
- 'email': 'testcustomer@test.com',
- 'property_account_payable_id': account_pay_id,
- })
-
- # In order to test I create purchase order and confirmed it.
- order = self.env['purchase.order'].create({
- 'partner_id': partner.id,
- 'location_id': location_id, })
- self.env['purchase.order.line'].create({
- 'order_id': order.id,
- 'product_id': product_id,
- 'product_qty': 100.0,
- 'product_uom': 1,
- 'price_unit': 89.0,
- 'name': 'Service',
- 'date_planned': '2014-05-31'})
- assert order, "purchase order will not created."
- context = {"active_model": 'purchase.order', "active_ids": [order.id], "active_id": order.id}
- order.with_context(context).wkf_confirm_order()
- # In order to test I create invoice.
- invoice = order.with_context(context).action_invoice_create()
- assert invoice, "No any invoice is created for this purchase order"
- # In order to test I validate invoice wihth Test User(invoicing and payment).
- res = self.env['account.invoice'].browse(invoice).with_context(context).invoice_validate()
- self.assertTrue(res, 'Invoice will not validated')
diff --git a/addons/purchase/views/report_purchaseorder.xml b/addons/purchase/views/report_purchaseorder.xml
index f7b5b2c8aa3..c6d089ba34a 100644
--- a/addons/purchase/views/report_purchaseorder.xml
+++ b/addons/purchase/views/report_purchaseorder.xml
@@ -43,10 +43,6 @@
<strong>Order Date:</strong>
<p t-field="o.date_order"/>
</div>
- <div t-if="o.validator" class="col-xs-3">
- <strong>Validated By:</strong>
- <p t-field="o.validator"/>
- </div>
</div>
<table class="table table-condensed">
diff --git a/addons/purchase/wizard/__init__.py b/addons/purchase/wizard/__init__.py
deleted file mode 100644
index b12716ddd44..00000000000
--- a/addons/purchase/wizard/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-import purchase_order_group
-import purchase_line_invoice
diff --git a/addons/purchase/wizard/purchase_line_invoice.py b/addons/purchase/wizard/purchase_line_invoice.py
deleted file mode 100644
index 4e1d8c96c26..00000000000
--- a/addons/purchase/wizard/purchase_line_invoice.py
+++ /dev/null
@@ -1,99 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from openerp.osv import osv
-from openerp.tools.translate import _
-
-
-class purchase_line_invoice(osv.osv_memory):
-
- """ To create invoice for purchase order line"""
-
- _name = 'purchase.order.line_invoice'
- _description = 'Purchase Order Line Make Invoice'
-
- def _make_invoice_by_partner(self, cr, uid, partner, orders, lines_ids, context=None):
- """
- create a new invoice for one vendor
- @param cr : Cursor
- @param uid : Id of current user
- @param partner : The object partner
- @param orders : The set of orders to add in the invoice
- @param lines : The list of line's id
- """
- purchase_obj = self.pool.get('purchase.order')
- account_jrnl_obj = self.pool.get('account.journal')
- invoice_obj = self.pool.get('account.invoice')
- name = orders and orders[0].name or ''
- journal_id = account_jrnl_obj\
- .search(cr, uid, [('type', '=', 'purchase')], context=None)
- journal_id = journal_id and journal_id[0] or False
- a = partner.property_account_payable_id.id
- inv = {
- 'name': name,
- 'origin': name,
- 'type': 'in_invoice',
- 'journal_id': journal_id,
- 'reference': partner.ref,
- 'account_id': a,
- 'partner_id': partner.id,
- 'invoice_line_ids': [(6, 0, lines_ids)],
- 'currency_id': orders[0].currency_id.id,
- 'comment': " \n".join([order.notes for order in orders if order.notes]),
- 'payment_term_id': orders[0].payment_term_id.id,
- 'fiscal_position_id': partner.property_account_position_id.id
- }
- inv_id = invoice_obj.create(cr, uid, inv, context=context)
- purchase_obj.write(cr, uid, [order.id for order in orders], {'invoice_ids': [(4, inv_id)]}, context=context)
- return inv_id
-
- def makeInvoices(self, cr, uid, ids, context=None):
-
- """
- To get Purchase Order line and create Invoice
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param context: A standard dictionary
- @return : retrun view of Invoice
- """
-
- if context is None:
- context={}
-
- record_ids = context.get('active_ids',[])
- if record_ids:
- res = False
- invoices = {}
- purchase_obj = self.pool.get('purchase.order')
- purchase_line_obj = self.pool.get('purchase.order.line')
- invoice_line_obj = self.pool.get('account.invoice.line')
-
- for line in purchase_line_obj.browse(cr, uid, record_ids, context=context):
- if (not line.invoiced) and (line.state not in ('draft', 'cancel')):
- if not line.partner_id.id in invoices:
- invoices[line.partner_id.id] = []
- acc_id = purchase_obj._choose_account_from_po_line(cr, uid, line, context=context)
- inv_line_data = purchase_obj._prepare_inv_line(cr, uid, acc_id, line, context=context)
- inv_line_data.update({'origin': line.order_id.name})
- inv_id = invoice_line_obj.create(cr, uid, inv_line_data, context=context)
- purchase_line_obj.write(cr, uid, [line.id], {'invoiced': True, 'invoice_lines': [(4, inv_id)]})
- invoices[line.partner_id.id].append((line,inv_id))
-
- res = []
- for result in invoices.values():
- il = map(lambda x: x[1], result)
- orders = list(set(map(lambda x : x[0].order_id, result)))
-
- res.append(self._make_invoice_by_partner(cr, uid, orders[0].partner_id, orders, il, context=context))
-
- return {
- 'domain': "[('id','in', ["+','.join(map(str,res))+"])]",
- 'name': _('Vendor Bills'),
- 'view_type': 'form',
- 'view_mode': 'tree,form',
- 'res_model': 'account.invoice',
- 'view_id': False,
- 'context': "{'type':'in_invoice', 'journal_type': 'purchase'}",
- 'type': 'ir.actions.act_window'
- }
diff --git a/addons/purchase/wizard/purchase_line_invoice_view.xml b/addons/purchase/wizard/purchase_line_invoice_view.xml
deleted file mode 100644
index 8af82622f4a..00000000000
--- a/addons/purchase/wizard/purchase_line_invoice_view.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
-
- <!-- Make the invoice-->
-
- <record id="view_purchase_line_invoice" model="ir.ui.view">
- <field name="name">Create invoices</field>
- <field name="model">purchase.order.line_invoice</field>
- <field name="arch" type="xml">
- <form string="Select an Open Sales Order">
- <group>
- <label string="Do you want to generate the supplier bills?"/>
- </group>
- <footer>
- <button name="makeInvoices" string="Confirm" type="object" class="btn-primary"/>
- <button string="Cancel" class="btn-default" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- <act_window name="Create invoices"
- res_model="purchase.order.line_invoice"
- src_model="purchase.order.line"
- view_mode="form"
- target="new"
- key2="client_action_multi"
- id="action_view_purchase_line_invoice"/>
- </data>
-</openerp>
-
diff --git a/addons/purchase/wizard/purchase_order_group.py b/addons/purchase/wizard/purchase_order_group.py
deleted file mode 100644
index 7c7f805cc68..00000000000
--- a/addons/purchase/wizard/purchase_order_group.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import time
-
-from openerp.osv import fields, osv
-from openerp.tools.translate import _
-from openerp.exceptions import UserError
-
-class purchase_order_group(osv.osv_memory):
- _name = "purchase.order.group"
- _description = "Purchase Order Merge"
-
- def fields_view_get(self, cr, uid, view_id=None, view_type='form',
- context=None, toolbar=False, submenu=False):
- """
- Changes the view dynamically
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param context: A standard dictionary
- @return: New arch of view.
- """
- if context is None:
- context={}
- res = super(purchase_order_group, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False)
- if context.get('active_model','') == 'purchase.order' and len(context['active_ids']) < 2:
- raise UserError(_('Please select multiple order to merge in the list view.'))
- return res
- def merge_orders(self, cr, uid, ids, context=None):
- """
- To merge similar type of purchase orders.
-
- @param self: The object pointer.
- @param cr: A database cursor
- @param uid: ID of the user currently logged in
- @param ids: the ID or list of IDs
- @param context: A standard dictionary
-
- @return: purchase order view
-
- """
- order_obj = self.pool.get('purchase.order')
- proc_obj = self.pool.get('procurement.order')
- mod_obj =self.pool.get('ir.model.data')
- if context is None:
- context = {}
- result = mod_obj._get_id(cr, uid, 'purchase', 'view_purchase_order_filter')
- id = mod_obj.read(cr, uid, result, ['res_id'])
-
- allorders = order_obj.do_merge(cr, uid, context.get('active_ids',[]), context)
-
- return {
- 'domain': "[('id','in', [" + ','.join(map(str, allorders.keys())) + "])]",
- 'name': _('Purchase Orders'),
- 'view_type': 'form',
- 'view_mode': 'tree,form',
- 'res_model': 'purchase.order',
- 'view_id': False,
- 'type': 'ir.actions.act_window',
- 'search_view_id': id['res_id']
- }
diff --git a/addons/purchase/wizard/purchase_order_group_view.xml b/addons/purchase/wizard/purchase_order_group_view.xml
deleted file mode 100644
index d0ee1bd3ad8..00000000000
--- a/addons/purchase/wizard/purchase_order_group_view.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<openerp>
- <data>
- <record id="view_purchase_order_group" model="ir.ui.view">
- <field name="name">Merger Purchase Orders</field>
- <field name="model">purchase.order.group</field>
- <field name="arch" type="xml">
- <form string="Merge orders">
- <group>
- <separator string="Are you sure you want to merge these orders?"/>
- <label string="
-Please note that: &#10; &#10;
-Orders will only be merged if: &#10;
-* Purchase Orders are in draft &#10;
-* Purchase Orders belong to the same supplier &#10;
-* Purchase Orders are have same stock location, same pricelist, same currency &#10; &#10;
-Lines will only be merged if: &#10;
-* Order lines are exactly the same except for the product,quantity and unit &#10;
-" colspan="4"/>
- </group>
-
- <footer>
- <button name="merge_orders" string="Merge orders" type="object" class="btn-primary"/>
- <button string="Cancel" class="btn-default" special="cancel" />
- </footer>
- </form>
- </field>
- </record>
-
- <act_window name="Merge Purchase orders"
- context="{'search_default_draft': 1, 'search_default_approved': False}"
- res_model="purchase.order.group"
- src_model="purchase.order"
- view_mode="form"
- target="new"
- multi="True"
- key2="client_action_multi"
- id="action_view_purchase_order_group"/>
-
- </data>
-</openerp>
-