Current behavior: Invoices report had a wrong average price when invoice had credit note Steps to reproduce: Create a db with accounting Create a customer C and a Product P Create an Invoice for C for 10 pieces of P Create a Credit note for C for 1 P Go to Reporting > Invoice Analysis Open the pivot view, in the Measures, display the Average Price, the Product quantity and the Untaxed Total The Total multiplied by the quantity is not equals to the average. opw-2557420 closes odoo/odoo#81238 X-original-commit: ef26467e288a12571c37d4c133b44f08d99cc702 Signed-off-by: William André (wan) <wan@odoo.com> Signed-off-by: Engels Robin (roen) <roen@odoo.com>
169 lines
7.8 KiB
Python
169 lines
7.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from odoo import models, fields, api
|
|
|
|
from functools import lru_cache
|
|
|
|
|
|
class AccountInvoiceReport(models.Model):
|
|
_name = "account.invoice.report"
|
|
_description = "Invoices Statistics"
|
|
_auto = False
|
|
_rec_name = 'invoice_date'
|
|
_order = 'invoice_date desc'
|
|
|
|
# ==== Invoice fields ====
|
|
move_id = fields.Many2one('account.move', readonly=True)
|
|
journal_id = fields.Many2one('account.journal', string='Journal', readonly=True)
|
|
company_id = fields.Many2one('res.company', string='Company', readonly=True)
|
|
company_currency_id = fields.Many2one('res.currency', string='Company Currency', readonly=True)
|
|
partner_id = fields.Many2one('res.partner', string='Partner', readonly=True)
|
|
commercial_partner_id = fields.Many2one('res.partner', string='Partner Company', help="Commercial Entity")
|
|
country_id = fields.Many2one('res.country', string="Country")
|
|
invoice_user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
|
|
move_type = fields.Selection([
|
|
('out_invoice', 'Customer Invoice'),
|
|
('in_invoice', 'Vendor Bill'),
|
|
('out_refund', 'Customer Credit Note'),
|
|
('in_refund', 'Vendor Credit Note'),
|
|
], readonly=True)
|
|
state = fields.Selection([
|
|
('draft', 'Draft'),
|
|
('posted', 'Open'),
|
|
('cancel', 'Cancelled')
|
|
], string='Invoice Status', readonly=True)
|
|
payment_state = fields.Selection(selection=[
|
|
('not_paid', 'Not Paid'),
|
|
('in_payment', 'In Payment'),
|
|
('paid', 'paid')
|
|
], string='Payment Status', readonly=True)
|
|
fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', readonly=True)
|
|
invoice_date = fields.Date(readonly=True, string="Invoice Date")
|
|
|
|
# ==== Invoice line fields ====
|
|
quantity = fields.Float(string='Product Quantity', readonly=True)
|
|
product_id = fields.Many2one('product.product', string='Product', readonly=True)
|
|
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure', readonly=True)
|
|
product_categ_id = fields.Many2one('product.category', string='Product Category', readonly=True)
|
|
invoice_date_due = fields.Date(string='Due Date', readonly=True)
|
|
account_id = fields.Many2one('account.account', string='Revenue/Expense Account', readonly=True, domain=[('deprecated', '=', False)])
|
|
analytic_account_id = fields.Many2one('account.analytic.account', string='Analytic Account', groups="analytic.group_analytic_accounting")
|
|
price_subtotal = fields.Float(string='Untaxed Total', readonly=True)
|
|
price_average = fields.Float(string='Average Price', readonly=True, group_operator="avg")
|
|
|
|
_depends = {
|
|
'account.move': [
|
|
'name', 'state', 'move_type', 'partner_id', 'invoice_user_id', 'fiscal_position_id',
|
|
'invoice_date', 'invoice_date_due', 'invoice_payment_term_id', 'partner_bank_id',
|
|
],
|
|
'account.move.line': [
|
|
'quantity', 'price_subtotal', 'amount_residual', 'balance', 'amount_currency',
|
|
'move_id', 'product_id', 'product_uom_id', 'account_id', 'analytic_account_id',
|
|
'journal_id', 'company_id', 'currency_id', 'partner_id',
|
|
],
|
|
'product.product': ['product_tmpl_id'],
|
|
'product.template': ['categ_id'],
|
|
'uom.uom': ['category_id', 'factor', 'name', 'uom_type'],
|
|
'res.currency.rate': ['currency_id', 'name'],
|
|
'res.partner': ['country_id'],
|
|
}
|
|
|
|
@property
|
|
def _table_query(self):
|
|
return '%s %s %s' % (self._select(), self._from(), self._where())
|
|
|
|
@api.model
|
|
def _select(self):
|
|
return '''
|
|
SELECT
|
|
line.id,
|
|
line.move_id,
|
|
line.product_id,
|
|
line.account_id,
|
|
line.analytic_account_id,
|
|
line.journal_id,
|
|
line.company_id,
|
|
line.company_currency_id,
|
|
line.partner_id AS commercial_partner_id,
|
|
move.state,
|
|
move.move_type,
|
|
move.partner_id,
|
|
move.invoice_user_id,
|
|
move.fiscal_position_id,
|
|
move.payment_state,
|
|
move.invoice_date,
|
|
move.invoice_date_due,
|
|
uom_template.id AS product_uom_id,
|
|
template.categ_id AS product_categ_id,
|
|
line.quantity / NULLIF(COALESCE(uom_line.factor, 1) / COALESCE(uom_template.factor, 1), 0.0) * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
|
|
AS quantity,
|
|
-line.balance * currency_table.rate AS price_subtotal,
|
|
-COALESCE(
|
|
-- Average line price
|
|
(line.balance / NULLIF(line.quantity, 0.0)) * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
|
|
-- convert to template uom
|
|
* (NULLIF(COALESCE(uom_line.factor, 1), 0.0) / NULLIF(COALESCE(uom_template.factor, 1), 0.0)),
|
|
0.0) * currency_table.rate AS price_average,
|
|
COALESCE(partner.country_id, commercial_partner.country_id) AS country_id
|
|
'''
|
|
|
|
@api.model
|
|
def _from(self):
|
|
return '''
|
|
FROM account_move_line line
|
|
LEFT JOIN res_partner partner ON partner.id = line.partner_id
|
|
LEFT JOIN product_product product ON product.id = line.product_id
|
|
LEFT JOIN account_account account ON account.id = line.account_id
|
|
LEFT JOIN account_account_type user_type ON user_type.id = account.user_type_id
|
|
LEFT JOIN product_template template ON template.id = product.product_tmpl_id
|
|
LEFT JOIN uom_uom uom_line ON uom_line.id = line.product_uom_id
|
|
LEFT JOIN uom_uom uom_template ON uom_template.id = template.uom_id
|
|
INNER JOIN account_move move ON move.id = line.move_id
|
|
LEFT JOIN res_partner commercial_partner ON commercial_partner.id = move.commercial_partner_id
|
|
JOIN {currency_table} ON currency_table.company_id = line.company_id
|
|
'''.format(
|
|
currency_table=self.env['res.currency']._get_query_currency_table({'multi_company': True, 'date': {'date_to': fields.Date.today()}}),
|
|
)
|
|
|
|
@api.model
|
|
def _where(self):
|
|
return '''
|
|
WHERE move.move_type IN ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt')
|
|
AND line.account_id IS NOT NULL
|
|
AND NOT line.exclude_from_invoice_tab
|
|
'''
|
|
|
|
|
|
class ReportInvoiceWithoutPayment(models.AbstractModel):
|
|
_name = 'report.account.report_invoice'
|
|
_description = 'Account report without payment lines'
|
|
|
|
@api.model
|
|
def _get_report_values(self, docids, data=None):
|
|
docs = self.env['account.move'].browse(docids)
|
|
|
|
qr_code_urls = {}
|
|
for invoice in docs:
|
|
if invoice.display_qr_code:
|
|
new_code_url = invoice.generate_qr_code()
|
|
if new_code_url:
|
|
qr_code_urls[invoice.id] = new_code_url
|
|
|
|
return {
|
|
'doc_ids': docids,
|
|
'doc_model': 'account.move',
|
|
'docs': docs,
|
|
'qr_code_urls': qr_code_urls,
|
|
}
|
|
|
|
class ReportInvoiceWithPayment(models.AbstractModel):
|
|
_name = 'report.account.report_invoice_with_payments'
|
|
_description = 'Account report with payment lines'
|
|
_inherit = 'report.account.report_invoice'
|
|
|
|
@api.model
|
|
def _get_report_values(self, docids, data=None):
|
|
rslt = super()._get_report_values(docids, data)
|
|
rslt['report_type'] = data.get('report_type') if data else ''
|
|
return rslt
|