19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:07 +01:00
parent ba20ce7443
commit 768b70e05e
2357 changed files with 1057103 additions and 712486 deletions

View file

@ -4,7 +4,7 @@
from odoo import api, fields, models
class ReportAccountHashIntegrity(models.AbstractModel):
class ReportAccountReport_Hash_Integrity(models.AbstractModel):
_name = 'report.account.report_hash_integrity'
_description = 'Get hash integrity result as PDF.'

View file

@ -9,7 +9,7 @@
<div class="row" id="hash_header">
<div class="col-12">
<br/>
<h2>Hash Integrity Result - <span t-esc="data['printing_date']"/></h2>
<h2>Data Inalterability Check Report - <span t-out="data['printing_date']"/></h2>
<br/>
</div>
</div>
@ -22,29 +22,29 @@
</div>
<div class="row">
<div class="col-12">
<table class="table table-bordered" style="table-layout: fixed" id="hash_config_table">
<table class="table table-borderless" id="hash_config_table">
<thead style="display: table-row-group">
<tr>
<th class="text-center" style="width: 30%" scope="col">Journal</th>
<th class="text-center" style="width: 20%" scope="col">Inalterability check</th>
<th class="text-center" style="width: 50%" scope="col">Coverage</th>
<th style="width: 30%" scope="col">Journal (Sequence Prefix)</th>
<th class="text-center text-nowrap" style="width: 20%" scope="col">Restricted</th>
<th style="width: 50%" scope="col">Check</th>
</tr>
</thead>
<tbody>
<t t-foreach="data['results']" t-as="result">
<tr>
<td>
[<span t-esc="result['journal_code']"/>] <span t-esc="result['journal_name']"/>
<span t-out="result['journal_name']"/>
</td>
<td class="text-center"><span t-esc="result['restricted_by_hash_table']"/></td>
<td><span t-esc="result['msg_cover']"/></td>
<td class="text-center"><span t-out="result['restricted_by_hash_table']"/></td>
<td><span t-out="result['msg_cover']"/></td>
</tr>
</t>
</tbody>
</table>
</div>
</div>
<t t-if="any(result['first_hash'] != 'None' for result in data['results'])">
<t t-if="any(result['status'] == 'verified' for result in data['results'])">
<div class="row" style="page-break-before:always;">
<div class="col-12" id="hash_data_consistency">
<br/>
@ -54,30 +54,30 @@
</div>
<div class="row">
<div class="col-12" id="hash_data_consistency_table">
<table class="table table-bordered" style="table-layout: fixed">
<thead style="display: table-row-group">
<table class="table table-borderless">
<thead style="display: table-row-group; border-bottom: 1px solid black;">
<tr>
<th class="text-center" style="width: 20%" scope="col">Journal</th>
<th class="text-center" style="width: 20%" scope="col">First Hash</th>
<th class="text-center" style="width: 20%" scope="col">First Entry</th>
<th class="text-center" style="width: 20%" scope="col">Last Hash</th>
<th class="text-center" style="width: 20%" scope="col">Last Entry</th>
<th class="text-start fw-bold" style="width: 20%" scope="col">Journal</th>
<th class="text-start fw-bold" style="width: 20%" scope="col">First Hash</th>
<th class="text-start fw-bold" style="width: 20%" scope="col">First Entry</th>
<th class="text-start fw-bold" style="width: 20%" scope="col">Last Hash</th>
<th class="text-start fw-bold" style="width: 20%" scope="col">Last Entry</th>
</tr>
</thead>
<tbody>
<t t-foreach="data['results']" t-as="result">
<t t-if="result['first_hash'] != 'None'">
<tr>
<td><span t-esc="result['journal_code']"/></td>
<td><span t-esc="result['first_hash']"/></td>
<td>
<span t-esc="result['first_move_name']"/> <br/>
<span t-esc="result['first_move_date']"/>
<t t-if="result['status'] == 'verified'">
<tr style="border-bottom: 1px solid lightgrey;">
<td class="align-top"><span t-out="result['journal_name']"/></td>
<td class="align-top text-break"><span t-out="result['first_hash']"/></td>
<td class="align-top">
<span t-out="result['first_move_name']"/> <br/>
<span t-out="result['first_move_date']"/>
</td>
<td><span t-esc="result['last_hash']"/></td>
<td>
<span t-esc="result['last_move_name']"/> <br/>
<span t-esc="result['last_move_date']"/>
<td class="align-top text-break"><span t-out="result['last_hash']"/></td>
<td class="align-top">
<span t-out="result['last_move_name']"/> <br/>
<span t-out="result['last_move_date']"/>
</td>
</tr>
</t>

