Initial commit: Accounting packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:47 +02:00
commit 4ef34c2317
2661 changed files with 1709616 additions and 0 deletions

View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_invoice_report
from . import account_hash_integrity_templates

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ReportAccountHashIntegrity(models.AbstractModel):
_name = 'report.account.report_hash_integrity'
_description = 'Get hash integrity result as PDF.'
@api.model
def _get_report_values(self, docids, data=None):
if data:
data.update(self.env.company._check_hash_integrity())
else:
data = self.env.company._check_hash_integrity()
return {
'doc_ids' : docids,
'doc_model' : self.env['res.company'],
'data' : data,
'docs' : self.env['res.company'].browse(self.env.company.id),
}

View file

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<template id="report_hash_integrity">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="company">
<t t-call="web.external_layout">
<div class="page">
<div class="row" id="hash_header">
<div class="col-12">
<br/>
<h2>Hash Integrity Result - <span t-esc="data['printing_date']"/></h2>
<br/>
</div>
</div>
<div class="row">
<div class="col-12" id="hash_config_review">
<br/>
<h3>Configuration review</h3>
<br/>
</div>
</div>
<div class="row">
<div class="col-12">
<table class="table table-bordered" style="table-layout: fixed" 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>
</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']"/>
</td>
<td class="text-center"><span t-esc="result['restricted_by_hash_table']"/></td>
<td><span t-esc="result['msg_cover']"/></td>
</tr>
</t>
</tbody>
</table>
</div>
</div>
<t t-if="any(result['first_hash'] != 'None' for result in data['results'])">
<div class="row" style="page-break-before:always;">
<div class="col-12" id="hash_data_consistency">
<br/>
<h3>Data consistency check</h3>
<br/>
</div>
</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">
<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>
</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']"/>
</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>
</tr>
</t>
</t>
</tbody>
</table>
</div>
</div>
<div class="row" id="hash_last_div">
<div class="col-12" id="hash_chain_compliant">
<br/>
<h6>
The hash chain is compliant: it is not possible to alter the
data without breaking the hash chain for subsequent parts.
</h6>
<br/>
</div>
</div>
</t>
</div>
</t>
</t>
</t>
</template>
</data>
</odoo>

View file

@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.addons.account.models.account_move import PAYMENT_STATE_SELECTION
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='Main Partner')
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=PAYMENT_STATE_SELECTION, 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)])
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")
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',
],
'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.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.journal_id,
line.company_id,
line.company_currency_id,
line.partner_id AS commercial_partner_id,
account.account_type AS user_type,
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,
line.price_total * (CASE WHEN move.move_type IN ('in_invoice','out_refund','in_receipt') THEN -1 ELSE 1 END)
AS price_total,
-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,
line.currency_id AS currency_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 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 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
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(silent_errors=data['report_type'] == 'html')
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

View file

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_account_invoice_report_pivot" model="ir.ui.view">
<field name="name">account.invoice.report.pivot</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<pivot string="Invoices Analysis" sample="1">
<field name="product_categ_id" type="col"/>
<field name="invoice_date" type="row"/>
<field name="price_subtotal" type="measure"/>
</pivot>
</field>
</record>
<record id="view_account_invoice_report_graph" model="ir.ui.view">
<field name="name">account.invoice.report.graph</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<graph string="Invoices Analysis" type="line" sample="1">
<field name="product_categ_id"/>
<field name="price_subtotal" type="measure"/>
</graph>
</field>
</record>
<record id="account_invoice_report_view_tree" model="ir.ui.view">
<field name="name">account.invoice.report.view.tree</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<tree string="Invoices Analysis">
<field name="move_id" string="Invoice Number"/>
<field name="journal_id" optional="hide"/>
<field name="partner_id" optional="show"/>
<field name="country_id" optional="hide"/>
<field name="invoice_date" optional="show"/>
<field name="invoice_date_due" optional="show"/>
<field name="invoice_user_id" optional="hide" widget="many2one_avatar_user"/>
<field name="product_categ_id" optional="hide"/>
<field name="product_id" optional="show"/>
<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" optional="show" sum="Total"/>
<field name="price_total" optional="show" sum="Total"/>
<field name="state" optional="hide"/>
<field name="payment_state" optional="hide"/>
<field name="move_type" optional="hide"/>
</tree>
</field>
</record>
<!-- Custom reports (aka filters) -->
<record id="filter_invoice_report_salespersons" model="ir.filters">
<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="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="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="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="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="context">{'group_by': ['invoice_date:month', 'country_id']}</field>
</record>
<record id="view_account_invoice_report_search" model="ir.ui.view">
<field name="name">account.invoice.report.search</field>
<field name="model">account.invoice.report</field>
<field name="arch" type="xml">
<search string="Invoices Analysis">
<filter string="My Invoices" name="my_invoice" domain="[('invoice_user_id', '=', uid)]"/>
<separator/>
<field name="invoice_date"/>
<separator/>
<filter string="To Invoice" name="toinvoice" domain="[('state','=','draft')]" help = "Draft Invoices"/>
<filter string="Invoiced" name="current" domain="[('state','not in', ('draft','cancel'))]"/>
<separator/>
<filter string="Customers" name="customer" domain="['|', ('move_type','=','out_invoice'),('move_type','=','out_refund')]"/>
<filter string="Vendors" name="supplier" domain="['|', ('move_type','=','in_invoice'),('move_type','=','in_refund')]"/>
<separator/>
<filter string="Invoices" name="invoice" domain="['|', ('move_type','=','out_invoice'),('move_type','=','in_invoice')]"/>
<filter string="Credit Notes" name="creditnote" domain="['|', ('move_type','=','out_refund'),('move_type','=','in_refund')]"/>
<separator/>
<filter name="filter_invoice_date" date="invoice_date"/>
<filter name="invoice_date_due" date="invoice_date_due"/>
<field name="partner_id" operator="child_of"/>
<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">
<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}"/>
<filter string="Status" name="status" context="{'group_by':'state'}"/>
<filter string="Company" name="company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<separator orientation="vertical" />
<filter string="Date" name="invoice_date" context="{'group_by':'invoice_date'}"
invisible="context.get('invoice_report_view_hide_invoice_date')"/>
<filter string="Date" name="group_by_invoice_date_week" context="{'group_by':'invoice_date:week'}"
invisible="not context.get('invoice_report_view_hide_invoice_date')"/>
<filter string="Due Date" name="duemonth" context="{'group_by':'invoice_date_due:month'}"/>
</group>
</search>
</field>
</record>
<record id="action_account_invoice_report_all_supp" model="ir.actions.act_window">
<field name="name">Invoices Analysis</field>
<field name="res_model">account.invoice.report</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="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="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="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>
</data>
</odoo>