mirror of
https://github.com/bringout/oca-report.git
synced 2026-04-20 07:22:04 +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,108 @@
|
|||
.o_web_client .mis_builder_amount {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.o_web_client .mis_builder_collabel {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.o_web_client .mis_builder_rowlabel {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.o_web_client .mis_builder a {
|
||||
/* we don't want the link color, to respect user styles */
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.o_web_client .mis_builder a:hover {
|
||||
/* underline links on hover to give a visual cue */
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.oe_mis_builder_content {
|
||||
}
|
||||
|
||||
.oe_mis_builder_report_wide_sheet {
|
||||
max-width: 95% !important;
|
||||
}
|
||||
|
||||
/* style for the control panel (search box and buttons) */
|
||||
|
||||
.oe_mis_builder_cp {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.oe_mis_builder_cp_left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.oe_mis_builder_cp_right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 2;
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
.oe_mis_builder_cp_right_top_right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.oe_mis_builder_cp_right_top {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.oe_mis_builder_cp_right_bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.oe_mis_builder_filter_buttons {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.oe_mis_builder_action_buttons {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.oe_mis_builder_dropdown {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.oe_mis_builder_footnote {
|
||||
font-size: 80%;
|
||||
color: red;
|
||||
position: relative;
|
||||
bottom: 1ex;
|
||||
width: 1em;
|
||||
display: inline-block;
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
.oe_mis_builder_footnote_table {
|
||||
list-style: none;
|
||||
white-space: pre-wrap;
|
||||
display: inline-block;
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_mis_builder_footnote_div {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.oe_mis_builder_menu_disabled {
|
||||
color: gainsboro;
|
||||
}
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import Dialog from "web.Dialog";
|
||||
import {Component, onMounted, onWillStart, useState, useSubEnv} from "@odoo/owl";
|
||||
import {DatePicker} from "@web/core/datepicker/datepicker";
|
||||
import {FilterMenu} from "@web/search/filter_menu/filter_menu";
|
||||
import {SearchBar} from "@web/search/search_bar/search_bar";
|
||||
import {SearchModel} from "@web/search/search_model";
|
||||
import {parseDate} from "@web/core/l10n/dates";
|
||||
import {qweb} from "web.core";
|
||||
import {registry} from "@web/core/registry";
|
||||
import {useBus, useService} from "@web/core/utils/hooks";
|
||||
|
||||
export class MisReportWidget extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.orm = useService("orm");
|
||||
this.user = useService("user");
|
||||
this.action = useService("action");
|
||||
this.view = useService("view");
|
||||
this.JSON = JSON;
|
||||
this.state = useState({
|
||||
mis_report_data: {header: [], body: [], notes: {}},
|
||||
pivot_date: null,
|
||||
can_edit_annotation: false,
|
||||
can_read_annotation: false,
|
||||
});
|
||||
this.searchModel = new SearchModel(this.env, {
|
||||
user: this.user,
|
||||
orm: this.orm,
|
||||
view: this.view,
|
||||
});
|
||||
useSubEnv({searchModel: this.searchModel});
|
||||
useBus(this.env.searchModel, "update", async () => {
|
||||
await this.env.searchModel.sectionsPromise;
|
||||
this.refresh();
|
||||
});
|
||||
onWillStart(this.willStart);
|
||||
|
||||
onMounted(this._onMounted);
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
async willStart() {
|
||||
const [result] = await this.orm.read(
|
||||
"mis.report.instance",
|
||||
[this._instanceId()],
|
||||
[
|
||||
"source_aml_model_name",
|
||||
"widget_show_filters",
|
||||
"widget_show_settings_button",
|
||||
"widget_search_view_id",
|
||||
"pivot_date",
|
||||
"widget_show_pivot_date",
|
||||
"user_can_read_annotation",
|
||||
"user_can_edit_annotation",
|
||||
"wide_display_by_default",
|
||||
],
|
||||
{context: this.context}
|
||||
);
|
||||
this.source_aml_model_name = result.source_aml_model_name;
|
||||
this.widget_show_filters = result.widget_show_filters;
|
||||
this.widget_show_settings_button = result.widget_show_settings_button;
|
||||
this.widget_search_view_id =
|
||||
result.widget_search_view_id && result.widget_search_view_id[0];
|
||||
this.state.pivot_date = parseDate(result.pivot_date);
|
||||
this.widget_show_pivot_date = result.widget_show_pivot_date;
|
||||
if (this.showSearchBar) {
|
||||
// Initialize the search model
|
||||
await this.searchModel.load({
|
||||
resModel: this.source_aml_model_name,
|
||||
searchViewId: this.widget_search_view_id,
|
||||
});
|
||||
}
|
||||
|
||||
this.wide_display = result.wide_display_by_default;
|
||||
|
||||
// Compute the report
|
||||
this.refresh();
|
||||
this.state.can_edit_annotation = result.user_can_edit_annotation;
|
||||
this.state.can_read_annotation = result.user_can_read_annotation;
|
||||
}
|
||||
|
||||
async _onMounted() {
|
||||
this.resize_sheet();
|
||||
}
|
||||
|
||||
get showSearchBar() {
|
||||
return (
|
||||
this.source_aml_model_name &&
|
||||
this.widget_show_filters &&
|
||||
this.widget_search_view_id
|
||||
);
|
||||
}
|
||||
|
||||
get showPivotDate() {
|
||||
return this.widget_show_pivot_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the mis.report.instance to which the widget is
|
||||
* bound.
|
||||
*
|
||||
* @returns int
|
||||
*/
|
||||
_instanceId() {
|
||||
if (this.props.value) {
|
||||
return this.props.value;
|
||||
}
|
||||
|
||||
/*
|
||||
* This trick is needed because in a dashboard the view does
|
||||
* not seem to be bound to an instance: it seems to be a limitation
|
||||
* of Odoo dashboards that are not designed to contain forms but
|
||||
* rather tree views or charts.
|
||||
*/
|
||||
var context = this.props.record.context;
|
||||
if (context.active_model === "mis.report.instance") {
|
||||
return context.active_id;
|
||||
}
|
||||
}
|
||||
|
||||
get context() {
|
||||
var ctx = super.context;
|
||||
if (this.showSearchBar) {
|
||||
ctx = {
|
||||
...ctx,
|
||||
mis_analytic_domain: this.searchModel.searchDomain,
|
||||
};
|
||||
}
|
||||
if (this.showPivotDate && this.state.pivot_date) {
|
||||
ctx = {
|
||||
...ctx,
|
||||
mis_pivot_date: this.state.pivot_date,
|
||||
};
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
async drilldown(event) {
|
||||
const drilldown = JSON.parse(event.target.dataset.drilldown);
|
||||
const action = await this.orm.call(
|
||||
"mis.report.instance",
|
||||
"drilldown",
|
||||
[this._instanceId(), drilldown],
|
||||
{context: this.context}
|
||||
);
|
||||
this.action.doAction(action);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
this.state.mis_report_data = await this.orm.call(
|
||||
"mis.report.instance",
|
||||
"compute",
|
||||
[this._instanceId()],
|
||||
{context: this.context}
|
||||
);
|
||||
}
|
||||
|
||||
async refresh_annotation() {
|
||||
this.state.mis_report_data.notes = await this.orm.call(
|
||||
"mis.report.instance",
|
||||
"get_notes_by_cell_id",
|
||||
[this._instanceId()],
|
||||
{context: this.context}
|
||||
);
|
||||
}
|
||||
|
||||
async printPdf() {
|
||||
const action = await this.orm.call(
|
||||
"mis.report.instance",
|
||||
"print_pdf",
|
||||
[this._instanceId()],
|
||||
{context: this.context}
|
||||
);
|
||||
this.action.doAction(action);
|
||||
}
|
||||
|
||||
async exportXls() {
|
||||
const action = await this.orm.call(
|
||||
"mis.report.instance",
|
||||
"export_xls",
|
||||
[this._instanceId()],
|
||||
{context: this.context}
|
||||
);
|
||||
this.action.doAction(action);
|
||||
}
|
||||
|
||||
async displaySettings() {
|
||||
const action = await this.orm.call(
|
||||
"mis.report.instance",
|
||||
"display_settings",
|
||||
[this._instanceId()],
|
||||
{context: this.context}
|
||||
);
|
||||
this.action.doAction(action);
|
||||
}
|
||||
|
||||
async _remove_annotation(cell_id) {
|
||||
await this.orm.call(
|
||||
"mis.report.instance.annotation",
|
||||
"remove_annotation",
|
||||
[cell_id, this._instanceId()],
|
||||
{context: this.context}
|
||||
);
|
||||
this.refresh_annotation();
|
||||
}
|
||||
|
||||
async _save_annotation(cell_id) {
|
||||
const text = document.querySelector(".o_mis_builder_annotation_text").value;
|
||||
await this.orm.call(
|
||||
"mis.report.instance.annotation",
|
||||
"set_annotation",
|
||||
[cell_id, this._instanceId(), text],
|
||||
{context: this.context}
|
||||
);
|
||||
await this.refresh_annotation();
|
||||
}
|
||||
|
||||
async annotate(event) {
|
||||
const cell_id = event.target.dataset.cellId;
|
||||
const note = this.state.mis_report_data.notes[cell_id];
|
||||
const note_text = (note && note.text) || "";
|
||||
var buttons = [
|
||||
{
|
||||
text: this.env._t("Save"),
|
||||
classes: "btn-primary",
|
||||
close: true,
|
||||
click: this._save_annotation.bind(this, cell_id),
|
||||
},
|
||||
{
|
||||
text: this.env._t("Cancel"),
|
||||
close: true,
|
||||
},
|
||||
];
|
||||
if (typeof note !== "undefined") {
|
||||
buttons.push({
|
||||
text: this.env._t("Remove"),
|
||||
classes: "btn-secondary",
|
||||
close: true,
|
||||
click: this._remove_annotation.bind(this, cell_id),
|
||||
});
|
||||
}
|
||||
|
||||
new Dialog(this, {
|
||||
title: "Annotate",
|
||||
size: "medium",
|
||||
$content: $(
|
||||
qweb.render("mis_builder.annotation_dialog", {
|
||||
text: note_text,
|
||||
})
|
||||
),
|
||||
buttons: buttons,
|
||||
}).open();
|
||||
}
|
||||
|
||||
async remove_annotation(event) {
|
||||
const cell_id = event.target.dataset.cellId;
|
||||
this._remove_annotation(cell_id);
|
||||
}
|
||||
|
||||
onDateTimeChanged(ev) {
|
||||
this.state.pivot_date = ev;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
async toggle_wide_display() {
|
||||
this.wide_display = !this.wide_display;
|
||||
this.resize_sheet();
|
||||
}
|
||||
|
||||
async resize_sheet() {
|
||||
var sheet_element = document.getElementsByClassName("o_form_sheet")[0];
|
||||
sheet_element.classList.toggle(
|
||||
"oe_mis_builder_report_wide_sheet",
|
||||
this.wide_display
|
||||
);
|
||||
var button_resize_element = document.getElementById("icon_resize");
|
||||
button_resize_element.classList.toggle("fa-expand", !this.wide_display);
|
||||
button_resize_element.classList.toggle("fa-compress", this.wide_display);
|
||||
}
|
||||
}
|
||||
|
||||
MisReportWidget.components = {FilterMenu, SearchBar, DatePicker};
|
||||
MisReportWidget.template = "mis_builder.MisReportWidget";
|
||||
|
||||
registry.category("fields").add("mis_report_widget", MisReportWidget);
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<templates>
|
||||
|
||||
<t t-name="mis_builder.MisReportWidget" owl="1">
|
||||
<div class="oe_mis_builder_content">
|
||||
<t t-if="state.mis_report_data">
|
||||
<t t-set="notes" t-value="state.mis_report_data.notes" />
|
||||
<div class="oe_mis_builder_cp">
|
||||
<div class="oe_mis_builder_cp_left">
|
||||
</div>
|
||||
<div class="oe_mis_builder_cp_right">
|
||||
<div class="oe_mis_builder_cp_right_top_right">
|
||||
<div class="oe_mis_builder_action_buttons">
|
||||
<button
|
||||
t-on-click="toggle_wide_display"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
<i id="icon_resize" class="fa" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_mis_builder_cp_right_top">
|
||||
<SearchBar t-if="showSearchBar" />
|
||||
</div>
|
||||
<div class="oe_mis_builder_cp_right_bottom">
|
||||
<div class="oe_mis_builder_filter_buttons">
|
||||
<FilterMenu t-if="showSearchBar" />
|
||||
<DatePicker
|
||||
date="state.pivot_date"
|
||||
onDateTimeChanged="onDateTimeChanged.bind(this)"
|
||||
placeholder="'Base date...'"
|
||||
t-if="showPivotDate"
|
||||
/>
|
||||
</div>
|
||||
<div class="oe_mis_builder_action_buttons">
|
||||
<button t-on-click="refresh" class="btn">
|
||||
<span class="fa fa-refresh" /> Refresh </button>
|
||||
<button t-on-click="printPdf" class="btn">
|
||||
<span class="fa fa-print" /> Print </button>
|
||||
<button t-on-click="exportXls" class="btn">
|
||||
<span class="fa fa-download" /> Export </button>
|
||||
<button
|
||||
t-on-click="displaySettings"
|
||||
t-if="widget_show_settings_button"
|
||||
class="btn"
|
||||
>
|
||||
<span class="fa fa-cog" /> Settings </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_list_renderer o_renderer table-responsive">
|
||||
<table
|
||||
class="o_list_table table table-sm table-hover table-striped mis_builder"
|
||||
>
|
||||
<thead>
|
||||
<tr
|
||||
t-foreach="state.mis_report_data.header"
|
||||
t-as="row"
|
||||
t-key="row_index"
|
||||
class="oe_list_header_columns"
|
||||
>
|
||||
<th class="oe_list_header_char">
|
||||
|
||||
</th>
|
||||
<th
|
||||
t-foreach="row.cols"
|
||||
t-as="col"
|
||||
t-key="col_index"
|
||||
class="oe_list_header_char mis_builder_collabel"
|
||||
t-att-colspan="col.colspan"
|
||||
>
|
||||
<t t-esc="col.label" />
|
||||
<t t-if="col.description">
|
||||
<br />
|
||||
<t t-esc="col.description" />
|
||||
</t>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
t-foreach="state.mis_report_data.body"
|
||||
t-as="row"
|
||||
t-key="row_index"
|
||||
>
|
||||
<td t-att="{'style': row.style}">
|
||||
<t t-esc="row.label" />
|
||||
<t t-if="row.description">
|
||||
<br />
|
||||
<t t-esc="row.description" />
|
||||
</t>
|
||||
</td>
|
||||
<td
|
||||
t-foreach="row.cells"
|
||||
t-as="cell"
|
||||
t-key="cell_index"
|
||||
t-att="{'style': cell.style, 'title': cell.val_c}"
|
||||
class="mis_builder_amount oe_mis_builder_dropdown"
|
||||
>
|
||||
<div>
|
||||
<t t-if="cell.drilldown_arg">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="mis_builder_drilldown"
|
||||
t-on-click="drilldown"
|
||||
t-att-data-drilldown="JSON.stringify(cell.drilldown_arg)"
|
||||
>
|
||||
<t t-esc="cell.val_r" />
|
||||
</a>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="cell.val_r" />
|
||||
</t>
|
||||
<span class="oe_mis_builder_footnote">
|
||||
<div t-if="notes[cell.cell_id]">
|
||||
<a
|
||||
t-att-id="'note_'+notes[cell.cell_id].sequence"
|
||||
t-out="notes[cell.cell_id] and notes[cell.cell_id].sequence"
|
||||
t-att="{'title': notes[cell.cell_id].text}"
|
||||
href="#footnotes"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<div id="dropdown_menu" class="btn-group">
|
||||
<div
|
||||
class="dropdown"
|
||||
t-if="state.can_edit_annotation and cell.can_be_annotated"
|
||||
>
|
||||
<div
|
||||
data-bs-toggle="dropdown"
|
||||
t-attf-class="dropdown-toggle"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="dropdown-menu o_filter_menu"
|
||||
role="menu"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
t-on-click="annotate"
|
||||
t-att-data-cell-id="cell.cell_id"
|
||||
role="menuitem"
|
||||
class="dropdown-item js_tag"
|
||||
>
|
||||
Annotate
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- show menu as disabled -->
|
||||
<div
|
||||
t-else=""
|
||||
class="dropdown-toggle oe_mis_builder_menu_disabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr />
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Adding notes -->
|
||||
<div class="oe_mis_builder_footnote_div" id="footnotes">
|
||||
<table class="oe_mis_builder_footnote_table">
|
||||
<t
|
||||
t-foreach="state.mis_report_data.notes"
|
||||
t-as="cell_id"
|
||||
t-key="cell_id"
|
||||
>
|
||||
<tr>
|
||||
<td><a
|
||||
t-out="notes[cell_id].sequence"
|
||||
t-att-href="'#note_'+notes[cell_id].sequence"
|
||||
/>. </td>
|
||||
<td><t t-out="notes[cell_id].text" /></td>
|
||||
<td><i
|
||||
href="javascript:void(0)"
|
||||
t-on-click="remove_annotation"
|
||||
t-att-data-cell-id="cell_id"
|
||||
class="btn fa fa-trash-o"
|
||||
t-if="state.can_edit_annotation"
|
||||
/></td>
|
||||
</tr>
|
||||
</t>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="mis_builder.annotation_dialog">
|
||||
<form role="form">
|
||||
<textarea
|
||||
class="o_mis_builder_annotation_text"
|
||||
name="note"
|
||||
rows='4'
|
||||
placeholder="Insert note here"
|
||||
><t t-out="text" t-att-data-textnote="text" /></textarea>
|
||||
</form>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
.mis_table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.mis_row {
|
||||
display: table-row;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.mis_cell {
|
||||
display: table-cell;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.mis_thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
.mis_tbody {
|
||||
display: table-row-group;
|
||||
}
|
||||
.mis_table,
|
||||
.mis_table .mis_row {
|
||||
border-left: 0px;
|
||||
border-right: 0px;
|
||||
text-align: left;
|
||||
padding-right: 3px;
|
||||
padding-left: 3px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.mis_table .mis_row {
|
||||
border-color: grey;
|
||||
border-bottom: 1px solid lightGrey;
|
||||
}
|
||||
.mis_table .mis_cell.mis_collabel {
|
||||
font-weight: bold;
|
||||
background-color: #f0f0f0;
|
||||
text-align: center;
|
||||
}
|
||||
.mis_table .mis_cell.mis_rowlabel {
|
||||
text-align: left;
|
||||
/*white-space: nowrap;*/
|
||||
}
|
||||
.mis_table .mis_cell.mis_amount {
|
||||
text-align: right;
|
||||
}
|
||||
.oe_mis_builder_footnote {
|
||||
font-size: 70%;
|
||||
color: red;
|
||||
position: relative;
|
||||
bottom: 1ex;
|
||||
width: 1em;
|
||||
display: inline-block;
|
||||
padding-right: 1px;
|
||||
}
|
||||
.oe_mis_builder_footnote_div {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.oe_mis_builder_footnote_table {
|
||||
list-style: none;
|
||||
white-space: pre-wrap;
|
||||
display: inline-block;
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue