418 lines
17 KiB
XML
Executable File
418 lines
17 KiB
XML
Executable File
<odoo>
|
|
<record id="paperformat_brm_landscape" model="report.paperformat">
|
|
<field name="name">BRM Landscape Format</field>
|
|
<field name="default" eval="False"/>
|
|
<field name="format">A4</field>
|
|
<field name="orientation">Landscape</field>
|
|
<field name="margin_top">10</field>
|
|
<field name="margin_bottom">10</field>
|
|
<field name="margin_left">10</field>
|
|
<field name="margin_right">10</field>
|
|
|
|
</record>
|
|
|
|
<!-- Updated report with paperformat reference -->
|
|
<record id="action_brm_summary_report" model="ir.actions.report">
|
|
<field name="name">To-Do Reports</field>
|
|
<field name="model">sos_brm_action</field>
|
|
<field name="report_type">qweb-html</field> <!-- Changed to qweb-pdf for printing -->
|
|
<field name="report_name">sos_brm.report_brm_action_plan_summary</field>
|
|
<field name="print_report_name">BRM_Plan_Summary</field>
|
|
<field name="paperformat_id" ref="paperformat_brm_landscape"/>
|
|
</record>
|
|
|
|
<template id="report_brm_action_plan_summary">
|
|
<t t-call="web.basic_layout">
|
|
<t t-call="web.html_container">
|
|
|
|
<!-- Optional: keep your global CSS -->
|
|
<link rel="stylesheet" href="/sos_inventory/static/src/css/style.css?v=7"/>
|
|
|
|
<!-- Polishing CSS just for this report -->
|
|
<style>
|
|
.subrow td { background:#fff; padding:0; }
|
|
.subtable { width:100%; border-collapse:collapse; font-size:12px; margin:4px 0 10px; }
|
|
.subtable thead th { background:#f2f3f7; border-bottom:1px solid #e2e4ea; padding:6px; text-align:left; }
|
|
.subtable td { padding:6px; border-bottom:1px solid #eee; }
|
|
.subtable tr, .subtable { page-break-inside: avoid; }
|
|
.status-pill { border-radius:999px; padding:2px 8px; font-size:12px; border:1px solid #ddd; display:inline-block; background-color: #e1375e;
|
|
color: #fff;
|
|
font-weight: bold; }
|
|
|
|
.page{
|
|
|
|
margin-top: 10px;
|
|
}
|
|
:root {
|
|
--ink: #1d1b23;
|
|
--muted: #6f6a7d;
|
|
--edge: #ece8ff;
|
|
--chip: #f4f1ff;
|
|
--badge-open: #fbd5ad;
|
|
--badge-open-text: #7a4b00;
|
|
--badge-close: #e9f7ec;
|
|
--badge-close-text: #1e6f3b;
|
|
--badge-hold: #f2f2f2;
|
|
--badge-hold-text: #606060;
|
|
--overdue: #b00020;
|
|
--soon: #9a6700;
|
|
--ok: #2e7d32;
|
|
--head: #eae6ff;
|
|
--hover: #f8f7ff;
|
|
}
|
|
.hdr { display:flex; justify-content:space-between; align-items:flex-start; margin:2px 0 10px; }
|
|
.title { margin:0; font-size:18px; color:var(--ink); }
|
|
.chips { display:flex; flex-wrap:wrap; gap:8px; margin-top:6px; }
|
|
.chip { background:var(--chip); border:1px solid var(--edge); border-radius:999px; padding:4px 10px; font-size:12px; }
|
|
.summary-row{width:100%;border-collapse:separate;border-spacing:10px 0;margin:8px 0 12px;}
|
|
.summary-row td{box-shadow: rgba(0, 0, 0, 0.12) 0px 2px 6px;width:33.33%;vertical-align:top;padding:10px 12px;border:1px solid var(--edge);background:#fff;border-radius:10px;}
|
|
.summary-row .lab{color:var(--muted);font-size:11px;}
|
|
.summary-row .val{font-weight:600;font-size:16px;}
|
|
@media print {.summary-row{border-spacing:8px 0;}}
|
|
|
|
.table_custom { width:100%; border-collapse:collapse; font-size:13px; }
|
|
.table_custom thead th { background:var(--head); border-bottom:1px solid #cfc8ff; padding:8px; text-align:left; }
|
|
.table_custom td { padding:8px; overflow-wrap: anywhere; }
|
|
.table_custom tbody tr:hover { background:var(--hover); }
|
|
.nowrap { white-space:nowrap; }
|
|
|
|
.badge { border-radius:999px; padding:2px 8px; font-size:12px; border:1px solid transparent; display:inline-block; }
|
|
.b-open { background:var(--badge-open); color:var(--badge-open-text); border-color:#ffe6a8; }
|
|
.b-close { background:var(--badge-close); color:var(--badge-close-text); border-color:#bfe8c8; }
|
|
.b-hold { background:var(--badge-hold); color:var(--badge-hold-text); border-color:#dedede; }
|
|
|
|
.date-ok { color:var(--ok); font-weight:600; }
|
|
.date-soon { color:var(--soon); font-weight:600; }
|
|
.date-overdue { color:var(--overdue); font-weight:700; }
|
|
|
|
.btn-link { border:1px solid #6a5acd; color:#4b3dbb; padding:4px 10px; border-radius:999px; text-decoration:none; font-size:12px; }
|
|
.btn-link:hover { background:#efeaff; }
|
|
|
|
/* keep rows together on PDF */
|
|
.table_custom tr { page-break-inside: avoid; }
|
|
h4 { margin:14px 0 6px; }
|
|
.section-head { display:flex; align-items:center; gap:10px; }
|
|
.pill { background: #202022;
|
|
border: 1px solid var(--edge);
|
|
border-radius: 999px;
|
|
padding: 2px 8px;
|
|
font-size: 12px;
|
|
color: #ffffff;}
|
|
</style>
|
|
|
|
<div class="page">
|
|
<!-- Today helper (remove if you pass 'today' from Python) -->
|
|
<t t-set="today" t-value="datetime.date.today()"/>
|
|
|
|
<!-- Header -->
|
|
<div class="hdr">
|
|
<h3 class="title">To-Do Summary</h3>
|
|
</div>
|
|
<div style="font-size:12px; margin-top:4px;">
|
|
<strong>Responsible:</strong>
|
|
<t t-esc="done_by.name or '—'"/>
|
|
 | 
|
|
<strong>Status:</strong> <t t-esc="status"/>
|
|
<t t-if="department">
|
|
 | 
|
|
<strong>Department:</strong> <t t-esc="department"/>
|
|
</t>
|
|
</div>
|
|
|
|
<!-- KPIs -->
|
|
<table class="summary-row">
|
|
<tr>
|
|
<t t-if="cross_dept_action != 'cross_dept'">
|
|
<td>
|
|
<div class="lab">Intra-Dept Actions</div>
|
|
<div class="val"><t t-esc="count_local"/></div>
|
|
</td>
|
|
</t>
|
|
<t t-if="cross_dept_action != 'inter_dept'">
|
|
<td>
|
|
<div class="lab">Cross-Dept Actions</div>
|
|
<div class="val"><t t-esc="count_cross"/></div>
|
|
</td>
|
|
</t>
|
|
<t t-if="cross_dept_action == 'all'">
|
|
<td>
|
|
<div class="lab">Total</div>
|
|
<div class="val"><t t-esc="count_local + count_cross"/></div>
|
|
</td>
|
|
</t>
|
|
</tr>
|
|
</table>
|
|
|
|
|
|
|
|
<!-- INTRA-DEPT -->
|
|
<t t-if="cross_dept_action != 'cross_dept'">
|
|
<div class="section-head">
|
|
<h4>Intra-Department Actions</h4>
|
|
<span class="pill">Total: <t t-esc="count_local"/></span>
|
|
</div>
|
|
|
|
<table class="table_custom">
|
|
<thead>
|
|
<tr>
|
|
<th>Action Point</th>
|
|
<th>Department</th>
|
|
<th>Assigned By</th>
|
|
<th>Assigned To</th>
|
|
<th class="nowrap" style="text-align:center;">Start Date</th>
|
|
<th class="nowrap" style="text-align:center;">Target Date</th>
|
|
<th class="nowrap" style="text-align:center;">Actual End Date</th>
|
|
<!-- <th class="nowrap" style="text-align:center;">Revised Target Date</th> -->
|
|
<!-- <th>Remarks</th> -->
|
|
<!-- NEW child columns -->
|
|
<th>Action Plan</th>
|
|
<th>Revised Target Date</th>
|
|
<th>Result</th>
|
|
<th style="text-align:center;">Status</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<t t-if="local and len(local)">
|
|
<t t-foreach="local" t-as="rec">
|
|
<t t-set="is_closed" t-value="rec.status == 'close'"/>
|
|
<t t-set="is_overdue" t-value="rec.target_date and not is_closed and rec.target_date < today"/>
|
|
<t t-set="is_soon" t-value="rec.target_date and not is_closed and not is_overdue and (rec.target_date - today).days <= 7"/>
|
|
|
|
<!-- fetch child lines and rows to span -->
|
|
<t t-set="lines" t-value="lines_map.get(rec.id, [])"/>
|
|
<t t-set="rows" t-value="max(1, len(lines))"/>
|
|
|
|
<!-- first row carries all parent cells (rowspanned) + first child (or dashes) -->
|
|
<tr>
|
|
<td t-att-rowspan="rows">
|
|
<a t-att-href="'/web#id=%d&model=sos_brm_action&view_type=form' % rec.id" target="_blank">
|
|
<t t-esc="rec.name or '—'"/>
|
|
</a>
|
|
</td>
|
|
<td t-att-rowspan="rows"><t t-esc="rec.department.name or '—'"/></td>
|
|
<td t-att-rowspan="rows">
|
|
|
|
<div style="display:flex; align-items:center;">
|
|
<!-- User Image -->
|
|
<img t-att-src="'/web/image/%s/%s/image_1920' % (rec.assigned_by._name, rec.assigned_by.id)"
|
|
style="width:24px; height:24px; border-radius:50%; margin-right:5px;"/>
|
|
|
|
<!-- User Name -->
|
|
<t t-esc="rec.assigned_by.name or '—'"/>
|
|
</div></td>
|
|
<td t-att-rowspan="rows">
|
|
<div style="display:flex; align-items:center;">
|
|
<!-- User Image -->
|
|
<img t-att-src="'/web/image/%s/%s/image_1920' % (rec.responsible_person._name, rec.responsible_person.id)"
|
|
style="width:24px; height:24px; border-radius:50%; margin-right:5px;"/>
|
|
|
|
<!-- User Name -->
|
|
<t t-esc="rec.responsible_person.name or '—'"/>
|
|
</div>
|
|
</td>
|
|
|
|
<td class="nowrap" style="text-align:center;" t-att-rowspan="rows">
|
|
<t t-esc="rec.start_date and rec.start_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;" t-att-rowspan="rows"
|
|
t-attf-class="#{is_overdue and 'date-overdue' or (is_soon and 'date-soon' or 'date-ok')}">
|
|
<t t-esc="rec.target_date and rec.target_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;" t-att-rowspan="rows">
|
|
<t t-esc="rec.end_date and rec.end_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<!-- <td class="nowrap" style="text-align:center;" t-att-rowspan="rows">
|
|
<t t-esc="rec.latest_target_date and rec.latest_target_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td> -->
|
|
<!-- <td style="text-align:center;" t-att-rowspan="rows">
|
|
<t t-set="cls" t-value="rec.status == 'open' and 'b-open' or (rec.status == 'close' and 'b-close' or 'b-hold')"/>
|
|
<span class="badge" t-attf-class="badge #{cls}">
|
|
<t t-esc="dict(rec._fields['status'].selection).get(rec.status, rec.status)"/>
|
|
</span>
|
|
</td> -->
|
|
<!-- <td t-att-rowspan="rows"><t t-esc="rec.result or ''"/></td> -->
|
|
|
|
<!-- child plan + status (first child or dashes) -->
|
|
<td>
|
|
<t t-esc="(len(lines) and (lines[0].name or '—')) or '—'"/>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;">
|
|
<t t-esc="(len(lines) and (lines[0].target_date and lines[0].target_date.strftime('%d-%m-%Y') or '—')) or '—'"/>
|
|
</td>
|
|
<td>
|
|
<t t-esc="(len(lines) and (lines[0].result or '—')) or '—'"/>
|
|
</td>
|
|
<td style="text-align:center;">
|
|
<t t-if="len(lines)">
|
|
<span class="status-pill"
|
|
t-att-style="'background-color:#2e7d32; border-color:#bfe8c8;' if lines[0].status == 'close' else ''">
|
|
<t t-esc="dict(lines[0]._fields['status'].selection).get(lines[0].status, lines[0].status)"/>
|
|
</span>
|
|
</t>
|
|
<t t-else="">—</t>
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
<!-- any remaining child lines as extra rows (only the FOUR child cols) -->
|
|
<t t-if="len(lines) > 1">
|
|
<t t-foreach="lines[1:]" t-as="ln">
|
|
<tr>
|
|
<td><t t-esc="ln.name or '—'"/></td>
|
|
<td class="nowrap" style="text-align:center;">
|
|
<t t-esc="ln.target_date and ln.target_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<td><t t-esc="ln.result or '—'"/></td>
|
|
<td style="text-align:center;">
|
|
<span class="status-pill"
|
|
t-att-style="'background-color:#2e7d32; border-color:#bfe8c8;' if ln.status == 'close' else 'background-color:#e1375e;'">
|
|
<t t-esc="dict(ln._fields['status'].selection).get(ln.status, ln.status)"/>
|
|
</span>
|
|
</td>
|
|
|
|
</tr>
|
|
</t>
|
|
</t>
|
|
</t>
|
|
</t>
|
|
<t t-if="not local or len(local) == 0">
|
|
<!-- total columns = 8 -->
|
|
<tr><td colspan="11" style="text-align:center; color:#666; padding:12px;">No within-department actions.</td></tr>
|
|
</t>
|
|
</tbody>
|
|
</table>
|
|
</t>
|
|
|
|
<div style="height:16px;"></div>
|
|
<t t-if="cross_dept_action != 'inter_dept'">
|
|
|
|
<!-- CROSS-DEPT -->
|
|
<div class="section-head">
|
|
<h4>Cross-Department Actions</h4>
|
|
<span class="pill">Total: <t t-esc="count_cross"/></span>
|
|
</div>
|
|
|
|
<table class="table_custom">
|
|
<thead>
|
|
<tr>
|
|
<th>Action Point</th>
|
|
<th>Assigned By Dept</th>
|
|
<th>Assigned To Dept</th>
|
|
<th>Assigned By</th>
|
|
<th>Assigned To</th>
|
|
<th class="nowrap" style="text-align:center;">Start Date</th>
|
|
<th class="nowrap" style="text-align:center;">Target Date</th>
|
|
<th class="nowrap" style="text-align:center;">Actual End Date</th>
|
|
|
|
|
|
<!-- NEW child columns -->
|
|
<th>Action Plan</th>
|
|
<th>Revised Target Date</th>
|
|
<th>Result</th>
|
|
<th style="text-align:center;">Status</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
<t t-if="cross and len(cross)">
|
|
<t t-foreach="cross" t-as="rec">
|
|
<t t-set="is_closed" t-value="rec.status == 'close'"/>
|
|
<t t-set="is_overdue" t-value="rec.target_date and not is_closed and rec.target_date < today"/>
|
|
<t t-set="is_soon" t-value="rec.target_date and not is_closed and not is_overdue and (rec.target_date - today).days <= 7"/>
|
|
|
|
<t t-set="lines" t-value="lines_map.get(rec.id, [])"/>
|
|
<t t-set="rows" t-value="max(1, len(lines))"/>
|
|
|
|
<tr>
|
|
<td t-att-rowspan="rows">
|
|
<a t-att-href="'/web#id=%d&model=sos_brm_action&view_type=form' % rec.id" target="_blank">
|
|
<t t-esc="rec.name or '—'"/>
|
|
</a>
|
|
</td>
|
|
<td t-att-rowspan="rows"><t t-esc="rec.assigned_from_dept.name or '—'"/></td>
|
|
<td t-att-rowspan="rows"><t t-esc="rec.assigned_to_dept.name or '—'"/></td>
|
|
<td t-att-rowspan="rows"><div style="display:flex; align-items:center;">
|
|
<!-- User Image -->
|
|
<img t-att-src="'/web/image/%s/%s/image_1920' % (rec.assigned_by._name, rec.assigned_by.id)"
|
|
style="width:24px; height:24px; border-radius:50%; margin-right:5px;"/>
|
|
|
|
<!-- User Name -->
|
|
<t t-esc="rec.assigned_by.name or '—'"/>
|
|
</div></td>
|
|
<td t-att-rowspan="rows">
|
|
<div style="display:flex; align-items:center;">
|
|
<!-- User Image -->
|
|
<img t-att-src="'/web/image/%s/%s/image_1920' % (rec.responsible_person._name, rec.responsible_person.id)"
|
|
style="width:24px; height:24px; border-radius:50%; margin-right:5px;"/>
|
|
|
|
<!-- User Name -->
|
|
<t t-esc="rec.responsible_person.name or '—'"/>
|
|
</div>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;" t-att-rowspan="rows">
|
|
<t t-esc="rec.start_date and rec.start_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;" t-att-rowspan="rows"
|
|
t-attf-class="#{is_overdue and 'date-overdue' or (is_soon and 'date-soon' or 'date-ok')}">
|
|
<t t-esc="rec.target_date and rec.target_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;" t-att-rowspan="rows">
|
|
<t t-esc="rec.end_date and rec.end_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
|
|
<!-- child plan + status (first child or dashes) -->
|
|
<td>
|
|
<t t-esc="(len(lines) and (lines[0].name or '—')) or '—'"/>
|
|
</td>
|
|
<td class="nowrap" style="text-align:center;">
|
|
<t t-esc="(len(lines) and (lines[0].target_date and lines[0].target_date.strftime('%d-%m-%Y') or '—')) or '—'"/>
|
|
</td>
|
|
<td>
|
|
<t t-esc="(len(lines) and (lines[0].result or '—')) or '—'"/>
|
|
</td>
|
|
<td style="text-align:center;">
|
|
<t t-if="len(lines)">
|
|
<span class="status-pill"
|
|
t-att-style="'background-color:#2e7d32; border-color:#bfe8c8;' if lines[0].status == 'close' else ''">
|
|
<t t-esc="dict(lines[0]._fields['status'].selection).get(lines[0].status, lines[0].status)"/>
|
|
</span>
|
|
</t>
|
|
<t t-else="">—</t>
|
|
</td>
|
|
</tr>
|
|
|
|
<!-- extra child rows: ONLY the four child columns -->
|
|
<t t-if="len(lines) > 1">
|
|
<t t-foreach="lines[1:]" t-as="ln">
|
|
<tr>
|
|
<td><t t-esc="ln.name or '—'"/></td>
|
|
<td class="nowrap" style="text-align:center;">
|
|
<t t-esc="ln.target_date and ln.target_date.strftime('%d-%m-%Y') or '—'"/>
|
|
</td>
|
|
<td><t t-esc="ln.result or '—'"/></td>
|
|
<td style="text-align:center;">
|
|
<span class="status-pill"
|
|
t-att-style="'background-color:#2e7d32; border-color:#bfe8c8;' if ln.status == 'close' else 'background-color:#e1375e;'">
|
|
<t t-esc="dict(ln._fields['status'].selection).get(ln.status, ln.status)"/>
|
|
</span>
|
|
</td>
|
|
|
|
</tr>
|
|
</t>
|
|
</t>
|
|
</t>
|
|
</t>
|
|
<t t-if="not cross or len(cross) == 0">
|
|
<!-- total columns = 12 -->
|
|
<tr><td colspan="9" style="text-align:center; color:#666; padding:12px;">No cross-department actions.</td></tr>
|
|
</t>
|
|
</tbody>
|
|
</table>
|
|
</t>
|
|
</div>
|
|
</t>
|
|
</t>
|
|
</template>
|
|
</odoo>
|