Reference #14
This commit is contained in:
parent
574bec5c2b
commit
ebdc1d3ca1
|
|
@ -0,0 +1,6 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
|
from . import wizard
|
||||||
|
from . import report
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "SOS BRM",
|
||||||
|
|
||||||
|
'summary': "Business Review",
|
||||||
|
|
||||||
|
'description': """
|
||||||
|
Long description of module's purpose
|
||||||
|
""",
|
||||||
|
|
||||||
|
'author': "Deena",
|
||||||
|
'website': "https://sosaley.com",
|
||||||
|
|
||||||
|
'category': 'Inventory',
|
||||||
|
'version': '17.0.1.0.0',
|
||||||
|
'depends': ['base','web','mail','sos_inventory','sos_sales','sos_qo_aod'],
|
||||||
|
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'security/record_rules.xml',
|
||||||
|
'views/menu.xml',
|
||||||
|
'views/sos_brm_action_view.xml',
|
||||||
|
'wizard/sos_brm_report_wizard_view.xml',
|
||||||
|
'wizard/sos_cross_dept_report_wizard_view.xml',
|
||||||
|
'report/sos_brm_report_result.xml',
|
||||||
|
'report/sos_cross_dept_report_result.xml'
|
||||||
|
],
|
||||||
|
'demo': [
|
||||||
|
'demo/demo.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'license': 'LGPL-3'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import controllers
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# from odoo import http
|
||||||
|
|
||||||
|
|
||||||
|
# class SosBrm(http.Controller):
|
||||||
|
# @http.route('/sos_brm/sos_brm', auth='public')
|
||||||
|
# def index(self, **kw):
|
||||||
|
# return "Hello, world"
|
||||||
|
|
||||||
|
# @http.route('/sos_brm/sos_brm/objects', auth='public')
|
||||||
|
# def list(self, **kw):
|
||||||
|
# return http.request.render('sos_brm.listing', {
|
||||||
|
# 'root': '/sos_brm/sos_brm',
|
||||||
|
# 'objects': http.request.env['sos_brm.sos_brm'].search([]),
|
||||||
|
# })
|
||||||
|
|
||||||
|
# @http.route('/sos_brm/sos_brm/objects/<model("sos_brm.sos_brm"):obj>', auth='public')
|
||||||
|
# def object(self, obj, **kw):
|
||||||
|
# return http.request.render('sos_brm.object', {
|
||||||
|
# 'object': obj
|
||||||
|
# })
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<!--
|
||||||
|
<record id="object0" model="sos_brm.sos_brm">
|
||||||
|
<field name="name">Object 0</field>
|
||||||
|
<field name="value">0</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object1" model="sos_brm.sos_brm">
|
||||||
|
<field name="name">Object 1</field>
|
||||||
|
<field name="value">10</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object2" model="sos_brm.sos_brm">
|
||||||
|
<field name="name">Object 2</field>
|
||||||
|
<field name="value">20</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object3" model="sos_brm.sos_brm">
|
||||||
|
<field name="name">Object 3</field>
|
||||||
|
<field name="value">30</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="object4" model="sos_brm.sos_brm">
|
||||||
|
<field name="name">Object 4</field>
|
||||||
|
<field name="value">40</field>
|
||||||
|
</record>
|
||||||
|
-->
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import sos_brm_action
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class sos_brm_action(models.Model):
|
||||||
|
_name = 'sos_brm_action'
|
||||||
|
_description = 'Business Review'
|
||||||
|
|
||||||
|
todo_id = fields.Char(string="To-Do ID", readonly= True)
|
||||||
|
name = fields.Char(string="Action Point")
|
||||||
|
priority = fields.Selection([
|
||||||
|
('0', '🟢 Low'),
|
||||||
|
('1', '🟡 Medium'),
|
||||||
|
('2', '🔴 High'),
|
||||||
|
('3', '🚨 Urgent')
|
||||||
|
], string='Priority', default='0')
|
||||||
|
start_date = fields.Date(string="Start Date",default=fields.Date.today)
|
||||||
|
target_date = fields.Date(string="Target Date")
|
||||||
|
end_date = fields.Date(string="Actual end Date")
|
||||||
|
status = fields.Selection([ ('open', 'Open'),('close', 'Closed'),('hold', 'Hold')], default='open' , string="Status")
|
||||||
|
line_ids = fields.One2many('sos_brm_action_lines', 'ref_id', string="Action Details",copy=True)
|
||||||
|
target_date_line_ids = fields.One2many('sos_brm_action_revised_targets', 'ref_id', string="Revised Target Details",copy=True)
|
||||||
|
result = fields.Html(string="Remarks")
|
||||||
|
cross_dept_action = fields.Selection(
|
||||||
|
selection=[
|
||||||
|
('cross_dept', 'Cross-Dept'),
|
||||||
|
('inter_dept', 'Intra-Dept')
|
||||||
|
],
|
||||||
|
string='Type',
|
||||||
|
default='inter_dept',
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
department = fields.Many2one('sos_departments', string='Self Department',required=True, default=lambda self: self._default_department())
|
||||||
|
responsible_person = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Assigned To',required=True,
|
||||||
|
domain="[('id', 'in', allowed_user_ids)]"
|
||||||
|
)
|
||||||
|
assigned_by = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Assigned By',
|
||||||
|
default=lambda self: self.env.user
|
||||||
|
)
|
||||||
|
assigned_from_dept = fields.Many2one('sos_departments', string='Assigned By Dept', default=lambda self: self._default_department())
|
||||||
|
assigned_to_dept = fields.Many2one('sos_departments', string='Assigned To Dept')
|
||||||
|
latest_target_date = fields.Date(
|
||||||
|
string="Latest Revised Target",
|
||||||
|
compute='_compute_latest_target',
|
||||||
|
store=True, compute_sudo=True, readonly=True,
|
||||||
|
)
|
||||||
|
latest_target_line_id = fields.Many2one(
|
||||||
|
'sos_brm_action_lines',
|
||||||
|
string="Latest Target Line",
|
||||||
|
compute='_compute_latest_target',
|
||||||
|
store=True, compute_sudo=True, readonly=True,
|
||||||
|
)
|
||||||
|
is_sales_user_created = fields.Boolean(
|
||||||
|
compute='_compute_is_sales_user_created',
|
||||||
|
store=True,
|
||||||
|
string='Created by Sales User'
|
||||||
|
)
|
||||||
|
allowed_user_ids = fields.Many2many('res.users', compute='_compute_allowed_users')
|
||||||
|
reporting_to = fields.Many2one('res.users',related="responsible_person.reporting_to", string='Reporting To')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _default_department(self):
|
||||||
|
dept = self.env['sos_departments'].search(
|
||||||
|
[('users_line_ids.users', '=', self.env.user.id)],
|
||||||
|
limit=1
|
||||||
|
)
|
||||||
|
if dept:
|
||||||
|
return dept.id
|
||||||
|
return False
|
||||||
|
|
||||||
|
@api.onchange('department')
|
||||||
|
def _onchange_department(self):
|
||||||
|
if self.department:
|
||||||
|
if self.cross_dept_action == "cross_dept":
|
||||||
|
self.assigned_from_dept = self.department.id
|
||||||
|
@api.depends('department','cross_dept_action','assigned_to_dept')
|
||||||
|
def _compute_allowed_users(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.cross_dept_action == "inter_dept":
|
||||||
|
rec.allowed_user_ids = rec.department.users_line_ids.mapped('users')
|
||||||
|
else:
|
||||||
|
rec.allowed_user_ids = rec.assigned_to_dept.users_line_ids.mapped('users')
|
||||||
|
@api.depends('create_uid')
|
||||||
|
def _compute_is_sales_user_created(self):
|
||||||
|
sales_groups = [
|
||||||
|
self.env.ref('sos_inventory.sos_sales_user').id,
|
||||||
|
self.env.ref('sos_inventory.sos_inside_sales_user').id
|
||||||
|
]
|
||||||
|
for record in self:
|
||||||
|
record.is_sales_user_created = any(
|
||||||
|
gid in record.create_uid.groups_id.ids for gid in sales_groups
|
||||||
|
)
|
||||||
|
def _generate_id(self):
|
||||||
|
sequence_util = self.env['sos_common_scripts']
|
||||||
|
scope = 'Cross' if self.cross_dept_action == 'cross_dept' else 'Intra'
|
||||||
|
dept_label = (
|
||||||
|
getattr(self.department, 'short_form', False)
|
||||||
|
or getattr(self.department, 'name', 'NoDept')
|
||||||
|
)
|
||||||
|
type_id = f"{scope}/{dept_label}"
|
||||||
|
return sequence_util.generate_sequence('sos_brm_action', type_id, 'todo_id')
|
||||||
|
|
||||||
|
@api.depends('line_ids.target_date', 'line_ids.create_date')
|
||||||
|
def _compute_latest_target(self):
|
||||||
|
Line = self.env['sos_brm_action_lines']
|
||||||
|
for rec in self:
|
||||||
|
latest = Line.search([
|
||||||
|
('ref_id', '=', rec.id),
|
||||||
|
('target_date', '!=', False),
|
||||||
|
], order='target_date desc, id desc', limit=1)
|
||||||
|
if not latest:
|
||||||
|
latest = Line.search([('ref_id', '=', rec.id)],
|
||||||
|
order='create_date desc, id desc', limit=1)
|
||||||
|
rec.latest_target_line_id = latest.id or False
|
||||||
|
rec.latest_target_date = latest.target_date or False
|
||||||
|
def action_revise_target(self):
|
||||||
|
print("bye")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
record = super().create(vals)
|
||||||
|
if record.priority == 0 :
|
||||||
|
priority_emo='🟢 Low'
|
||||||
|
elif record.priority == 1 :
|
||||||
|
priority_emo='🟡 Medium'
|
||||||
|
elif record.priority == 2 :
|
||||||
|
priority_emo='🔴 High'
|
||||||
|
else:
|
||||||
|
priority_emo='🚨 Urgent'
|
||||||
|
|
||||||
|
# Set assigned_by
|
||||||
|
record.assigned_by = self.env.user.id
|
||||||
|
|
||||||
|
# Send email if responsible person exists and is different
|
||||||
|
responsible_id = record.responsible_person
|
||||||
|
process_incharge = record.reporting_to
|
||||||
|
if record.cross_dept_action == "cross_dept":
|
||||||
|
if process_incharge.id != self.env.user.id:
|
||||||
|
cc_mail_id = process_incharge.login
|
||||||
|
else:
|
||||||
|
cc_mail_id = ""
|
||||||
|
if responsible_id and responsible_id.id != self.env.user.id:
|
||||||
|
body_html = f"""
|
||||||
|
<p>Below <b>Action Plan</b> is assigned to your Department</p>
|
||||||
|
<p><b>Action Plan : </b> {record.name}</p>
|
||||||
|
<p><b>Priority : </b> {priority_emo}</p>
|
||||||
|
<p><b>Assigned By Dept : </b> {record.assigned_from_dept.name}</p>
|
||||||
|
<p><b>Assigned By : </b> {self.env.user.name}</p>
|
||||||
|
"""
|
||||||
|
subject = "Action Plan - Notification"
|
||||||
|
self.env['sos_common_scripts'].send_direct_email(
|
||||||
|
self.env,
|
||||||
|
"sos_brm_action",
|
||||||
|
record.id,
|
||||||
|
responsible_id.login,
|
||||||
|
subject,
|
||||||
|
body_html,
|
||||||
|
cc_mail_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if responsible_id and responsible_id.id != self.env.user.id:
|
||||||
|
body_html = f"""
|
||||||
|
<p>Below <b>Action Plan</b> is assigned to you</p>
|
||||||
|
<p><b>Action Plan : </b> {record.name}</p>
|
||||||
|
<p><b>Priority : </b> {priority_emo}</p>
|
||||||
|
<p><b>Assigned By : </b> {self.env.user.name}</p>
|
||||||
|
"""
|
||||||
|
subject = "Action Plan - Notification"
|
||||||
|
self.env['sos_common_scripts'].send_direct_email(
|
||||||
|
self.env,
|
||||||
|
"sos_brm_action",
|
||||||
|
record.id,
|
||||||
|
responsible_id.login,
|
||||||
|
subject,
|
||||||
|
body_html
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now department is guaranteed → Generate ID
|
||||||
|
dept_label = record.department.short_form or record.department.name or 'NoDept'
|
||||||
|
scope = 'Cross' if record.cross_dept_action == 'cross_dept' else 'Intra'
|
||||||
|
type_id = f"{scope}/{dept_label}"
|
||||||
|
record.todo_id = self.env['sos_common_scripts'].generate_sequence('sos_brm_action', type_id, 'todo_id')
|
||||||
|
|
||||||
|
return record
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def action_assign_action_btn(self):
|
||||||
|
return {
|
||||||
|
'name': "Assign Action to Other Department",
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'sos_brm_action',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'view_id': self.env.ref('sos_brm.view_form_sos_brm_action_cross_dept').id,
|
||||||
|
'target': 'new',
|
||||||
|
'context': {
|
||||||
|
'from_wizard': True,
|
||||||
|
'default_cross_dept_action': 'cross_dept'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class sos_brm_action_lines(models.Model):
|
||||||
|
_name = 'sos_brm_action_lines'
|
||||||
|
_description = 'Business Review'
|
||||||
|
|
||||||
|
ref_id = fields.Many2one('sos_brm_action', string="BRM action lines", ondelete="cascade")
|
||||||
|
name = fields.Char(string="Action Plan")
|
||||||
|
start_date = fields.Date(string="Entry Date")
|
||||||
|
target_date = fields.Date(string="Revised Target Date")
|
||||||
|
result = fields.Text(string="Result")
|
||||||
|
status = fields.Selection([ ('open', 'Open'),('close', 'Closed'),('hold', 'Hold')], default='open' , string="Status")
|
||||||
|
|
||||||
|
|
||||||
|
class sos_revised_targets(models.Model):
|
||||||
|
_name = 'sos_brm_action_revised_targets'
|
||||||
|
_description = 'Business Review'
|
||||||
|
|
||||||
|
ref_id = fields.Many2one('sos_brm_action', string="BRM action lines", ondelete="cascade")
|
||||||
|
revised_date = fields.Date(string="Revised Date")
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .import sos_brm_summary_report
|
||||||
|
from .import sos_cross_dept_report
|
||||||
|
|
||||||
|
|
@ -0,0 +1,417 @@
|
||||||
|
<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>
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
from odoo import models, api
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from calendar import month_name
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
class BRM_WeekSummaryReport(models.AbstractModel):
|
||||||
|
_name = 'report.sos_brm.report_brm_action_plan_summary'
|
||||||
|
_description = 'BRM Action Plan Summary Report'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_report_values(self, docids, data=None):
|
||||||
|
if not data:
|
||||||
|
raise UserError("No filter data provided for the report.")
|
||||||
|
|
||||||
|
done_by = data.get('done_by')
|
||||||
|
status = data.get('status')
|
||||||
|
department = data.get('department')
|
||||||
|
department_name = data.get('department_name')
|
||||||
|
cross_dept_action = data.get('cross_dept_action')
|
||||||
|
|
||||||
|
domain = []
|
||||||
|
if done_by:
|
||||||
|
domain.append(('responsible_person', '=', int(done_by)))
|
||||||
|
if department:
|
||||||
|
domain += [
|
||||||
|
'|',
|
||||||
|
('department', '=', department),
|
||||||
|
('assigned_to_dept', '=', department)
|
||||||
|
]
|
||||||
|
|
||||||
|
if status and status != 'all':
|
||||||
|
domain.append(('status', '=', status))
|
||||||
|
if cross_dept_action != "all":
|
||||||
|
domain.append(('cross_dept_action', '=', cross_dept_action))
|
||||||
|
|
||||||
|
|
||||||
|
# Main actions
|
||||||
|
docs = self.env['sos_brm_action'].search(domain, order='target_date asc, id asc')
|
||||||
|
|
||||||
|
# Subsets
|
||||||
|
cross = docs.filtered(lambda r: r.cross_dept_action == 'cross_dept')
|
||||||
|
local = docs.filtered(lambda r: r.cross_dept_action == 'inter_dept')
|
||||||
|
|
||||||
|
# Prefetch all child lines once and group by parent id
|
||||||
|
ActionLines = self.env['sos_brm_action_lines']
|
||||||
|
lines_map = defaultdict(list) # action_id -> list of line records
|
||||||
|
if docs:
|
||||||
|
all_lines = ActionLines.search([
|
||||||
|
('ref_id', 'in', docs.ids)
|
||||||
|
], order='ref_id, start_date, id')
|
||||||
|
|
||||||
|
for ln in all_lines:
|
||||||
|
lines_map[ln.ref_id.id].append(ln)
|
||||||
|
return {
|
||||||
|
'doc_ids': docs.ids,
|
||||||
|
'doc_model': 'sos_brm_action',
|
||||||
|
'done_by': self.env['res.users'].browse(int(done_by)) if done_by else self.env['res.users'],
|
||||||
|
'status': status or '',
|
||||||
|
'department': department_name or '',
|
||||||
|
'docs': docs,
|
||||||
|
'cross': cross,
|
||||||
|
'local': local,
|
||||||
|
'count_cross': len(cross),
|
||||||
|
'count_local': len(local),
|
||||||
|
'cross_dept_action':cross_dept_action,
|
||||||
|
'lines_map': lines_map,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
from odoo import models, api
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from calendar import month_name
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
class BRM_CrossReport(models.AbstractModel):
|
||||||
|
_name = 'report.sos_brm.report_cross_dept_summary'
|
||||||
|
_description = 'Cross Dept Summary Report'
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_report_values(self, docids, data=None):
|
||||||
|
if not data:
|
||||||
|
raise UserError("No filter data provided for the report.")
|
||||||
|
|
||||||
|
# Wizard inputs (expect raw IDs / strings, or falsy)
|
||||||
|
assigned_from_dept = data.get('assigned_from_dept')
|
||||||
|
assigned_to_dept = data.get('assigned_to_dept')
|
||||||
|
assigned_by = data.get('assigned_by')
|
||||||
|
status = data.get('status') or False
|
||||||
|
|
||||||
|
# Cross-dept only
|
||||||
|
domain = [('cross_dept_action', '=', 'cross_dept')]
|
||||||
|
if assigned_from_dept not in (None, '', 0, '0'):
|
||||||
|
try:
|
||||||
|
domain.append(('assigned_from_dept', '=', int(assigned_from_dept)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
if assigned_to_dept not in (None, '', 0, '0'):
|
||||||
|
try:
|
||||||
|
domain.append(('assigned_to_dept', '=', int(assigned_to_dept)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
if assigned_by not in (None, '', 0, '0'):
|
||||||
|
try:
|
||||||
|
domain.append(('assigned_by', '=', int(assigned_by)))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
if status and status != 'all':
|
||||||
|
domain.append(('status', '=', status))
|
||||||
|
|
||||||
|
Action = self.env['sos_brm_action']
|
||||||
|
ActionLine = self.env['sos_brm_action_lines']
|
||||||
|
|
||||||
|
# Main actions
|
||||||
|
docs = Action.search(domain, order='target_date asc, id asc')
|
||||||
|
|
||||||
|
# Prefetch child lines (open only) and group by parent
|
||||||
|
lines_map = defaultdict(list)
|
||||||
|
# ensure keys exist for all docs (lets us index lines_map[rec.id] in QWeb)
|
||||||
|
for rec in docs:
|
||||||
|
lines_map.setdefault(rec.id, [])
|
||||||
|
if docs:
|
||||||
|
all_lines = ActionLine.search(
|
||||||
|
[('ref_id', 'in', docs.ids)],
|
||||||
|
order='ref_id, start_date, id'
|
||||||
|
)
|
||||||
|
for ln in all_lines:
|
||||||
|
lines_map[ln.ref_id.id].append(ln)
|
||||||
|
|
||||||
|
# Precompute for QWeb (avoid len()/dict() calls there)
|
||||||
|
count_cross = len(docs)
|
||||||
|
child_counts = {rid: len(lst) for rid, lst in lines_map.items()}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'doc_ids': docs.ids,
|
||||||
|
'doc_model': 'sos_brm_action',
|
||||||
|
'docs': docs,
|
||||||
|
'status': status or '',
|
||||||
|
'lines_map': lines_map,
|
||||||
|
'count_cross': count_cross,
|
||||||
|
'child_counts': child_counts,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
<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_cross_dept_report" model="ir.actions.report">
|
||||||
|
<field name="name">BRM 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_cross_dept_summary</field>
|
||||||
|
<field name="print_report_name">Cross Dept Summary</field>
|
||||||
|
<field name="paperformat_id" ref="paperformat_brm_landscape"/>
|
||||||
|
</record>
|
||||||
|
<template id="report_cross_dept_summary">
|
||||||
|
<t t-call="web.basic_layout">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="/sos_inventory/static/src/css/style.css?v=7"/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page{ margin-top:10px; }
|
||||||
|
.hdr { display:flex; justify-content:space-between; align-items:center; margin:2px 0 10px; }
|
||||||
|
.title { margin:0; font-size:18px; }
|
||||||
|
.pill { background:#202022; color:#fff; border-radius:999px; padding:2px 8px; font-size:12px; }
|
||||||
|
.table_custom { width:100%; border-collapse:collapse; font-size:13px; }
|
||||||
|
.table_custom thead th { background:#eae6ff; border-bottom:1px solid #cfc8ff; padding:8px; text-align:left; }
|
||||||
|
.table_custom td { padding:8px; overflow-wrap: anywhere; }
|
||||||
|
.nowrap { white-space:nowrap; }
|
||||||
|
.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; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="hdr">
|
||||||
|
<h3 class="title">Cross-Department Actions</h3>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 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="count_cross">
|
||||||
|
<t t-foreach="docs" t-as="rec">
|
||||||
|
<!-- No function calls: use dict indexing only -->
|
||||||
|
<t t-set="lines" t-value="lines_map[rec.id]"/>
|
||||||
|
<t t-set="rows" t-value="child_counts[rec.id]"/>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td t-att-rowspan="rows or 1">
|
||||||
|
<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 or 1"><t t-esc="rec.assigned_from_dept.name or '—'"/></td>
|
||||||
|
<td t-att-rowspan="rows or 1"><t t-esc="rec.assigned_to_dept.name or '—'"/></td>
|
||||||
|
<td t-att-rowspan="rows or 1"><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 or 1">
|
||||||
|
<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 or 1">
|
||||||
|
<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 or 1">
|
||||||
|
<t t-esc="rec.end_date and rec.end_date.strftime('%d-%m-%Y') or '—'"/>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- first child row (or dashes) -->
|
||||||
|
<td>
|
||||||
|
<t t-esc="(rows and (lines[0].name or '—')) or '—'"/>
|
||||||
|
</td>
|
||||||
|
<td class="nowrap" style="text-align:center;">
|
||||||
|
<t t-esc="(rows and (lines[0].target_date and lines[0].target_date.strftime('%d-%m-%Y') or '—')) or '—'"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<t t-esc="(rows and (lines[0].result or '—')) or '—'"/>
|
||||||
|
</td>
|
||||||
|
<td style="text-align:center;">
|
||||||
|
<t t-if="rows">
|
||||||
|
<span class="status-pill"
|
||||||
|
t-att-style="'background-color:#2e7d32; border-color:#bfe8c8;' if lines[0].status == 'close' else 'background-color:#e1375e;'">
|
||||||
|
<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 -->
|
||||||
|
<t t-if="rows > 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-else="">
|
||||||
|
<tr><td colspan="11" style="text-align:center; color:#666; padding:12px;">No cross-department actions.</td></tr>
|
||||||
|
</t>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_sos_brm_action,sos_brm_action access,model_sos_brm_action,base.group_user,1,1,1,1
|
||||||
|
access_sos_brm_action_lines,sos_brm_action_lines access,model_sos_brm_action_lines,base.group_user,1,1,1,1
|
||||||
|
access_sos_brm_action_revised_targets,sos_brm_action_revised_targets access,model_sos_brm_action_revised_targets,base.group_user,1,1,1,1
|
||||||
|
access_sos_brm_action_report_wizard,sos_brm_action_report_wizard access,model_sos_brm_action_report_wizard,base.group_user,1,1,1,1
|
||||||
|
access_sos_cross_dept_report_wizard,sos_cross_dept_report_wizard access,model_sos_cross_dept_report_wizard,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<!-- FULL control on own/assigned -->
|
||||||
|
<record id="sos_brm_action_rule_full_own" model="ir.rule">
|
||||||
|
<field name="name">BRM Action: Full (own/assigned/creator)</field>
|
||||||
|
<field name="model_id" ref="model_sos_brm_action"/>
|
||||||
|
<field name="domain_force">[
|
||||||
|
'|','|','|',
|
||||||
|
('responsible_person', '=', user.id),
|
||||||
|
('assigned_by', '=', user.id),
|
||||||
|
('create_uid', '=', user.id),
|
||||||
|
('reporting_to', '=', user.id)
|
||||||
|
]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||||
|
<field name="perm_read" eval="1"/>
|
||||||
|
<field name="perm_write" eval="1"/>
|
||||||
|
<field name="perm_unlink" eval="1"/>
|
||||||
|
<field name="perm_create" eval="0"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- keep a separate unfiltered CREATE rule -->
|
||||||
|
<record id="sos_brm_action_rule_create_any" model="ir.rule">
|
||||||
|
<field name="name">BRM Action: Create (any)</field>
|
||||||
|
<field name="model_id" ref="model_sos_brm_action"/>
|
||||||
|
<field name="domain_force">[(1,'=',1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||||
|
<field name="perm_create" eval="1"/>
|
||||||
|
<field name="perm_read" eval="0"/>
|
||||||
|
<field name="perm_write" eval="0"/>
|
||||||
|
<field name="perm_unlink" eval="0"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- CREATE must not be filtered by a domain -->
|
||||||
|
<record id="sos_brm_action_rule_create_any" model="ir.rule">
|
||||||
|
<field name="name">BRM Action: Create (any)</field>
|
||||||
|
<field name="model_id" ref="model_sos_brm_action"/>
|
||||||
|
<field name="domain_force">[(1,'=',1)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||||
|
<field name="perm_create" eval="1"/>
|
||||||
|
<field name="perm_read" eval="0"/>
|
||||||
|
<field name="perm_write" eval="0"/>
|
||||||
|
<field name="perm_unlink" eval="0"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- (Optional) Managers/Finance see/edit all -->
|
||||||
|
<record id="sos_brm_action_rule_full_managers" model="ir.rule">
|
||||||
|
<field name="name">BRM Action: Full (managers/finance)</field>
|
||||||
|
<field name="model_id" ref="model_sos_brm_action"/>
|
||||||
|
<field name="domain_force">[(1,'=',1)]</field>
|
||||||
|
<field name="groups" eval="[(6, 0, [
|
||||||
|
ref('sos_inventory.sos_management_user'),
|
||||||
|
ref('sos_inventory.sos_finance_head_user')
|
||||||
|
])]"/>
|
||||||
|
<field name="perm_read" eval="1"/>
|
||||||
|
<field name="perm_write" eval="1"/>
|
||||||
|
<field name="perm_create" eval="1"/>
|
||||||
|
<field name="perm_unlink" eval="1"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="sos_brm_action_rule_reviewer_read" model="ir.rule">
|
||||||
|
<field name="name">BRM Action: Reviewer Read Sales User Created Records</field>
|
||||||
|
<field name="model_id" ref="model_sos_brm_action"/>
|
||||||
|
<field name="domain_force">[('is_sales_user_created', '=', True)]</field>
|
||||||
|
<field name="groups" eval="[(4, ref('sos_inventory.sos_sales_reviewer'))]"/>
|
||||||
|
<field name="perm_read" eval="1"/>
|
||||||
|
<field name="perm_write" eval="0"/>
|
||||||
|
<field name="perm_unlink" eval="0"/>
|
||||||
|
<field name="perm_create" eval="0"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<menuitem id="sos_brm_menu_root" name="To-Do Management (BRM)"/>
|
||||||
|
<menuitem id="sos_brm_reports_root" sequence="2" name="Reports" parent="sos_brm_menu_root"/>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="action_brm_action_list" model="ir.actions.act_window">
|
||||||
|
<field name="name">Actions</field>
|
||||||
|
<field name="type">ir.actions.act_window</field>
|
||||||
|
<field name="res_model">sos_brm_action</field>
|
||||||
|
<field name="view_mode">tree,form,kanban</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="sos_brm_action_search" model="ir.ui.view">
|
||||||
|
<field name="name">sos_brm_action.view.search</field>
|
||||||
|
<field name="model">sos_brm_action</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Actions">
|
||||||
|
<field name="name" string="Action Point"/>
|
||||||
|
<filter name="cross_only"
|
||||||
|
string="Cross Dept Activities"
|
||||||
|
domain="[('cross_dept_action','=','cross_dept')]"
|
||||||
|
context="{'show_cross_cols': True}"/>
|
||||||
|
<searchpanel>
|
||||||
|
<field name="cross_dept_action" string="Action Type" icon="fa-list-ul" enable_counters="1"/>
|
||||||
|
<field name="department" string="Department" icon="fa-list-ul" enable_counters="1"/>
|
||||||
|
|
||||||
|
</searchpanel>
|
||||||
|
<field name="responsible_person" string="Assigned To"/>
|
||||||
|
<field name="assigned_by" string="Assigned By"/>
|
||||||
|
<field name="department" string="Department"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="sos_brm_action_view_tree" model="ir.ui.view">
|
||||||
|
<field name="name">sos_brm_action.view.tree</field>
|
||||||
|
<field name="model">sos_brm_action</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree>
|
||||||
|
<!-- <header> <button class="oe_highlight" type="object" name="action_assign_action_btn" display="always" string="Add Cross Dept Activities"></button> </header> -->
|
||||||
|
<field name="cross_dept_action"/>
|
||||||
|
<field name="todo_id" invisible="not id"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="priority"/>
|
||||||
|
<field name="assigned_by" widget="many2one_avatar_user"/>
|
||||||
|
<field name="responsible_person" widget="many2one_avatar_user"/>
|
||||||
|
<field name="status" widget="badge"
|
||||||
|
decoration-success="status == 'close'"
|
||||||
|
decoration-danger="status == 'open'"/>
|
||||||
|
<!-- Extra columns: shown only when context flag is true -->
|
||||||
|
<field name="assigned_from_dept"
|
||||||
|
column_invisible="not context.get('show_cross_cols', False)"/>
|
||||||
|
<field name="assigned_to_dept"
|
||||||
|
column_invisible="not context.get('show_cross_cols', False)"/>
|
||||||
|
|
||||||
|
<field name="write_uid" string="Last Edited By" optional="hide"/>
|
||||||
|
<field name="write_date" string="Last Edited On" optional="hide"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_form_sos_brm_action_cross_dept" model="ir.ui.view">
|
||||||
|
<field name="name">Form</field>
|
||||||
|
<field name="model">sos_brm_action</field>
|
||||||
|
<field name="priority" eval="90"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Model Form">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="todo_id" invisible="not id"/>
|
||||||
|
<field name="cross_dept_action" invisible="1"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="start_date" invisible="1"/>
|
||||||
|
<field name="allowed_user_ids" invisible="1"/>
|
||||||
|
<field name="assigned_to_dept"/>
|
||||||
|
|
||||||
|
<field name="result" string="Description"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="assigned_from_dept"/>
|
||||||
|
<field name="responsible_person" widget="many2one_avatar_user"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="sos_brm_action_form_view" model="ir.ui.view">
|
||||||
|
<field name="name">Form</field>
|
||||||
|
<field name="model">sos_brm_action</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Model Form">
|
||||||
|
<sheet>
|
||||||
|
<widget name="web_ribbon" text="Open" bg_color="bg-danger" invisible="status == 'close'"/>
|
||||||
|
<widget name="web_ribbon" text="Closed" bg_color="bg-success" invisible="status == 'open'"/>
|
||||||
|
<h2 style="text-align: center;text-transform: uppercase;text-shadow: 1px 1p 1px #140718;color: #65407c;padding:5px;">To-Do Management</h2><hr></hr><br></br>
|
||||||
|
<table class="table" style="box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;">
|
||||||
|
<tr>
|
||||||
|
<td><group><field name="cross_dept_action" readonly="id"/></group></td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<br></br><br></br><br></br>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="todo_id" invisible="not id"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="priority"/>
|
||||||
|
<field name="department"/>
|
||||||
|
<field name="start_date" readonly="id"/>
|
||||||
|
<field name="target_date"/>
|
||||||
|
<field name="end_date"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="status"/>
|
||||||
|
<field name="cross_dept_action" invisible="1"/>
|
||||||
|
<field name="assigned_from_dept" invisible="cross_dept_action == 'inter_dept'"/>
|
||||||
|
<field name="assigned_to_dept" invisible="cross_dept_action == 'inter_dept'"/>
|
||||||
|
<field name="allowed_user_ids" invisible="1"/>
|
||||||
|
<field name="responsible_person" widget="many2one_avatar_user"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group> <field name="result"/></group>
|
||||||
|
<br></br><br></br>
|
||||||
|
<field name="line_ids">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="start_date"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="target_date"/>
|
||||||
|
<field name="result"/>
|
||||||
|
<field name="status"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_brm_monthly_summary_wizard" model="ir.actions.act_window">
|
||||||
|
<field name="name">Reports</field>
|
||||||
|
<field name="res_model">sos_brm_action_report_wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_cross_dept_report_wizard" model="ir.actions.act_window">
|
||||||
|
<field name="name">Reports</field>
|
||||||
|
<field name="res_model">sos_cross_dept_report_wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
<menuitem id="sos_brm_action_menu" sequence="1" action="action_brm_action_list" name="Action Plan" parent="sos_brm_menu_root" />
|
||||||
|
<menuitem id="sos_brm_report_menu" action="action_brm_monthly_summary_wizard" name="To-Do Reports" parent="sos_brm_reports_root" />
|
||||||
|
<menuitem id="sos_cross_dept_report_menu" action="action_cross_dept_report_wizard" name="Cross-Dept Reports" parent="sos_brm_reports_root" />
|
||||||
|
<menuitem id="qo_menu"
|
||||||
|
name="Quality Objectives (QO)"
|
||||||
|
parent="sos_brm_menu_root" action="sos_qo_aod.action_qo_form_list"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from .import sos_brm_report_wizard
|
||||||
|
from .import sos_cross_dept_report_wizard
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
from odoo import models, fields, api
|
||||||
|
import io
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
class SOS_BRM_Report_Wizard(models.TransientModel):
|
||||||
|
_name = 'sos_brm_action_report_wizard'
|
||||||
|
_description = 'BRM Report Wizard'
|
||||||
|
|
||||||
|
cross_dept_action = fields.Selection(
|
||||||
|
selection=[
|
||||||
|
('all', 'Both'),
|
||||||
|
('cross_dept', 'Cross-Dept'),
|
||||||
|
('inter_dept', 'Intra-Dept')
|
||||||
|
],
|
||||||
|
string='Type',
|
||||||
|
default='all'
|
||||||
|
)
|
||||||
|
done_by = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Responsible')
|
||||||
|
department = fields.Many2one('sos_departments', string='Department')
|
||||||
|
status = fields.Selection([ ('all', 'All'),('open', 'Open'),('close', 'Closed'),('hold', 'Hold')], default='open' , string="Status")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def generate_report(self):
|
||||||
|
# Build domain for filtering records
|
||||||
|
domain = []
|
||||||
|
if self.done_by:
|
||||||
|
|
||||||
|
domain.append(('responsible_person', '=', self.done_by.id))
|
||||||
|
if self.status != 'all':
|
||||||
|
domain.append(('status', '=', self.status))
|
||||||
|
if self.department:
|
||||||
|
domain.append(('department', '=', self.department.id))
|
||||||
|
if self.cross_dept_action != "all":
|
||||||
|
domain.append(('cross_dept_action', '=', self.cross_dept_action))
|
||||||
|
# Search for records
|
||||||
|
records = self.env['sos_brm_action'].search(domain)
|
||||||
|
if not records:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': ('No Records Found'),
|
||||||
|
'message': ('No Records Found'),
|
||||||
|
'type': 'warning',
|
||||||
|
'sticky': False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Generate report with filtered record IDs
|
||||||
|
return self.env.ref('sos_brm.action_brm_summary_report').report_action(
|
||||||
|
records.ids,
|
||||||
|
data={
|
||||||
|
|
||||||
|
'done_by' : self.done_by.id,
|
||||||
|
'department':self.department.id,
|
||||||
|
'department_name':self.department.name,
|
||||||
|
'status' : self.status,
|
||||||
|
'cross_dept_action' : self.cross_dept_action
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<odoo>
|
||||||
|
<record id="view_sos_brm_action_report_wizard" model="ir.ui.view">
|
||||||
|
<field name="name">sos_brm_action_report_wizard.form</field>
|
||||||
|
<field name="model">sos_brm_action_report_wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Generate Summary Report by Sales Person">
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<field name="cross_dept_action"/>
|
||||||
|
<field name="department"/>
|
||||||
|
<field name="done_by"/>
|
||||||
|
<field name="status"/>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button type="object" name="generate_report" class="btn-primary"><i class="fa fa-file-text-o"></i> View Report</button>
|
||||||
|
|
||||||
|
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
from odoo import models, fields, api
|
||||||
|
import io
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
class SOS_BRM_Report_Wizard(models.TransientModel):
|
||||||
|
_name = 'sos_cross_dept_report_wizard'
|
||||||
|
_description = 'Cross dept Report Wizard'
|
||||||
|
|
||||||
|
|
||||||
|
assigned_from_dept = fields.Many2one('sos_departments', string='Assigned By')
|
||||||
|
assigned_to_dept = fields.Many2one('sos_departments', string='Assigned To Dept')
|
||||||
|
status = fields.Selection([ ('all', 'All'),('open', 'Open'),('close', 'Closed'),('hold', 'Hold')], default='open' , string="Status")
|
||||||
|
assigned_by = fields.Many2one(
|
||||||
|
'res.users',
|
||||||
|
string='Assigned By'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_report(self):
|
||||||
|
# Build domain for filtering records
|
||||||
|
domain = [('cross_dept_action','=','cross_dept')]
|
||||||
|
if self.assigned_from_dept:
|
||||||
|
domain.append(('assigned_from_dept', '=', self.assigned_from_dept.id))
|
||||||
|
if self.assigned_to_dept:
|
||||||
|
domain.append(('assigned_to_dept', '=', self.assigned_to_dept.id))
|
||||||
|
if self.assigned_by:
|
||||||
|
domain.append(('assigned_by', '=', self.assigned_by.id))
|
||||||
|
if self.status and self.status != 'all':
|
||||||
|
domain.append(('status', '=', self.status))
|
||||||
|
|
||||||
|
# Search for records
|
||||||
|
records = self.env['sos_brm_action'].search(domain)
|
||||||
|
if not records:
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.client',
|
||||||
|
'tag': 'display_notification',
|
||||||
|
'params': {
|
||||||
|
'title': ('No Records Found'),
|
||||||
|
'message': ('No Records Found'),
|
||||||
|
'type': 'warning',
|
||||||
|
'sticky': False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Generate report with filtered record IDs
|
||||||
|
return self.env.ref('sos_brm.action_cross_dept_report').report_action(
|
||||||
|
records.ids,
|
||||||
|
data={
|
||||||
|
|
||||||
|
'assigned_from_dept' : self.assigned_from_dept.id,
|
||||||
|
'assigned_to_dept':self.assigned_to_dept.id,
|
||||||
|
'assigned_by':self.assigned_by.id,
|
||||||
|
'status' : self.status,
|
||||||
|
'cross_dept_action':'cross_dept'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<odoo>
|
||||||
|
<record id="view_sos_cross_dept_report_wizard" model="ir.ui.view">
|
||||||
|
<field name="name">sos_cross_dept_report_wizard.form</field>
|
||||||
|
<field name="model">sos_cross_dept_report_wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<field name="assigned_from_dept" string="Assigned By Department"/>
|
||||||
|
<field name="assigned_to_dept" string="Assigned To Department"/>
|
||||||
|
<field name="assigned_by"/>
|
||||||
|
<field name="status"/>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button type="object" name="generate_report" class="btn-primary"><i class="fa fa-file-text-o"></i> View Report</button>
|
||||||
|
|
||||||
|
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in New Issue