View file

@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.tools import SQL
from odoo.tools.query import Query
from odoo.addons.account.models.account_move import PAYMENT_STATE_SELECTION
from functools import lru_cache
class AccountInvoiceReport(models.Model):
_name = "account.invoice.report"
_name = 'account.invoice.report'
_description = "Invoices Statistics"
_auto = False
_rec_name = 'invoice_date'
@ -40,39 +42,44 @@ class AccountInvoiceReport(models.Model):
# ==== 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_uom_id = fields.Many2one('uom.uom', string='Unit', 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)])
price_subtotal = fields.Float(string='Untaxed Total', readonly=True)
price_total = fields.Float(string='Total in Currency', readonly=True)
price_average = fields.Float(string='Average Price', readonly=True, group_operator="avg")
account_id = fields.Many2one('account.account', string='Revenue/Expense Account', readonly=True)
price_subtotal_currency = fields.Float(string='Untaxed Amount in Currency', readonly=True)
price_subtotal = fields.Float(string='Untaxed Amount', readonly=True)
price_total = fields.Float(string='Total', readonly=True)
price_total_currency = fields.Float(string='Total in Currency', readonly=True)
price_average = fields.Float(string='Average Price', readonly=True, aggregator="avg")
price_margin = fields.Float(string='Margin', readonly=True)
inventory_value = fields.Float(string='Inventory Value', readonly=True)
currency_id = fields.Many2one('res.currency', string='Currency', readonly=True)
_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',
'invoice_date', 'invoice_date_due', 'invoice_payment_term_id', 'partner_bank_id', 'invoice_currency_rate',
],
'account.move.line': [
'quantity', 'price_subtotal', 'price_total', 'amount_residual', 'balance', 'amount_currency',
'move_id', 'product_id', 'product_uom_id', 'account_id',
'journal_id', 'company_id', 'currency_id', 'partner_id',
],
'product.product': ['product_tmpl_id'],
'product.product': ['product_tmpl_id', 'standard_price'],
'product.template': ['categ_id'],
'uom.uom': ['category_id', 'factor', 'name', 'uom_type'],
'uom.uom': ['factor', 'name'],
'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())
def _table_query(self) -> SQL:
return SQL('%s %s %s', self._select(), self._from(), self._where())
@api.model
def _select(self):
return '''
def _select(self) -> SQL:
return SQL(
'''
SELECT
line.id,
line.move_id,
@ -93,24 +100,39 @@ class AccountInvoiceReport(models.Model):
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)
line.quantity * COALESCE(uom_line.factor, 1) / NULLIF(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,
line.price_subtotal * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
AS price_subtotal_currency,
-line.balance * account_currency_table.rate AS price_subtotal,
line.price_total * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
/ move.invoice_currency_rate
AS price_total,
line.price_total * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
AS price_total_currency,
-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,
/ NULLIF(COALESCE(uom_line.factor, 1), 0.0) * COALESCE(uom_template.factor, 1),
0.0) * account_currency_table.rate AS price_average,
CASE
WHEN move.move_type NOT IN ('out_invoice', 'out_receipt', 'out_refund') THEN 0.0
WHEN move.move_type = 'out_refund' THEN account_currency_table.rate * (-line.balance + (line.quantity * COALESCE(uom_line.factor, 1) / NULLIF(COALESCE(uom_template.factor, 1), 0.0)) * COALESCE(product.standard_price -> line.company_id::text, to_jsonb(0.0))::float)
ELSE account_currency_table.rate * (-line.balance - (line.quantity * COALESCE(uom_line.factor, 1) / NULLIF(COALESCE(uom_template.factor, 1), 0.0)) * COALESCE(product.standard_price -> line.company_id::text, to_jsonb(0.0))::float)
END
AS price_margin,
account_currency_table.rate * line.quantity * COALESCE(uom_line.factor, 1) / NULLIF(COALESCE(uom_template.factor, 1), 0.0) * (CASE WHEN move.move_type IN ('out_invoice','in_refund','out_receipt') THEN -1 ELSE 1 END)
* COALESCE(product.standard_price -> line.company_id::text, to_jsonb(0.0))::float AS inventory_value,
COALESCE(partner.country_id, commercial_partner.country_id) AS country_id,
line.currency_id AS currency_id
'''
''',
)
@api.model
def _from(self):
return '''
def _from(self) -> SQL:
return SQL(
'''
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
@ -120,45 +142,33 @@ class AccountInvoiceReport(models.Model):
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()}}),
JOIN %(currency_table)s ON account_currency_table.company_id = line.company_id
''',
currency_table=self.env['res.currency']._get_simple_currency_table(self.env.companies),
)
@api.model
def _where(self):
return '''
def _where(self) -> SQL:
return SQL(
'''
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 line.display_type = 'product'
'''
''',
)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
"""
This is a hack to allow us to correctly calculate the average price.
"""
set_fields = set(fields)
if 'price_average:avg' in fields:
set_fields.add('quantity:sum')
set_fields.add('price_subtotal:sum')
res = super().read_group(domain, list(set_fields), groupby, offset, limit, orderby, lazy)
if 'price_average:avg' in fields:
for data in res:
data['price_average'] = data['price_subtotal'] / data['quantity'] if data['quantity'] else 0
if 'quantity:sum' not in fields:
del data['quantity']
if 'price_subtotal:sum' not in fields:
del data['price_subtotal']
return res
def _read_group_select(self, aggregate_spec: str, query: Query) -> SQL:
""" This override allows us to correctly calculate the average price of products. """
if aggregate_spec != 'price_average:avg':
return super()._read_group_select(aggregate_spec, query)
return SQL(
'COALESCE(SUM(%(f_price)s) / NULLIF(SUM(%(f_qty)s), 0.0), 0)',
f_qty=self._field_to_sql(self._table, 'quantity', query),
f_price=self._field_to_sql(self._table, 'price_subtotal', query),
)
class ReportInvoiceWithoutPayment(models.AbstractModel):
class ReportAccountReport_Invoice(models.AbstractModel):
_name = 'report.account.report_invoice'
_description = 'Account report without payment lines'
@ -180,10 +190,11 @@ class ReportInvoiceWithoutPayment(models.AbstractModel):
'qr_code_urls': qr_code_urls,
}
class ReportInvoiceWithPayment(models.AbstractModel):
class ReportAccountReport_Invoice_With_Payments(models.AbstractModel):
_name = 'report.account.report_invoice_with_payments'
_description = 'Account report with payment lines'
_inherit = 'report.account.report_invoice'
_inherit = ['report.account.report_invoice']
@api.model
def _get_report_values(self, docids, data=None):

View file

@ -25,10 +25,10 @@
</record>
<record id="account_invoice_report_view_tree" model="ir.ui.view">
<field name="name">account.invoice.report.view.tree</field>
<field name="name">account.invoice.report.view.list</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<tree string="Invoices Analysis">
<list string="Invoices Analysis">
<field name="move_id" string="Invoice Number"/>
<field name="journal_id" optional="hide"/>
<field name="partner_id" optional="show"/>
@ -41,12 +41,16 @@
<field name="company_id" groups="base.group_multi_company"/>
<field name="price_average" optional="hide" sum="Total"/>
<field name="quantity" optional="hide" sum="Total"/>
<field name="price_subtotal_currency" optional="show" sum="Total"/>
<field name="price_subtotal" optional="show" sum="Total"/>
<field name="price_total" optional="show" sum="Total"/>
<field name="price_total_currency" optional="show" sum="Total"/>
<field name="price_margin" optional="hide"/>
<field name="inventory_value" optional="hide" sum="Total"/>
<field name="state" optional="hide"/>
<field name="payment_state" optional="hide"/>
<field name="move_type" optional="hide"/>
</tree>
</list>
</field>
</record>
@ -55,35 +59,35 @@
<field name="name">By Salespersons</field>
<field name="model_id">account.invoice.report</field>
<field name="domain">[]</field>
<field name="user_id" eval="False"/>
<field name="user_ids" eval="False"/>
<field name="context">{'group_by': ['invoice_date:month', 'invoice_user_id']}</field>
</record>
<record id="filter_invoice_product" model="ir.filters">
<field name="name">By Product</field>
<field name="model_id">account.invoice.report</field>
<field name="domain">[]</field>
<field name="user_id" eval="False"/>
<field name="user_ids" eval="False"/>
<field name="context">{'group_by': ['invoice_date:month', 'product_id'], 'set_visible':True, 'residual_invisible':True}</field>
</record>
<record id="filter_invoice_product_category" model="ir.filters">
<field name="name">By Product Category</field>
<field name="model_id">account.invoice.report</field>
<field name="domain">[]</field>
<field name="user_id" eval="False"/>
<field name="user_ids" eval="False"/>
<field name="context">{'group_by': ['invoice_date:month', 'product_categ_id'], 'residual_invisible':True}</field>
</record>
<record id="filter_invoice_refund" model="ir.filters">
<field name="name">By Credit Note</field>
<field name="model_id">account.invoice.report</field>
<field name="domain">[('move_type', '=', 'out_refund')]</field>
<field name="user_id" eval="False"/>
<field name="user_ids" eval="False"/>
<field name="context">{'group_by': ['invoice_date:month', 'invoice_user_id']}</field>
</record>
<record id="filter_invoice_country" model="ir.filters">
<field name="name">By Country</field>
<field name="model_id">account.invoice.report</field>
<field name="domain">[]</field>
<field name="user_id" eval="False"/>
<field name="user_ids" eval="False"/>
<field name="context">{'group_by': ['invoice_date:month', 'country_id']}</field>
</record>
@ -111,7 +115,7 @@
<field name="invoice_user_id" />
<field name="product_id" />
<field name="product_categ_id" filter_domain="[('product_categ_id', 'child_of', self)]"/>
<group expand="1" string="Group By">
<group>
<filter string="Salesperson" name='user' context="{'group_by':'invoice_user_id'}"/>
<filter string="Partner" name="partner_id" context="{'group_by':'partner_id','residual_visible':True}"/>
<filter string="Product Category" name="category_product" context="{'group_by':'product_categ_id','residual_invisible':True}"/>
@ -129,18 +133,20 @@
</record>
<record id="action_account_invoice_report_all_supp" model="ir.actions.act_window">
<field name="name">Invoices Analysis</field>
<field name="name">Bills Analysis</field>
<field name="res_model">account.invoice.report</field>
<field name="path">vendor-bills-analysis</field>
<field name="view_mode">graph,pivot</field>
<field name="context">{'search_default_current':1, 'search_default_supplier': 1, 'group_by':['invoice_date'], 'group_by_no_leaf':1}</field>
<field name="context">{'search_default_current':1, 'search_default_supplier': 1, 'group_by':['invoice_date:month']}</field>
<field name="search_view_id" ref="view_account_invoice_report_search"/>
<field name="help">From this report, you can have an overview of the amount invoiced from your vendors. The search tool can also be used to personalise your Invoices reports and so, match this analysis to your needs.</field>
</record>
<record id="action_account_invoice_report_all" model="ir.actions.act_window">
<field name="name">Invoices Analysis</field>
<field name="res_model">account.invoice.report</field>
<field name="path">customer-invoices-analysis</field>
<field name="view_mode">graph,pivot</field>
<field name="context">{'search_default_current':1, 'search_default_customer': 1, 'group_by':['invoice_date'], 'group_by_no_leaf':1}</field>
<field name="context">{'search_default_current':1, 'search_default_customer': 1, 'group_by':['invoice_date:month']}</field>
<field name="search_view_id" ref="view_account_invoice_report_search"/>
<field name="help">From this report, you can have an overview of the amount invoiced to your customers. The search tool can also be used to personalise your Invoices reports and so, match this analysis to your needs.</field>
</record>