mirror of
https://github.com/bringout/oca-report.git
synced 2026-04-20 13:02:00 +02:00
Initial commit: OCA Report packages (45 packages)
This commit is contained in:
commit
2f4db400df
2543 changed files with 469120 additions and 0 deletions
|
|
@ -0,0 +1,5 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import mis_report_instance_qweb
|
||||
from . import mis_report_instance_xlsx
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import logging
|
||||
|
||||
from odoo import models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Report(models.Model):
|
||||
_inherit = "ir.actions.report"
|
||||
|
||||
def _render_qweb_pdf(self, report_ref, res_ids=None, data=None):
|
||||
if (
|
||||
self._get_report(report_ref).report_name
|
||||
== "mis_builder.report_mis_report_instance"
|
||||
):
|
||||
if not res_ids:
|
||||
res_ids = self.env.context.get("active_ids")
|
||||
mis_report_instance = self.env["mis.report.instance"].browse(res_ids)[0]
|
||||
# data=None, because it was there only to force Odoo
|
||||
# to propagate context
|
||||
return super(
|
||||
Report, self.with_context(landscape=mis_report_instance.landscape_pdf)
|
||||
)._render_qweb_pdf(report_ref, res_ids, data=None)
|
||||
return super()._render_qweb_pdf(report_ref, res_ids, data)
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="qweb_pdf_export" model="ir.actions.report">
|
||||
<field name="name">MIS report instance QWEB PDF report</field>
|
||||
<field name="model">mis.report.instance</field>
|
||||
<field name="type">ir.actions.report</field>
|
||||
<field name="report_name">mis_builder.report_mis_report_instance</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
</record>
|
||||
<!--
|
||||
TODO we use divs with css table layout, but this has drawbacks:
|
||||
(bad layout of first column, no colspan for first header row),
|
||||
consider getting back to a plain HTML table.
|
||||
-->
|
||||
<template id="report_mis_report_instance">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.internal_layout">
|
||||
<t t-set="matrix" t-value="o._compute_matrix()" />
|
||||
<t t-set="notes" t-value="o.get_notes_by_cell_id()" />
|
||||
<t t-set="style_obj" t-value="o.env['mis.report.style']" />
|
||||
<div class="page">
|
||||
<h3>
|
||||
<span t-field="o.name" />
|
||||
<span>-</span>
|
||||
<t t-foreach="o.query_company_ids" t-as="company">
|
||||
<span t-field="company.name" />
|
||||
<span t-if="company != o.query_company_ids[-1]">,</span>
|
||||
</t>
|
||||
</h3>
|
||||
<p>
|
||||
<div class="mis_report_filers">
|
||||
<t
|
||||
t-foreach="o.get_filter_descriptions()"
|
||||
t-as="filter_description"
|
||||
>
|
||||
<div>
|
||||
<span t-out="filter_description" />
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</p>
|
||||
<div class="mis_table">
|
||||
<div class="mis_thead">
|
||||
<div class="mis_row">
|
||||
<div class="mis_cell mis_collabel" />
|
||||
<t t-foreach="matrix.iter_cols()" t-as="col">
|
||||
<div class="mis_cell mis_collabel">
|
||||
<t t-out="col.label" />
|
||||
<t t-if="col.description">
|
||||
<br />
|
||||
<t t-out="col.description" />
|
||||
</t>
|
||||
</div>
|
||||
<!-- add empty cells because we have no colspan with css tables -->
|
||||
<t
|
||||
t-foreach="list(col.iter_subcols())[1:]"
|
||||
t-as="subcol"
|
||||
>
|
||||
<div class="mis_cell mis_collabel" />
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div class="mis_row">
|
||||
<div class="mis_cell mis_collabel" />
|
||||
<t t-foreach="matrix.iter_subcols()" t-as="subcol">
|
||||
<div class="mis_cell mis_collabel">
|
||||
<t t-out="subcol.label" />
|
||||
<t t-if="subcol.description">
|
||||
<br />
|
||||
<t t-out="subcol.description" />
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mis_tbody">
|
||||
<t t-foreach="matrix.iter_rows()" t-as="row">
|
||||
<div
|
||||
t-if="not ((row.style_props.hide_empty and row.is_empty()) or row.style_props.hide_always)"
|
||||
class="mis_row"
|
||||
>
|
||||
<div
|
||||
t-att-style="style_obj.to_css_style(row.style_props)"
|
||||
class="mis_cell mis_rowlabel"
|
||||
>
|
||||
<t t-out="row.label" />
|
||||
<t t-if="row.description">
|
||||
<br />
|
||||
<t t-out="row.description" />
|
||||
</t>
|
||||
</div>
|
||||
<t t-foreach="row.iter_cells()" t-as="cell">
|
||||
<div
|
||||
t-att-style="cell and style_obj.to_css_style(cell.style_props) or ''"
|
||||
class="mis_cell mis_amount"
|
||||
>
|
||||
<t
|
||||
t-out="cell and cell.val_rendered or ''"
|
||||
/>
|
||||
<span
|
||||
class="oe_mis_builder_footnote"
|
||||
t-if="cell"
|
||||
>
|
||||
<t
|
||||
t-out="notes.get(cell.cell_id,{}).get('sequence','')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Foot notes -->
|
||||
<div class="oe_mis_builder_footnote_div">
|
||||
<table class="oe_mis_builder_footnote_table">
|
||||
<t
|
||||
t-foreach="sorted(notes.values(),key=lambda r:r['sequence'])"
|
||||
t-as="note"
|
||||
>
|
||||
<tr>
|
||||
<td><t t-out="note['sequence']" />. </td>
|
||||
<td><t t-out="note['text']" /></td>
|
||||
</tr>
|
||||
</t>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
# Copyright 2014 ACSONE SA/NV (<http://acsone.eu>)
|
||||
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
import logging
|
||||
import numbers
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
from ..models.accounting_none import AccountingNone
|
||||
from ..models.data_error import DataError
|
||||
from ..models.mis_report_style import TYPE_STR
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
ROW_HEIGHT = 15 # xlsxwriter units
|
||||
COL_WIDTH = 0.9 # xlsxwriter units
|
||||
MIN_COL_WIDTH = 10 # characters
|
||||
MAX_COL_WIDTH = 50 # characters
|
||||
|
||||
|
||||
class MisBuilderXlsx(models.AbstractModel):
|
||||
_name = "report.mis_builder.mis_report_instance_xlsx"
|
||||
_description = "MIS Builder XLSX report"
|
||||
_inherit = "report.report_xlsx.abstract"
|
||||
|
||||
@api.model
|
||||
def _mis_builder_add_annotation(self, sheet, cell, row_pos, col_pos, notes):
|
||||
"""
|
||||
Add anotation as a comment on cell in .xls
|
||||
"""
|
||||
if cell and (annotation := notes.get(cell.cell_id, {}).get("text")):
|
||||
sheet.write_comment(row_pos, col_pos, annotation)
|
||||
|
||||
def generate_xlsx_report(self, workbook, data, objects):
|
||||
# get the computed result of the report
|
||||
matrix = objects._compute_matrix()
|
||||
notes = objects.get_notes_by_cell_id()
|
||||
style_obj = self.env["mis.report.style"]
|
||||
|
||||
# create worksheet
|
||||
report_name = "{} - {}".format(
|
||||
objects[0].name, ", ".join([a.name for a in objects[0].query_company_ids])
|
||||
)
|
||||
sheet = workbook.add_worksheet(report_name[:31])
|
||||
row_pos = 0
|
||||
col_pos = 0
|
||||
# width of the labels column
|
||||
label_col_width = MIN_COL_WIDTH
|
||||
# {col_pos: max width in characters}
|
||||
col_width = defaultdict(lambda: MIN_COL_WIDTH)
|
||||
|
||||
# document title
|
||||
bold = workbook.add_format({"bold": True})
|
||||
header_format = workbook.add_format(
|
||||
{"bold": True, "align": "center", "bg_color": "#F0EEEE"}
|
||||
)
|
||||
sheet.write(row_pos, 0, report_name, bold)
|
||||
row_pos += 2
|
||||
|
||||
# filters
|
||||
filter_descriptions = objects.get_filter_descriptions()
|
||||
if filter_descriptions:
|
||||
for filter_description in objects.get_filter_descriptions():
|
||||
sheet.write(row_pos, 0, filter_description)
|
||||
row_pos += 1
|
||||
row_pos += 1
|
||||
|
||||
# column headers
|
||||
sheet.write(row_pos, 0, "", header_format)
|
||||
col_pos = 1
|
||||
for col in matrix.iter_cols():
|
||||
label = col.label
|
||||
if col.description:
|
||||
label += "\n" + col.description
|
||||
sheet.set_row(row_pos, ROW_HEIGHT * 2)
|
||||
if col.colspan > 1:
|
||||
sheet.merge_range(
|
||||
row_pos,
|
||||
col_pos,
|
||||
row_pos,
|
||||
col_pos + col.colspan - 1,
|
||||
label,
|
||||
header_format,
|
||||
)
|
||||
else:
|
||||
sheet.write(row_pos, col_pos, label, header_format)
|
||||
col_width[col_pos] = max(
|
||||
col_width[col_pos], len(col.label or ""), len(col.description or "")
|
||||
)
|
||||
col_pos += col.colspan
|
||||
row_pos += 1
|
||||
|
||||
# sub column headers
|
||||
sheet.write(row_pos, 0, "", header_format)
|
||||
col_pos = 1
|
||||
for subcol in matrix.iter_subcols():
|
||||
label = subcol.label
|
||||
if subcol.description:
|
||||
label += "\n" + subcol.description
|
||||
sheet.set_row(row_pos, ROW_HEIGHT * 2)
|
||||
sheet.write(row_pos, col_pos, label, header_format)
|
||||
col_width[col_pos] = max(
|
||||
col_width[col_pos],
|
||||
len(subcol.label or ""),
|
||||
len(subcol.description or ""),
|
||||
)
|
||||
col_pos += 1
|
||||
row_pos += 1
|
||||
|
||||
# rows
|
||||
for row in matrix.iter_rows():
|
||||
if (
|
||||
row.style_props.hide_empty and row.is_empty()
|
||||
) or row.style_props.hide_always:
|
||||
continue
|
||||
row_xlsx_style = style_obj.to_xlsx_style(TYPE_STR, row.style_props)
|
||||
row_format = workbook.add_format(row_xlsx_style)
|
||||
col_pos = 0
|
||||
label = row.label
|
||||
if row.description:
|
||||
label += "\n" + row.description
|
||||
sheet.set_row(row_pos, ROW_HEIGHT * 2)
|
||||
sheet.write(row_pos, col_pos, label, row_format)
|
||||
label_col_width = max(
|
||||
label_col_width, len(row.label or ""), len(row.description or "")
|
||||
)
|
||||
for cell in row.iter_cells():
|
||||
col_pos += 1
|
||||
self._mis_builder_add_annotation(sheet, cell, row_pos, col_pos, notes)
|
||||
if not cell or cell.val is AccountingNone:
|
||||
# TODO col/subcol format
|
||||
sheet.write(row_pos, col_pos, "", row_format)
|
||||
continue
|
||||
cell_xlsx_style = style_obj.to_xlsx_style(
|
||||
cell.val_type, cell.style_props, no_indent=True
|
||||
)
|
||||
cell_xlsx_style["align"] = "right"
|
||||
cell_format = workbook.add_format(cell_xlsx_style)
|
||||
if isinstance(cell.val, DataError):
|
||||
val = cell.val.name
|
||||
# TODO display cell.val.msg as Excel comment?
|
||||
elif cell.val is None or cell.val is AccountingNone:
|
||||
val = ""
|
||||
else:
|
||||
divider = float(cell.style_props.get("divider", 1))
|
||||
if (
|
||||
divider != 1
|
||||
and isinstance(cell.val, numbers.Number)
|
||||
and not cell.val_type == "pct"
|
||||
):
|
||||
val = cell.val / divider
|
||||
else:
|
||||
val = cell.val
|
||||
sheet.write(row_pos, col_pos, val, cell_format)
|
||||
col_width[col_pos] = max(
|
||||
col_width[col_pos], len(cell.val_rendered or "")
|
||||
)
|
||||
row_pos += 1
|
||||
|
||||
# Add date/time footer
|
||||
row_pos += 1
|
||||
footer_format = workbook.add_format(
|
||||
{"italic": True, "font_color": "#202020", "font_size": 9}
|
||||
)
|
||||
lang_model = self.env["res.lang"]
|
||||
lang = lang_model._lang_get(self.env.user.lang)
|
||||
|
||||
now_tz = fields.Datetime.context_timestamp(
|
||||
self.env["res.users"], datetime.now()
|
||||
)
|
||||
create_date = _(
|
||||
"Generated on %(gen_date)s at %(gen_time)s",
|
||||
gen_date=now_tz.strftime(lang.date_format),
|
||||
gen_time=now_tz.strftime(lang.time_format),
|
||||
)
|
||||
sheet.write(row_pos, 0, create_date, footer_format)
|
||||
|
||||
# adjust col widths
|
||||
sheet.set_column(0, 0, min(label_col_width, MAX_COL_WIDTH) * COL_WIDTH)
|
||||
data_col_width = min(MAX_COL_WIDTH, max(col_width.values()))
|
||||
min_col_pos = min(col_width.keys())
|
||||
max_col_pos = max(col_width.keys())
|
||||
sheet.set_column(min_col_pos, max_col_pos, data_col_width * COL_WIDTH)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="xls_export" model="ir.actions.report">
|
||||
<field name="name">MIS report instance XLS report</field>
|
||||
<field name="model">mis.report.instance</field>
|
||||
<field name="type">ir.actions.report</field>
|
||||
<field name="report_name">mis_builder.mis_report_instance_xlsx</field>
|
||||
<field name="report_type">xlsx</field>
|
||||
<field name="report_file">mis_report_instance</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Loading…
Add table
Add a link
Reference in a new issue