[IMP] purchase: make billing consitent with sale invoicing

========
Purpose
========

Currently, purchase missing some billing functions that we have in sale
invoicing:
 - Create credit note when necessary
 - Create vendor bill in batch in list view

========
Spec
========

 - In case of retures, when create a bill for a purchase order, we check
the total amount we want to bill to decide whether it's a vendor bill or
a credit note.
 - In the list view, add a new action to create vendor bill/credit note
for all selected order.

========
Links
========

Task 2170715
PR #44210

Related: odoo/upgrade#919
Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
This commit is contained in:
yhu-odoo
2020-03-27 09:33:21 +00:00
parent 3c08091940
commit d0455ae64b
8 changed files with 455 additions and 84 deletions
+3 -5
View File
@@ -36,11 +36,9 @@ class AccountMove(models.Model):
if not self.purchase_id:
return
# Copy partner.
self.partner_id = self.purchase_id.partner_id
self.fiscal_position_id = self.purchase_id.fiscal_position_id
self.invoice_payment_term_id = self.purchase_id.payment_term_id
self.currency_id = self.purchase_id.currency_id
# Copy data from PO
invoice_vals = self.purchase_id._prepare_invoice()
self.update(invoice_vals)
# Copy purchase lines.
po_lines = self.purchase_id.order_line - self.line_ids.mapped('purchase_line_id')
+150 -53
View File
@@ -3,11 +3,12 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
from itertools import groupby
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.float_utils import float_compare
from odoo.tools.float_utils import float_compare, float_is_zero
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.misc import formatLang, get_lang
@@ -31,7 +32,7 @@ class PurchaseOrder(models.Model):
'amount_total': amount_untaxed + amount_tax,
})
@api.depends('state', 'order_line.qty_invoiced', 'order_line.qty_received', 'order_line.product_qty')
@api.depends('state', 'order_line.qty_to_invoice')
def _get_invoiced(self):
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for order in self:
@@ -39,13 +40,9 @@ class PurchaseOrder(models.Model):
order.invoice_status = 'no'
continue
if any(float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) == -1 for line in order.order_line):
if any(not float_is_zero(line.qty_to_invoice, precision_digits=precision) for line in order.order_line):
order.invoice_status = 'to invoice'
elif all(
(line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received)
and float_compare(line.qty_invoiced, line.product_qty if line.product_id.purchase_method == 'purchase' else line.qty_received, precision_digits=precision) >= 0
for line in order.order_line
):
elif all(float_is_zero(line.qty_to_invoice, precision_digits=precision) for line in order.order_line) and order.invoice_ids:
order.invoice_status = 'invoiced'
else:
order.invoice_status = 'no'
@@ -402,39 +399,127 @@ class PurchaseOrder(models.Model):
except AccessError: # no write access rights -> just ignore
break
def action_view_invoice(self):
'''
This function returns an action that display existing vendor bills of given purchase order ids.
When only one found, show the vendor bill immediately.
'''
def action_create_invoice(self):
"""Create the invoice associated to the PO.
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
# 1) Prepare invoice vals and clean-up the section lines
invoice_vals_list = []
for order in self:
if order.invoice_status != 'to invoice':
continue
pending_section = None
# Invoice values.
invoice_vals = order._prepare_invoice()
# Invoice line values (keep only necessary sections).
for line in order.order_line:
if line.display_type == 'line_section':
pending_section = line
continue
if not float_is_zero(line.qty_to_invoice, precision_digits=precision):
if pending_section:
invoice_vals['invoice_line_ids'].append((0, 0, pending_section._prepare_account_move_line()))
pending_section = None
invoice_vals['invoice_line_ids'].append((0, 0, line._prepare_account_move_line()))
invoice_vals_list.append(invoice_vals)
if not invoice_vals_list:
raise UserError(_('There is no invoiceable line. If a product has a control policy based on received quantity, please make sure that a quantity has been received.'))
# 2) group by (company_id, partner_id, currency_id) for batch creation
new_invoice_vals_list = []
for grouping_keys, invoices in groupby(invoice_vals_list, key=lambda x: (x.get('company_id'), x.get('partner_id'), x.get('currency_id'))):
origins = set()
payment_refs = set()
refs = set()
ref_invoice_vals = None
for invoice_vals in invoices:
if not ref_invoice_vals:
ref_invoice_vals = invoice_vals
else:
ref_invoice_vals['invoice_line_ids'] += invoice_vals['invoice_line_ids']
origins.add(invoice_vals['invoice_origin'])
payment_refs.add(invoice_vals['invoice_payment_ref'])
refs.add(invoice_vals['ref'])
ref_invoice_vals.update({
'ref': ', '.join(refs)[:2000],
'invoice_origin': ', '.join(origins),
'invoice_payment_ref': len(payment_refs) == 1 and payment_refs.pop() or False,
})
new_invoice_vals_list.append(ref_invoice_vals)
invoice_vals_list = new_invoice_vals_list
# 3) Create invoices.
moves = self.env['account.move']
AccountMove = self.env['account.move'].with_context(default_move_type='in_invoice')
for vals in invoice_vals_list:
moves |= AccountMove.with_company(vals['company_id']).create(vals)
# 4) Some moves might actually be refunds: convert them if the total amount is negative
# We do this after the moves have been created since we need taxes, etc. to know if the total
# is actually negative or not
moves.filtered(lambda m: m.currency_id.round(m.amount_total) < 0).action_switch_invoice_into_refund_credit_note()
return self.action_view_invoice(moves)
def _prepare_invoice(self):
"""Prepare the dict of values to create the new invoice for a purchase order.
"""
self.ensure_one()
self = self.with_company(self.company_id)
move_type = self._context.get('default_move_type', 'in_invoice')
journal = self.env['account.move'].with_context(default_move_type=move_type)._get_default_journal()
if not journal:
raise UserError(_('Please define an accounting purchase journal for the company %s (%s).') % (self.company_id.name, self.company_id.id))
partner_invoice_id = self.partner_id.address_get(['invoice'])['invoice']
invoice_vals = {
'ref': self.partner_ref or '',
'move_type': move_type,
'narration': self.notes,
'currency_id': self.currency_id.id,
'invoice_user_id': self.user_id and self.user_id.id,
'partner_id': partner_invoice_id,
'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(partner_invoice_id)).id,
'invoice_payment_ref': self.partner_ref or '',
'invoice_partner_bank_id': self.partner_id.bank_ids[:1].id,
'invoice_origin': self.name,
'invoice_payment_term_id': self.payment_term_id.id,
'invoice_line_ids': [],
'company_id': self.company_id
}
return invoice_vals
def action_view_invoice(self, invoices=False):
"""This function returns an action that display existing vendor bills of
given purchase order ids. When only one found, show the vendor bill
immediately.
"""
if not invoices:
# Invoice_ids may be filtered depending on the user. To ensure we get all
# invoices related to the purchase order, we read them in sudo to fill the
# cache.
self.sudo()._read(['invoice_ids'])
invoices = self.invoice_ids
action = self.env.ref('account.action_move_in_invoice_type')
result = action.read()[0]
create_bill = self.env.context.get('create_bill', False)
# override the context to get rid of the default filtering
result['context'] = {
'default_move_type': 'in_invoice',
'default_company_id': self.company_id.id,
'default_purchase_id': self.id,
}
# Invoice_ids may be filtered depending on the user. To ensure we get all
# invoices related to the purchase order, we read them in sudo to fill the
# cache.
self.sudo()._read(['invoice_ids'])
# choose the view_mode accordingly
if len(self.invoice_ids) > 1 and not create_bill:
result['domain'] = "[('id', 'in', " + str(self.invoice_ids.ids) + ")]"
else:
if len(invoices) > 1:
result['domain'] = [('id', 'in', invoices.ids)]
elif len(invoices) == 1:
res = self.env.ref('account.view_move_form', False)
form_view = [(res and res.id or False, 'form')]
if 'views' in result:
result['views'] = form_view + [(state,view) for state,view in action['views'] if view != 'form']
result['views'] = form_view + [(state, view) for state, view in action['views'] if view != 'form']
else:
result['views'] = form_view
# Do not set an invoice_id if we want to create a new bill.
if not create_bill:
result['res_id'] = self.invoice_ids.id or False
result['context']['default_invoice_origin'] = self.name
result['context']['default_ref'] = self.partner_ref
result['res_id'] = invoices.id
else:
result = {'type': 'ir.actions.act_window_close'}
return result
@@ -476,6 +561,8 @@ class PurchaseOrderLine(models.Model):
" - Stock Moves: the quantity comes from confirmed pickings\n")
qty_received = fields.Float("Received Qty", compute='_compute_qty_received', inverse='_inverse_qty_received', compute_sudo=True, store=True, digits='Product Unit of Measure')
qty_received_manual = fields.Float("Manual Received Qty", digits='Product Unit of Measure', copy=False)
qty_to_invoice = fields.Float(compute='_compute_qty_invoiced', string='To Invoice Quantity', store=True, readonly=True,
digits='Product Unit of Measure')
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)
@@ -533,9 +620,10 @@ class PurchaseOrderLine(models.Model):
taxes = line.product_id.supplier_taxes_id.filtered(lambda r: r.company_id == line.env.company)
line.taxes_id = fpos.map_tax(taxes, line.product_id, line.order_id.partner_id)
@api.depends('invoice_lines.move_id.state', 'invoice_lines.quantity')
@api.depends('invoice_lines.move_id.state', 'invoice_lines.quantity', 'qty_received', 'product_uom_qty', 'order_id.state')
def _compute_qty_invoiced(self):
for line in self:
# compute qty_invoiced
qty = 0.0
for inv_line in line.invoice_lines:
if inv_line.move_id.state not in ['cancel']:
@@ -545,6 +633,15 @@ class PurchaseOrderLine(models.Model):
qty -= inv_line.product_uom_id._compute_quantity(inv_line.quantity, line.product_uom)
line.qty_invoiced = qty
# compute qty_to_invoice
if line.order_id.state in ['purchase', 'done']:
if line.product_id.purchase_method == 'purchase':
line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced
else:
line.qty_to_invoice = line.qty_received - line.qty_invoiced
else:
line.qty_to_invoice = 0
@api.depends('product_id')
def _compute_qty_received_method(self):
for line in self:
@@ -734,36 +831,36 @@ class PurchaseOrderLine(models.Model):
return name
def _prepare_account_move_line(self, move):
def _prepare_account_move_line(self, move=False):
self.ensure_one()
if self.product_id.purchase_method == 'purchase':
qty = self.product_qty - self.qty_invoiced
else:
qty = self.qty_received - self.qty_invoiced
if float_compare(qty, 0.0, precision_rounding=self.product_uom.rounding) <= 0:
qty = 0.0
res = {
'display_type': self.display_type,
'sequence': self.sequence,
'name': '%s: %s' % (self.order_id.name, self.name),
'product_id': self.product_id.id,
'product_uom_id': self.product_uom.id,
'quantity': self.qty_to_invoice,
'price_unit': self.price_unit,
'tax_ids': [(6, 0, self.taxes_id.ids)],
'analytic_account_id': self.account_analytic_id.id,
'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
'purchase_line_id': self.id,
}
if not move:
return res
if self.currency_id == move.company_id.currency_id:
currency = False
else:
currency = move.currency_id
return {
'name': '%s: %s' % (self.order_id.name, self.name),
res.update({
'move_id': move.id,
'currency_id': currency and currency.id or False,
'purchase_line_id': self.id,
'date_maturity': move.invoice_date_due,
'product_uom_id': self.product_uom.id,
'product_id': self.product_id.id,
'price_unit': self.price_unit,
'quantity': qty,
'partner_id': move.partner_id.id,
'analytic_account_id': self.account_analytic_id.id,
'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
'tax_ids': [(6, 0, self.taxes_id.ids)],
'display_type': self.display_type,
}
})
return res
@api.model
def _prepare_add_missing_fields(self, values):
+1
View File
@@ -3,4 +3,5 @@
from . import test_purchase
from . import test_purchase_order_report
from . import test_purchase_invoice
from . import test_access_rights
+13 -11
View File
@@ -61,11 +61,9 @@ class TestPurchaseInvoice(SavepointCase):
purchase_order = purchase_order_form.save()
purchase_order.button_confirm()
action = purchase_order.with_user(self.purchase_user).action_view_invoice()
invoice_form = Form(self.env['account.move'].with_user(self.purchase_user).with_context(
action['context']
))
invoice = invoice_form.save()
purchase_order.order_line.qty_received = 4
purchase_order.action_create_invoice()
invoice = purchase_order.invoice_ids
with self.assertRaises(AccessError):
invoice.post()
@@ -86,9 +84,11 @@ class TestPurchaseInvoice(SavepointCase):
line.price_unit = 5
purchase_order_user2 = purchase_order_form.save()
action = purchase_order_user2.with_user(purchase_user_2).action_view_invoice()
invoice_form = Form(self.env['account.move'].with_user(purchase_user_2).with_context(action['context']))
vendor_bill_user2 = invoice_form.save()
purchase_order_user2.button_confirm()
purchase_order_user2.order_line.qty_received = 4
purchase_order_user2.action_create_invoice()
vendor_bill_user2 = purchase_order_user2.invoice_ids
# open purchase_order_user2 and vendor_bill_user2 with `self.purchase_user`
purchase_order_user1 = Form(purchase_order_user2.with_user(self.purchase_user))
@@ -121,9 +121,11 @@ class TestPurchaseInvoice(SavepointCase):
line.price_unit = 5
purchase_order_user2 = purchase_order_form.save()
action = purchase_order_user2.with_user(purchase_user_2).action_view_invoice()
invoice_form = Form(self.env['account.move'].with_user(purchase_user_2).with_context(action['context']))
vendor_bill_user2 = invoice_form.save()
purchase_order_user2.button_confirm()
purchase_order_user2.order_line.qty_received = 4
purchase_order_user2.action_create_invoice()
vendor_bill_user2 = purchase_order_user2.invoice_ids
# check user 1 cannot read the invoice
with self.assertRaises(AccessError):
@@ -0,0 +1,263 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.account.tests.common import AccountTestNoChartCommon
class TestCommonPurchaseNoChart(AccountTestNoChartCommon):
""" This class should be extended for test suite of purchase flows with a minimal chart of accounting
installed. This test suite should be executed at module installation.
This class provides some method to generate testing data well configured, according to the minimal
chart of account, defined in `AccountTestNoChartCommon` class.
"""
@classmethod
def setUpClass(cls):
super(TestCommonPurchaseNoChart, cls).setUpClass()
# create a vendor
context_no_mail = {'no_reset_password': True, 'mail_create_nosubscribe': True, 'mail_create_nolog': True}
Partner = cls.env['res.partner'].with_context(context_no_mail)
cls.vendor = Partner.create({
'name': 'vendor',
'email': 'vendor@vendor.com',
'property_account_payable_id': cls.account_payable.id,
'property_account_receivable_id': cls.account_receivable.id,
'company_id': cls.env.ref('base.main_company').id
})
@classmethod
def setUpClassicProducts(cls):
uom_unit = cls.env.ref('uom.product_uom_unit')
uom_hour = cls.env.ref('uom.product_uom_hour')
cls.product_order = cls.env['product.product'].create({
'name': "Zed+ Antivirus",
'standard_price': 235.0,
'list_price': 280.0,
'type': 'consu',
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
'purchase_method': 'purchase',
'default_code': 'PROD_ORDER',
'taxes_id': False,
# 'categ_id': cls.product_category.id,
})
cls.service_deliver = cls.env['product.product'].create({
'name': "Cost-plus Contract",
'standard_price': 200.0,
'list_price': 180.0,
'type': 'service',
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
'purchase_method': 'receive',
'default_code': 'SERV_DEL',
'taxes_id': False,
# 'categ_id': cls.product_category.id,
})
cls.service_order = cls.env['product.product'].create({
'name': "Prepaid Consulting",
'standard_price': 40.0,
'list_price': 90.0,
'type': 'service',
'uom_id': uom_hour.id,
'uom_po_id': uom_hour.id,
'purchase_method': 'purchase',
'default_code': 'PRE-PAID',
'taxes_id': False,
# 'categ_id': cls.product_category.id,
})
cls.product_deliver = cls.env['product.product'].create({
'name': "Switch, 24 ports",
'standard_price': 55.0,
'list_price': 70.0,
'type': 'consu',
'uom_id': uom_unit.id,
'uom_po_id': uom_unit.id,
'purchase_method': 'receive',
'default_code': 'PROD_DEL',
'taxes_id': False,
# 'categ_id': cls.product_category.id,
})
class TestPurchaseToInvoice(TestCommonPurchaseNoChart):
@classmethod
def setUpClass(cls):
super(TestPurchaseToInvoice, cls).setUpClass()
cls.setUpClassicProducts()
cls.setUpAdditionalAccounts()
cls.setUpAccountJournal()
def test_vendor_bill_delivered(self):
"""Test if a order of product invoiced by delivered quantity can be
correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.vendor.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_deliver = PurchaseOrderLine.create({
'name': self.product_deliver.name,
'product_id': self.product_deliver.id,
'product_qty': 10.0,
'product_uom': self.product_deliver.uom_id.id,
'price_unit': self.product_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_deliver = PurchaseOrderLine.create({
'name': self.service_deliver.name,
'product_id': self.service_deliver.id,
'product_qty': 10.0,
'product_uom': self.service_deliver.uom_id.id,
'price_unit': self.service_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
self.assertEqual(purchase_order.invoice_status, "no")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 5)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 5)
def test_vendor_bill_ordered(self):
"""Test if a order of product invoiced by ordered quantity can be
correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.vendor.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_order = PurchaseOrderLine.create({
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 10.0,
'product_uom': self.product_order.uom_id.id,
'price_unit': self.product_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_order = PurchaseOrderLine.create({
'name': self.service_order.name,
'product_id': self.service_order.id,
'product_qty': 10.0,
'product_uom': self.service_order.uom_id.id,
'price_unit': self.service_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 10)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 10)
self.assertEqual(line.qty_invoiced, 0.0)
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
def test_vendor_bill_delivered_return(self):
"""Test when return product, a order of product invoiced by delivered
quantity can be correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.vendor.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_deliver = PurchaseOrderLine.create({
'name': self.product_deliver.name,
'product_id': self.product_deliver.id,
'product_qty': 10.0,
'product_uom': self.product_deliver.uom_id.id,
'price_unit': self.product_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_deliver = PurchaseOrderLine.create({
'name': self.service_deliver.name,
'product_id': self.service_deliver.id,
'product_qty': 10.0,
'product_uom': self.service_deliver.uom_id.id,
'price_unit': self.service_deliver.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
purchase_order.order_line.qty_received = 10
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "to invoice")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, -5)
self.assertEqual(line.qty_invoiced, 10)
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 5)
def test_vendor_bill_ordered_return(self):
"""Test when return product, a order of product invoiced by ordered
quantity can be correctly invoiced."""
purchase_order = self.env['purchase.order'].with_context(tracking_disable=True).create({
'partner_id': self.vendor.id,
})
PurchaseOrderLine = self.env['purchase.order.line'].with_context(tracking_disable=True)
pol_prod_order = PurchaseOrderLine.create({
'name': self.product_order.name,
'product_id': self.product_order.id,
'product_qty': 10.0,
'product_uom': self.product_order.uom_id.id,
'price_unit': self.product_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
pol_serv_order = PurchaseOrderLine.create({
'name': self.service_order.name,
'product_id': self.service_order.id,
'product_qty': 10.0,
'product_uom': self.service_order.uom_id.id,
'price_unit': self.service_order.list_price,
'order_id': purchase_order.id,
'taxes_id': False,
})
purchase_order.button_confirm()
purchase_order.order_line.qty_received = 10
purchase_order.action_create_invoice()
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
purchase_order.order_line.qty_received = 5
self.assertEqual(purchase_order.invoice_status, "invoiced")
for line in purchase_order.order_line:
self.assertEqual(line.qty_to_invoice, 0.0)
self.assertEqual(line.qty_invoiced, 10)
+14 -2
View File
@@ -127,12 +127,12 @@
<button name="print_quotation" string="Print RFQ" type="object" states="draft" class="oe_highlight" groups="base.group_user"/>
<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_view_invoice" string="Create Bill" type="object" class="oe_highlight" context="{'create_bill':True}" attrs="{'invisible': ['|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'in', ('no', 'invoiced'))]}"/>
<button name="action_create_invoice" string="Create Bill" type="object" class="oe_highlight" context="{'create_bill':True}" attrs="{'invisible': ['|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'in', ('no', 'invoiced'))]}"/>
<button name="action_rfq_send" states="sent" string="Re-Send by Email" type="object" context="{'send_rfq':True}"/>
<button name="print_quotation" string="Print RFQ" type="object" states="sent" groups="base.group_user"/>
<button name="button_confirm" type="object" states="draft" string="Confirm Order" id="draft_confirm"/>
<button name="action_rfq_send" states="purchase" string="Send PO by Email" type="object" context="{'send_rfq':False}"/>
<button name="action_view_invoice" string="Create Bill" type="object" context="{'create_bill':True}" attrs="{'invisible': ['|', '|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'not in', ('no', 'invoiced')), ('order_line', '=', [])]}"/>
<button name="action_create_invoice" string="Create Bill" type="object" context="{'create_bill':True}" attrs="{'invisible': ['|', '|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'not in', ('no', 'invoiced')), ('order_line', '=', [])]}"/>
<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="Lock" states="purchase"/>
@@ -652,4 +652,16 @@
</search>
</field>
</record>
<record id="action_purchase_batch_bills" model="ir.actions.server">
<field name="name">Create Vendor Bills</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="binding_model_id" ref="purchase.model_purchase_order"/>
<field name='groups_id' eval="[(4, ref('account.group_account_invoice'))]"/>
<field name="state">code</field>
<field name="code">
if records:
action = records.action_create_invoice()
</field>
</record>
</odoo>
+4 -4
View File
@@ -135,10 +135,10 @@ class PurchaseOrder(models.Model):
result['res_id'] = pick_ids.id
return result
def action_view_invoice(self):
result = super().action_view_invoice()
result['context']['default_invoice_incoterm_id'] = self.incoterm_id.id
return result
def _prepare_invoice(self):
invoice_vals = super()._prepare_invoice()
invoice_vals['invoice_incoterm_id'] = self.incoterm_id.id
return invoice_vals
# --------------------------------------------------
# Business methods
@@ -278,9 +278,8 @@ class TestStockValuationLCFIFOVB(TestStockValuationLCCommon):
self.assertEqual(valuation_aml.credit, 0)
# Create a vebdor bill for the RFQ
action = rfq.action_view_invoice()
vb = Form(self.env['account.move'].with_context(action['context']))
vb = vb.save()
action = rfq.action_create_invoice()
vb = self.env['account.move'].search([('id', '=', action['res_id'])])
vb.post()
input_aml = self._get_stock_input_move_lines()[-1]
@@ -366,9 +365,9 @@ class TestStockValuationLCFIFOVB(TestStockValuationLCCommon):
self.assertEqual(valuation_aml.debit, 100)
self.assertEqual(valuation_aml.credit, 0)
# Create a vebdor bill for the RFQ and add to it the landed cost
action = rfq.action_view_invoice()
vb = Form(self.env['account.move'].with_context(action['context']))
# Create a vendor bill for the RFQ and add to it the landed cost
vb = Form(self.env['account.move'].with_context(default_move_type='in_invoice'))
vb.partner_id = self.vendor1
with vb.invoice_line_ids.new() as inv_line:
inv_line.product_id = self.productlc1
inv_line.price_unit = 50
@@ -419,9 +418,8 @@ class TestStockValuationLCFIFOVB(TestStockValuationLCCommon):
self.assertEqual(valuation_aml.credit, 0)
# Create a vebdor bill for the RFQ
action = rfq.action_view_invoice()
vb = Form(self.env['account.move'].with_context(action['context']))
vb = vb.save()
action = rfq.action_create_invoice()
vb = self.env['account.move'].search([('id', '=', action['res_id'])])
vb.post()
expense_aml = self._get_expense_move_lines()[-1]