Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
ceb5e58e3e | |
|
|
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>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
'version': '17.0.1.0.0',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['base','web','mail'],
|
||||
'depends': ['base','web','mail','one2many_search_widget'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
'security/record_rules.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'data/cron_jobs.xml',
|
||||
'views/pareto_chart_widget_view.xml',
|
||||
'views/sos_audit_log_view.xml',
|
||||
'views/menu.xml',
|
||||
'views/sos_quote_generation.xml',
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
'views/sos_fg_view.xml',
|
||||
'views/sos_sfg_view.xml',
|
||||
'views/sos_material_view.xml',
|
||||
'views/sos_budget_plan_view.xml',
|
||||
'views/sos_material_bom_view.xml',
|
||||
'views/sos_sfg_bom_view.xml',
|
||||
'views/sos_fg_bom_view.xml',
|
||||
|
|
@ -81,6 +83,8 @@
|
|||
'views/sos_service_call_log_report.xml',
|
||||
'views/sos_transfer_challan_return_from_customer_view.xml',
|
||||
'views/sos_shelflife_register_view.xml',
|
||||
'views/sos_prf_view.xml',
|
||||
|
||||
'wizard/sfg_bom_bulk_upload_view.xml',
|
||||
'wizard/mon_bulk_upload_view.xml',
|
||||
'wizard/missing_component_wizard.xml',
|
||||
|
|
@ -115,6 +119,7 @@
|
|||
'report/sos_boq_labels.xml',
|
||||
'report/shelflife_report.xml',
|
||||
'report/sos_boq_report.xml',
|
||||
'report/sos_budget_plan_summary.xml',
|
||||
'data/send_indent_plan_email_template.xml',
|
||||
'data/selection_item.xml'
|
||||
|
||||
|
|
@ -130,6 +135,8 @@
|
|||
'sos_inventory/static/src/components/**/*.js',
|
||||
'sos_inventory/static/src/components/**/*.xml',
|
||||
'sos_inventory/static/src/components/**/*.scss',
|
||||
'sos_inventory/static/src/js/pareto_chart_widget.js',
|
||||
'sos_inventory/static/src/xml/pareto_chart_template.xml',
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -4,7 +4,9 @@ import io
|
|||
import xlsxwriter
|
||||
|
||||
class MaterialBackupExportController(http.Controller):
|
||||
|
||||
@http.route('/supplier/form', auth='public')
|
||||
def index(self, **kw):
|
||||
return "Hello, world"
|
||||
@http.route(['/download/material/backup/<string:year>/<string:item_type>'], type='http', auth='user')
|
||||
def download_backup_by_year(self, year, item_type, **kwargs):
|
||||
# Safely map table names
|
||||
|
|
|
|||
|
|
@ -59,3 +59,5 @@ from . import sos_service_call_log_report
|
|||
from . import sos_inhouse_validation_reports_files
|
||||
from . import sos_transfer_challan_return_from_customer
|
||||
from . import sos_shelflife_register
|
||||
from . import sos_budget_plan
|
||||
from . import sos_prf
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
from math import ceil
|
||||
|
||||
|
||||
class SOS_Budget_Plan(models.Model):
|
||||
_name = 'sos_budget_plan'
|
||||
_description = 'Budget Plan'
|
||||
|
||||
name= fields.Char(string="Plan Name")
|
||||
sfg_option = fields.Boolean('Semi-Finished Goods')
|
||||
fg_option = fields.Boolean('Finished Goods',default=True)
|
||||
fg_name = fields.Many2one('sos_fg', string='FG Name')
|
||||
sfg_name = fields.Many2one('sos_sfg', string="SFG Name")
|
||||
quantity = fields.Integer(string="Quantity",required=True)
|
||||
@api.onchange('sfg_option')
|
||||
def _onchange_sfg_option(self):
|
||||
if self.sfg_option:
|
||||
self.fg_option = False
|
||||
@api.onchange('fg_option')
|
||||
def _onchange_fg_option(self):
|
||||
if self.fg_option:
|
||||
self.sfg_option = False
|
||||
def create_budget_plan(self):
|
||||
self.ensure_one()
|
||||
|
||||
if not self.quantity:
|
||||
raise UserError("Quantity must be greater than zero.")
|
||||
|
||||
if not (self.fg_option or getattr(self, 'sfg_option', False)):
|
||||
raise UserError("Select Finished Goods or SFG option.")
|
||||
|
||||
# --- Accumulator keyed by product/component id ---
|
||||
# { prod_id: {'name': str, 'needed': float, 'inhand': float, 'price': float} }
|
||||
agg = {}
|
||||
|
||||
def _accumulate(line, needed_qty):
|
||||
"""Accumulate needed qty for a line's primary component."""
|
||||
prod = line.primary_component_id
|
||||
if not prod:
|
||||
return
|
||||
pid = prod.id
|
||||
rec = agg.get(pid)
|
||||
if rec:
|
||||
rec['needed'] += float(needed_qty or 0.0)
|
||||
else:
|
||||
agg[pid] = {
|
||||
'name': prod.part_no or prod.name or '—',
|
||||
'needed': float(needed_qty or 0.0),
|
||||
'inhand': float(prod.inhand_stock_qty or 0.0), # take once per product
|
||||
'price': float(prod.unit_price or 0.0),
|
||||
'pack': float(getattr(prod, 'std_packing_qty', 0.0) or 0.0),
|
||||
}
|
||||
|
||||
label_name = "" # what we'll display in the report header
|
||||
|
||||
if self.fg_option:
|
||||
# -------- FG → explode SFGs + add direct materials --------
|
||||
product_bom = self.env['sos_fg_bom'].search([
|
||||
('fg_name', '=', self.fg_name.id),
|
||||
('is_primary', '=', True)
|
||||
], limit=1)
|
||||
if not product_bom:
|
||||
raise UserError("BOM for product is Missing")
|
||||
|
||||
label_name = self.fg_name.display_name
|
||||
|
||||
# 1) FG BOM lines that reference SFG BOMs
|
||||
fg_sfg_lines = self.env['sos_fg_bom_line'].search([('bom_id', '=', product_bom.id)])
|
||||
for sfg_line in fg_sfg_lines:
|
||||
sfg_bom = sfg_line.sfg_bom_id # Many2one to SFG BOM
|
||||
if not sfg_bom:
|
||||
continue
|
||||
|
||||
# Required SFG qty = (FG qty * SFG-per-FG) - SFG in-hand (clamped ≥ 0)
|
||||
inhand_sfg = float((sfg_bom.name and sfg_bom.name.inhand_stock_qty) or 0.0)
|
||||
requested = float(self.quantity or 0.0) * float(sfg_line.quantity or 0.0)
|
||||
required_sfg_qty = max(requested - inhand_sfg, 0.0)
|
||||
if not required_sfg_qty:
|
||||
continue
|
||||
|
||||
# Explode SFG BOM to components for the required SFG qty
|
||||
for comp in sfg_bom.sfg_bom_line_ids:
|
||||
needed_qty = float(comp.quantity or 0.0) * required_sfg_qty
|
||||
_accumulate(comp, needed_qty)
|
||||
|
||||
# 2) Direct materials under the FG BOM
|
||||
direct_material_lines = self.env['sos_sfg_bom_line'].search([('fg_bom_id', '=', product_bom.id)])
|
||||
for mat in direct_material_lines:
|
||||
needed_qty = float(mat.quantity or 0.0) * float(self.quantity or 0.0)
|
||||
_accumulate(mat, needed_qty)
|
||||
|
||||
else:
|
||||
# -------- SFG-only path --------
|
||||
if not getattr(self, 'sfg_option', False):
|
||||
raise UserError("SFG option must be selected.")
|
||||
|
||||
sfg_bom = self.env['sos_sfg_bom'].search([('name', '=', self.sfg_name.id)], limit=1)
|
||||
if not sfg_bom:
|
||||
raise UserError("SFG BOM is missing.")
|
||||
|
||||
label_name = self.sfg_name.display_name
|
||||
|
||||
# Required SFG qty = requested - in-hand (clamped ≥ 0)
|
||||
inhand_sfg = float((sfg_bom.name and sfg_bom.name.inhand_stock_qty) or 0.0)
|
||||
requested = float(self.quantity or 0.0)
|
||||
required_sfg_qty = max(requested - inhand_sfg, 0.0)
|
||||
|
||||
if required_sfg_qty:
|
||||
for comp in sfg_bom.sfg_bom_line_ids:
|
||||
needed_qty = float(comp.quantity or 0.0) * required_sfg_qty
|
||||
_accumulate(comp, needed_qty)
|
||||
|
||||
# ---- Build final list from aggregator (merge duplicates; consider inhand once) ----
|
||||
materials = []
|
||||
for rec in agg.values():
|
||||
raw_to_purchase = max(rec['needed'] - rec['inhand'], 0.0)
|
||||
if raw_to_purchase <= 0:
|
||||
continue
|
||||
pack = float(getattr(rec, 'pack', 0.0) or rec.get('pack') or 0.0)
|
||||
if pack > 0:
|
||||
pack_count = ceil(raw_to_purchase / pack)
|
||||
to_purchase = pack_count * pack
|
||||
else:
|
||||
pack_count = 0
|
||||
to_purchase = raw_to_purchase
|
||||
materials.append({
|
||||
'material_name': rec['name'],
|
||||
'needed_quantity': rec['needed'],
|
||||
'inhand_quantity': rec['inhand'],
|
||||
'actual_needed': raw_to_purchase,
|
||||
'std_packing_qty': pack,
|
||||
'packs_to_buy': pack_count,
|
||||
'to_purchase': to_purchase,
|
||||
'price': rec['price'],
|
||||
'cost': round(to_purchase * rec['price'], 2),
|
||||
})
|
||||
|
||||
if not materials:
|
||||
raise UserError("No materials need to be purchased.")
|
||||
|
||||
# Optional ordering: most to purchase first
|
||||
materials.sort(key=lambda r: (-r['to_purchase'], r['material_name']))
|
||||
|
||||
total_cost = sum(m['cost'] for m in materials)
|
||||
|
||||
action = self.env.ref('sos_inventory.action_material_budget_summary')
|
||||
return action.report_action(
|
||||
self,
|
||||
data={
|
||||
'fg_name': label_name, # shown as header label
|
||||
'total_quantity': self.quantity,
|
||||
'materials': materials,
|
||||
'total_cost': total_cost,
|
||||
'currency_symbol': self.env.company.currency_id.symbol or '',
|
||||
}
|
||||
)
|
||||
|
|
@ -41,7 +41,7 @@ class SOS_CCRF(models.Model):
|
|||
batch_release_date = fields.Date(string="Invoice Date")
|
||||
brcoa_no = fields.Char(string="BRCOA No")
|
||||
brcoa_date = fields.Date(string="BRCOA Date")
|
||||
other_complaints = fields.Text(string="Details of any other Complaints received from this Production run")
|
||||
other_complaints = fields.Text(string="Defective Details/Symptom")
|
||||
data_collected_by = fields.Many2one('res.users', string='Data Collected By')
|
||||
data_collected_image = fields.Image(related="data_collected_by.signature_image",string='Data Collected Sign',readonly=True)
|
||||
data_collected_on = fields.Datetime(string="Data Collected On")
|
||||
|
|
@ -56,12 +56,17 @@ class SOS_CCRF(models.Model):
|
|||
investigation_carriedout_image = fields.Image(related="investigation_carriedout_by.signature_image",string='Investigation Carried Out Sign',readonly=True)
|
||||
investigation_carriedout_on = fields.Datetime(string="Investigation Carried Out On")
|
||||
root_cause = fields.Html(string="Root Cause",
|
||||
default="<p>Why #1</p><p>Why #2</p><p>Why #3</p>")
|
||||
default="<p>Why #1 :</p><p>Why #2 :</p><p>Why #3 :</p><p>Why #4 :</p><p>Why #5 :</p>")
|
||||
#team_formation = fields.Html(string="Team Formation",
|
||||
#default="<p>Name :</p><br><p>Department :</p><br><p>Roles :</p>")
|
||||
#problem_description = fields.Html(string="Problem Description",
|
||||
#default="<p>Clear Statement :</p><br><p>Photo :</p><br><p>What :</p><br><p>Where :</p><br><p>When :</p><br><p>Why :</p><br><p>Who :</p><br><p>How :</p><br><p>How many :</p>")
|
||||
rootcause_carriedout_by = fields.Many2one('res.users', string='Root Cause Carried Out By')
|
||||
rootcause_carriedout_image = fields.Image(related="rootcause_carriedout_by.signature_image",string='Root Cause Carried Out Sign',readonly=True)
|
||||
rootcause_carriedout_on = fields.Datetime(string="Root Cause Carried Out On")
|
||||
corrective_action = fields.Html(string="Corrective Action")
|
||||
preventive_action = fields.Html(string="Preventive Action")
|
||||
corrective_action = fields.Html(string="D5:Permanent Corrective Action")
|
||||
implement_validate_corrective_action = fields.Html(string="D6:Implement & Validate Corrective Actions")
|
||||
preventive_action = fields.Html(string="D7:Preventive Recurrence")
|
||||
capa_status = fields.Selection([ ('open', 'OPEN'),('close', 'Close')], default='open' , string="Status")
|
||||
status_reviewed_by = fields.Many2one('res.users', string='Status Reviewed By')
|
||||
status_reviewed_image = fields.Image(related="status_reviewed_by.signature_image",string='Status Reviewed Out Sign',readonly=True)
|
||||
|
|
@ -76,6 +81,11 @@ class SOS_CCRF(models.Model):
|
|||
rootcause_verifiedout_image = fields.Image(related="rootcause_verifiedout_by.signature_image",string='Root Cause Verified By Sign',readonly=True)
|
||||
rootcause_verifiedout_on = fields.Datetime(string="Root Cause Verified On")
|
||||
helper_field = fields.Many2many('sos_fg', string="Helper Field")
|
||||
team_recognition = fields.Html(string="Team Recognition (Image)")
|
||||
capa_line_ids = fields.One2many('sos_ccrf_capa_line', 'ccrf_id', string="CAPA Line Ids",copy=True)
|
||||
team_formation_line_ids = fields.One2many('sos_ccrf_team_formation_line', 'ccrf_id', string="Team Formation Line Ids",copy=True)
|
||||
problem_description_line_ids = fields.One2many('sos_ccrf_problem_description_line', 'ccrf_id', string="Problem Description Line Ids",copy=True)
|
||||
|
||||
@api.onchange('fg_name')
|
||||
def _onchange_fg_name(self):
|
||||
if self.fg_name:
|
||||
|
|
@ -141,3 +151,37 @@ class SOS_CCRF(models.Model):
|
|||
'rootcause_verifiedout_on'
|
||||
)
|
||||
|
||||
class CCRF_Model_CAPA_Line(models.Model):
|
||||
_name = 'sos_ccrf_capa_line'
|
||||
_description = 'CAPA Lines'
|
||||
|
||||
ccrf_id = fields.Many2one('sos_ccrf', string="CCRF Reference", ondelete="cascade")
|
||||
issue = fields.Char(string="Issue")
|
||||
corrective_action = fields.Html(string="D5:Permanent Corrective Action")
|
||||
implement_validate_corrective_action = fields.Html(string="D6:Implement & Validate Corrective Actions")
|
||||
preventive_action = fields.Html(string="D7:Preventive Recurrence")
|
||||
|
||||
class CCRF_Model_TEAM_FORMATION_Line(models.Model):
|
||||
_name = 'sos_ccrf_team_formation_line'
|
||||
_description = 'Team Formation Lines'
|
||||
|
||||
ccrf_id = fields.Many2one('sos_ccrf', string="CCRF Reference", ondelete="cascade")
|
||||
name = fields.Char(string="Name")
|
||||
department = fields.Char(string="Department")
|
||||
role = fields.Char(string="Role")
|
||||
|
||||
|
||||
class CCRF_Model_PROBLEM_DESCRIPTION_Line(models.Model):
|
||||
_name = 'sos_ccrf_problem_description_line'
|
||||
_description = 'Problem Description Lines'
|
||||
|
||||
ccrf_id = fields.Many2one('sos_ccrf', string="CCRF Reference", ondelete="cascade")
|
||||
clear_statement = fields.Text(string="Clear Statement")
|
||||
photos = fields.Html(string="Photos")
|
||||
what = fields.Text(string="What")
|
||||
where = fields.Text(string="Where")
|
||||
when = fields.Text(string="When")
|
||||
why = fields.Text(string="Why")
|
||||
who = fields.Text(string="Who")
|
||||
how = fields.Text(string="How")
|
||||
how_many = fields.Text(string="How many")
|
||||
|
|
@ -288,7 +288,7 @@ class Sequence_Generator(models.AbstractModel):
|
|||
reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)])
|
||||
email_to = reporting_user.login
|
||||
else:
|
||||
email_to = "ramachandran.r@sosaley.in"
|
||||
email_to = "ramachandran.r@sosaley.com"
|
||||
mail_values = {
|
||||
'subject': subject,
|
||||
'body_html': body_html,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ class sos__dc(models.Model):
|
|||
dept_in_charge_image = fields.Image(related="dept_in_charge_name.signature_image",string='Department In-Charge Sign',readonly=True)
|
||||
dept_in_charge_approved_on = fields.Datetime(string="Approved On")
|
||||
remarks = fields.Text(string="Remarks")
|
||||
|
||||
courier = fields.Char(string="Courier Name")
|
||||
lr_no = fields.Char(string="LR No")
|
||||
@api.onchange('dock_audit_no')
|
||||
def _onchange_dock_audit_no(self):
|
||||
if self.dock_audit_no:
|
||||
|
|
@ -220,7 +221,7 @@ class sos__dc(models.Model):
|
|||
"""
|
||||
subject = f"Delivery Challan Approval Request - {self.dc_no}"
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_dc",self.id,"ramachandran.r@sosaley.in",subject,body_html)
|
||||
send_email.send_direct_email(self.env,"sos_dc",self.id,"ramachandran.r@sosaley.com",subject,body_html)
|
||||
# Email part ends
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
sequence_util.action_assign_signature(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,12 @@ class SosTestingParameters(models.Model):
|
|||
communication_type = fields.Selection([
|
||||
('wired', 'Wired'),
|
||||
('wireless', 'Wireless')
|
||||
], string="Communication Type", default='wired')
|
||||
], string="Communication Type")
|
||||
slave_type = fields.Selection([
|
||||
('15S Individual Slave', '15S Individual Slave'),
|
||||
('90S Electrical Panel', '90S Electrical Panel'),
|
||||
('60S Electrical Panel', '60S Electrical Panel')
|
||||
], string="Slave Type")
|
||||
fg_ids = fields.One2many('sos_fg_deliverables', 'ref_id', string= 'FG Deliverables',copy=True)
|
||||
sfg_ids = fields.One2many('sos_sfg_deliverables', 'ref_id', string= 'SFG Deliverables',copy=True)
|
||||
material_ids = fields.One2many('sos_material_deliverables', 'ref_id', string='Material Deliverables',copy=True)
|
||||
|
|
@ -57,6 +62,7 @@ class SOS_SFG_Deliverables(models.Model):
|
|||
item_type = fields.Selection([
|
||||
('Master Panel', 'Master Panel'),
|
||||
('CT Module', 'CT Module'),
|
||||
('Battery Count', 'Battery Count'),
|
||||
('Slave Module', 'Slave Module'),
|
||||
('Internet Module', 'Internet Module')
|
||||
], string="Type",default="Master Panel")
|
||||
|
|
@ -82,6 +88,7 @@ class SOS_Material_Deliverables(models.Model):
|
|||
item_type = fields.Selection([
|
||||
('Master Panel', 'Master Panel'),
|
||||
('CT Module', 'CT Module'),
|
||||
('Battery Count', 'Battery Count'),
|
||||
('Slave Module', 'Slave Module'),
|
||||
('Internet Module', 'Internet Module')
|
||||
], string="Type",default="Master Panel")
|
||||
|
|
|
|||
|
|
@ -8,4 +8,34 @@ class sos_department(models.Model):
|
|||
|
||||
|
||||
name = fields.Char(string="Department Name")
|
||||
short_form = fields.Char(string="Short Text")
|
||||
process_incharge = fields.Many2one('res.users', string='Process Incharge')
|
||||
users_line_ids = fields.One2many('sos_departments_user_lines','ref_id', string='Users')
|
||||
|
||||
_sql_constraints = [
|
||||
('uniq_department_name', 'unique(name)', 'Department name must be unique.'),
|
||||
]
|
||||
class sos_department_line(models.Model):
|
||||
_name = 'sos_departments_user_lines'
|
||||
_description = 'Users in department'
|
||||
|
||||
ref_id = fields.Many2one('sos_departments', string="Departments", ondelete="cascade")
|
||||
users = fields.Many2one('res.users', string='Users')
|
||||
# @api.onchange('users')
|
||||
# def _onchange_users(self):
|
||||
# if self.users:
|
||||
# domain = [('users', '=', self.users.id)]
|
||||
# if self.id and isinstance(self.id, int): # Only add if it's a real ID
|
||||
# domain.append(('id', '!=', self.id))
|
||||
|
||||
# existing_line = self.env['sos_departments_user_lines'].search(domain, limit=1)
|
||||
# if existing_line:
|
||||
# return {
|
||||
# 'warning': {
|
||||
# 'title': 'User Already Assigned',
|
||||
# 'message': f"This user is already assigned to {existing_line.ref_id.name}."
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
|
||||
|
|
@ -96,7 +96,7 @@ class sos_Disposal_Register(models.Model):
|
|||
<p>Below Item is waiting for your approval to Dispose</p>
|
||||
"""
|
||||
|
||||
sequence_util.send_direct_email(self.env,"sos_disposal_register",self.id,"ramachandran.r@sosaley.in","Disposal Approved",body_html)
|
||||
sequence_util.send_direct_email(self.env,"sos_disposal_register",self.id,"ramachandran.r@sosaley.com","Disposal Approved",body_html)
|
||||
# Email part ends
|
||||
return sequence_util.action_assign_signature(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ class SOS_Dock_Audit(models.Model):
|
|||
],
|
||||
string="Product Type",required=True)
|
||||
quantity = fields.Integer(string="Quantity")
|
||||
warranty = fields.Integer(string="Warranty(In Months)")
|
||||
invoice_no = fields.Char(string="Invoice No")
|
||||
invoice_date = fields.Date(string="Invoice Date")
|
||||
customer_name = fields.Char(string="Customer Name")
|
||||
lead_time = fields.Datetime(string="Lead Time")
|
||||
customer_po_no = fields.Char(string="PO No")
|
||||
|
|
@ -140,6 +142,18 @@ class SOS_Dock_Audit(models.Model):
|
|||
})
|
||||
|
||||
def action_acc_esign_btn(self):
|
||||
required_fields = ['payment_status', 'invoice_date','invoice_no', 'billing_address', 'gst_no', 'shipping_address']
|
||||
missing_fields = []
|
||||
|
||||
for field in required_fields:
|
||||
if not self[field]: # Check if field is empty/False
|
||||
missing_fields.append(field)
|
||||
|
||||
# If any fields are missing, show warning
|
||||
if missing_fields:
|
||||
warning_msg = "Please fill the following required fields before proceeding:\n"
|
||||
warning_msg += "\n".join([f"👉 {field.replace('_', ' ').title()}" for field in missing_fields])
|
||||
raise UserError(warning_msg)
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
sequence_util.action_assign_signature(
|
||||
self,
|
||||
|
|
@ -151,7 +165,7 @@ class SOS_Dock_Audit(models.Model):
|
|||
body_html = f"""
|
||||
<p>Below Dock Audit is waiting for your Approval</p>
|
||||
"""
|
||||
sequence_util.send_direct_email(self.env,"sos_dock_audit",self.id,"ramachandran.r@sosaley.in","Dock Audit Approval",body_html)
|
||||
sequence_util.send_direct_email(self.env,"sos_dock_audit",self.id,"ramachandran.r@sosaley.com","Dock Audit Approval",body_html)
|
||||
# Email part ends
|
||||
def action_auditor_esign_btn(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
|
|
@ -206,7 +220,7 @@ class SOS_Dock_Audit(models.Model):
|
|||
self.shipping_address = self.deliverables_boq_id.sales_id.shipping_address
|
||||
self.gst_no = self.deliverables_boq_id.sales_id.gst_no
|
||||
self.payment_status = self.deliverables_boq_id.sales_id.payment_status
|
||||
self.customer_name = self.deliverables_boq_id.sales_id.customer_name
|
||||
self.customer_name = self.deliverables_boq_id.sales_id.customer_name.customer_name
|
||||
self.fg_name = self.deliverables_boq_id.sales_id.fg_name
|
||||
self.quantity = self.deliverables_boq_id.sales_id.qty
|
||||
self.lead_time = self.deliverables_boq_id.sales_id.lead_time
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ class SOS_FG_Plan(models.Model):
|
|||
prepared_by = fields.Many2one('res.users', string='Planned By')
|
||||
prepared_image = fields.Image(related="prepared_by.signature_image",string='Prepared By Sign',readonly=True)
|
||||
prepared_on = fields.Datetime(string="Planned On")
|
||||
|
||||
accounts_approved_on = fields.Datetime(string="Accounts Approved On")
|
||||
accounts_approved_name = fields.Many2one('res.users', string='Accounts Sign')
|
||||
accounts_approved_by_image = fields.Image(related="accounts_approved_name.signature_image",string='Accounts Sign',readonly=True)
|
||||
|
||||
blowup = fields.Boolean(string="Blowup Done",default=False)
|
||||
target_date = fields.Date(string="Target Date",required=True)
|
||||
indent_start_date = fields.Date(string="Indent Start Date", default=lambda self: fields.Date.context_today(self))
|
||||
|
|
@ -116,30 +121,46 @@ class SOS_FG_Plan(models.Model):
|
|||
month = today.strftime('%m')
|
||||
base_sequence_prefix = f"SOS/{form_name}/{fy}/{month}/"
|
||||
|
||||
# Search for latest record for this fiscal year (not just this month)
|
||||
records = self.env[model_name].sudo().search(
|
||||
[(field_name, 'like', f"{base_sequence_prefix}%")],
|
||||
[(field_name, 'like', f"SOS/{form_name}/{fy}/%")],
|
||||
order=f"{field_name} desc",
|
||||
limit=1
|
||||
)
|
||||
|
||||
if records:
|
||||
last_sequence = records[0][field_name]
|
||||
last_suffix = last_sequence.split('/')[-1]
|
||||
if last_suffix.isdigit():
|
||||
new_suffix = f"{last_suffix}a"
|
||||
parts = last_sequence.split('/')
|
||||
last_month = parts[-2]
|
||||
last_suffix = parts[-1]
|
||||
|
||||
# Extract numeric part only (ignore trailing alphabet)
|
||||
numeric_part = ''.join(filter(str.isdigit, last_suffix))
|
||||
|
||||
if last_month != month:
|
||||
# New month: increment numeric part, no suffix
|
||||
new_number = int(numeric_part) + 1
|
||||
new_suffix = f"{new_number:03d}"
|
||||
else:
|
||||
base_num = last_suffix[:-1]
|
||||
last_alpha = last_suffix[-1]
|
||||
next_alpha = chr(ord(last_alpha) + 1) if last_alpha < 'z' else 'a'
|
||||
new_suffix = f"{base_num}{next_alpha}"
|
||||
# Same month: check for alphabet suffix
|
||||
alpha_part = ''.join(filter(str.isalpha, last_suffix))
|
||||
if alpha_part:
|
||||
next_alpha = chr(ord(alpha_part) + 1) if alpha_part < 'z' else 'a'
|
||||
new_suffix = f"{numeric_part}{next_alpha}"
|
||||
else:
|
||||
new_suffix = '016'
|
||||
# First duplicate in same month
|
||||
new_suffix = f"{numeric_part}a"
|
||||
else:
|
||||
# No previous records at all
|
||||
new_suffix = '001'
|
||||
|
||||
return f"{base_sequence_prefix}{new_suffix}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def _get_default_lines(self,model,column):
|
||||
products = self.env[model].search([])
|
||||
default_lines = []
|
||||
|
|
@ -170,9 +191,10 @@ class SOS_FG_Plan(models.Model):
|
|||
|
||||
|
||||
def send_indent_plan_email(self, email_ids):
|
||||
valid_emails = [email for email in email_ids if email]
|
||||
template = self.env.ref('sos_inventory.send_indent_plan_email_template')
|
||||
if template:
|
||||
template.email_to = ','.join(email_ids)
|
||||
template.email_to = ','.join(valid_emails)
|
||||
template.send_mail(self.id, force_send=True)
|
||||
def get_unique_emails(self):
|
||||
group_refs = [
|
||||
|
|
@ -290,6 +312,21 @@ class SOS_FG_Plan(models.Model):
|
|||
else:
|
||||
worksheet.write(row, 1, value)
|
||||
|
||||
def action_acc_approver_esign_btn(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
body_html = f"""
|
||||
<p>Below <b>Indent Budget</b> is waiting for your Approval</p>
|
||||
"""
|
||||
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_fg_plan",self.id,"ramachandran.r@sosaley.com","Indent Budget Approval",body_html)
|
||||
result = sequence_util.action_assign_signature(
|
||||
self,
|
||||
'accounts_approved_name',
|
||||
'accounts_approved_on',
|
||||
'sos_inventory.sos_finance_user'
|
||||
)
|
||||
|
||||
|
||||
def action_top_approver_esign_btn(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
|
|
@ -334,11 +371,9 @@ class SOS_FG_Plan(models.Model):
|
|||
body_html = f"""
|
||||
<p>Below <b>Indent Budget</b> is waiting for your Approval</p>
|
||||
"""
|
||||
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_fg_plan",self.id,"ramachandran.r@sosaley.in","Indent Budget Approval",body_html)
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
result = sequence_util.action_assign_signature(
|
||||
send_email.send_group_email(self.env,'sos_fg_plan',self.id,"deenalaura.m@sosaley.in","Indent Budget Approval",body_html,'sos_inventory.sos_finance_user')
|
||||
result = send_email.action_assign_signature(
|
||||
self,
|
||||
'prepared_by',
|
||||
'prepared_on'
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class FIR_BRR(models.Model):
|
|||
sos_record.target_date ,
|
||||
date.today()
|
||||
)
|
||||
week_number = min(week_number, 8)
|
||||
field_name = f'qc_week_{week_number}'
|
||||
fgplan_field_name = f'planned_week_{week_number}'
|
||||
# FG Plan update
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ class sos__grn(models.Model):
|
|||
print(f"Failed to find report action: {e}")
|
||||
|
||||
def action_report_esign_btn(self):
|
||||
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
sequence_util.action_assign_signature(
|
||||
self,
|
||||
|
|
@ -84,6 +85,7 @@ class sos__grn(models.Model):
|
|||
'stores_approval_on',
|
||||
'sos_inventory.sos_scg_group_user'
|
||||
)
|
||||
self.generate_supplier_service()
|
||||
if self.received_goods_type == "Materials":
|
||||
for item in self.line_ids:
|
||||
component = self.env['sos_material'].browse(item.component_id.id)
|
||||
|
|
@ -148,7 +150,180 @@ class sos__grn(models.Model):
|
|||
def _compute_sequence(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.generate_sequence('sos_grn','GRN', 'grn_no')
|
||||
def generate_supplier_service(self):
|
||||
|
||||
grn = self.env['sos_grn'].browse(self.id)
|
||||
|
||||
if self.received_goods_type == "Materials":
|
||||
# Check for existing register line
|
||||
already_created = self.env['sos_mat_outsourcing_vendor_register_lines'].search([
|
||||
('ref_id.supplier_name', '=', grn.supplier_name.id),
|
||||
('po_no', '=', grn.po_no.id)
|
||||
], limit=1)
|
||||
# Get or create the main register
|
||||
outsource = self.env['sos_mat_outsourcing_vendor_register'].search(
|
||||
[('supplier_name', '=', grn.supplier_name.id)], limit=1)
|
||||
if not outsource:
|
||||
outsource = self.env['sos_mat_outsourcing_vendor_register'].create({
|
||||
'supplier_name': grn.supplier_name.id,
|
||||
})
|
||||
ir_records = self.env['sos_ir'].search([('po_no', '=', grn.po_no.id)])
|
||||
iqi_records = self.env['sos_iqi'].search([('ir_id_unique_id', 'in', ir_records.ids)])
|
||||
grn_records = self.env['sos_grn'].search([('po_no', '=', grn.po_no.id)])
|
||||
grn_line_records = self.env['sos_grn_line'].search([('grn_id', 'in', grn_records.ids)])
|
||||
|
||||
# Prepare material_names list
|
||||
mat_record = [line.component_id.id for line in grn_line_records if line.component_id and line.component_id.part_no]
|
||||
material_names = [(6, 0, mat_record)]
|
||||
|
||||
# Sum all GRN line quantities including current
|
||||
total_received_qty = sum(grn_line_records.mapped('received_qty')) #+ new_received
|
||||
total_approved_qty = sum(grn_line_records.mapped('approved_qty')) #+ new_approved
|
||||
total_rejected_qty = sum(grn_line_records.mapped('rejected_qty')) #+ new_rejected
|
||||
|
||||
delivery_values = grn_records.mapped('delivery')
|
||||
delivery_numbers = [float(value) for value in delivery_values if value]
|
||||
existing_count = len(delivery_numbers)
|
||||
|
||||
responsive_values = grn_records.mapped('responsiveness')
|
||||
responsive_numbers = [float(value) for value in responsive_values if value]
|
||||
|
||||
reports_values = grn_records.mapped('reports')
|
||||
reports_numbers = [float(value) for value in reports_values if value]
|
||||
|
||||
avg_delivery_marks = sum(delivery_numbers) / (existing_count)
|
||||
delivery = (
|
||||
30 if 27.5 < avg_delivery_marks <= 30 else
|
||||
25 if 22.5 < avg_delivery_marks <= 27.5 else
|
||||
10 if avg_delivery_marks < 20 else
|
||||
0
|
||||
)
|
||||
|
||||
avg_report_marks = sum(reports_numbers) / (existing_count)
|
||||
report = (
|
||||
30 if 27.5 < avg_report_marks <= 30 else
|
||||
25 if 22.5 < avg_report_marks <= 27.5 else
|
||||
10 if avg_report_marks < 20 else
|
||||
0
|
||||
)
|
||||
|
||||
avg_responsiveness_marks = sum(responsive_numbers) / (existing_count)
|
||||
|
||||
responsiveness = (
|
||||
10 if 5 <= avg_responsiveness_marks <= 10 else
|
||||
5 if avg_responsiveness_marks < 5 else
|
||||
0
|
||||
)
|
||||
if already_created:
|
||||
updated_vals = {
|
||||
'received_qty': total_received_qty,
|
||||
'approved_qty': total_approved_qty,
|
||||
'rejected_qty': total_rejected_qty,
|
||||
'delivery_marks': delivery,
|
||||
'responsiveness_marks': responsiveness,
|
||||
'report_marks': report,
|
||||
'iqi_references': [(6, 0, iqi_records.ids)],
|
||||
'grn_references': [(6, 0, grn_records.ids)],
|
||||
'material_names': material_names,
|
||||
}
|
||||
already_created.write(updated_vals)
|
||||
else:
|
||||
self.env['sos_mat_outsourcing_vendor_register_lines'].create({
|
||||
'ref_id': outsource.id,
|
||||
'po_no': grn.po_no.id,
|
||||
'received_qty': total_received_qty,
|
||||
'approved_qty': total_approved_qty,
|
||||
'rejected_qty': total_rejected_qty,
|
||||
'iqi_references': [(6, 0, iqi_records.ids)],
|
||||
'grn_references': [(6, 0, grn_records.ids)],
|
||||
'material_names': material_names,
|
||||
'delivery_marks': delivery,
|
||||
'responsiveness_marks': responsiveness,
|
||||
'report_marks': report,
|
||||
})
|
||||
|
||||
elif self.received_goods_type == "SFG":
|
||||
|
||||
# Check for existing register line
|
||||
already_created = self.env['sos_outsourcing_vendor_monitoring_register_lines'].search([
|
||||
('ref_id.service_provider_name', '=', grn.service_provider_name.id),
|
||||
('wo_no', '=', grn.wo_no.id)
|
||||
], limit=1)
|
||||
|
||||
# Get or create the main register
|
||||
outsource = self.env['sos_outsourcing_vendor_monitoring_register'].search(
|
||||
[('service_provider_name', '=', grn.service_provider_name.id)], limit=1)
|
||||
if not outsource:
|
||||
outsource = self.env['sos_outsourcing_vendor_monitoring_register'].create({
|
||||
'service_provider_name': grn.service_provider_name.id,
|
||||
})
|
||||
# Related documents
|
||||
ir_records = self.env['sos_ir'].search([('wo_no', '=', grn.wo_no.id)])
|
||||
iqi_records = self.env['sos_iqi'].search([('ir_id_unique_id', 'in', ir_records.ids)])
|
||||
dc_records = self.env['sos_dc'].search([('wo_no', '=', grn.wo_no.id)])
|
||||
grn_records = self.env['sos_grn'].search([('wo_no', '=', grn.wo_no.id)])
|
||||
|
||||
grn_line_records = self.env['sos_grn_line_sfg'].search([('grn_id', 'in', grn_records.ids)])
|
||||
|
||||
# Prepare material_names list
|
||||
sfg_ids = [line.component_id.id for line in grn_line_records if line.component_id and line.component_id.name]
|
||||
|
||||
# Include current line component manually if not yet saved in DB
|
||||
current_component_id = self.line_ids.component_id
|
||||
if current_component_id and current_component_id not in sfg_ids:
|
||||
sfg_ids.append(current_component_id)
|
||||
|
||||
sfg_names = [(6, 0, sfg_ids)]
|
||||
|
||||
total_received_qty = sum(grn_line_records.mapped('received_qty'))
|
||||
total_approved_qty = sum(grn_line_records.mapped('approved_qty'))
|
||||
total_rejected_qty = sum(grn_line_records.mapped('rejected_qty'))
|
||||
|
||||
delivery_values = grn_line_records.mapped('delivery')
|
||||
delivery_numbers = [float(value) for value in delivery_values if value]
|
||||
existing_count = len(delivery_numbers)
|
||||
|
||||
responsive_values = grn_line_records.mapped('responsiveness')
|
||||
responsive_numbers = [float(value) for value in responsive_values if value]
|
||||
|
||||
reports_values = grn_line_records.mapped('reports')
|
||||
reports_numbers = [float(value) for value in reports_values if value]
|
||||
|
||||
avg_delivery_marks = sum(delivery_numbers) / (existing_count)
|
||||
avg_responsiveness_marks = sum(responsive_numbers) / (existing_count)
|
||||
avg_report_marks = sum(reports_numbers) / (existing_count)
|
||||
|
||||
if already_created:
|
||||
#print(f" sfg_names : if {sfg_names}")
|
||||
updated_vals = {
|
||||
'received_qty': total_received_qty,
|
||||
'approved_qty': total_approved_qty,
|
||||
'rejected_qty': total_rejected_qty,
|
||||
'delivery_marks': avg_delivery_marks,
|
||||
'responsiveness_marks': avg_responsiveness_marks,
|
||||
'report_marks': avg_report_marks,
|
||||
'iqi_references': [(6, 0, iqi_records.ids)],
|
||||
'dc_references': [(6, 0, dc_records.ids)],
|
||||
'grn_references': [(6, 0, grn_records.ids)],
|
||||
'sfg_names': sfg_names,
|
||||
}
|
||||
already_created.write(updated_vals)
|
||||
else:
|
||||
#print(f" sfg_names : else {sfg_names}")
|
||||
self.env['sos_outsourcing_vendor_monitoring_register_lines'].create({
|
||||
'ref_id': outsource.id,
|
||||
'iqi_references': [(6, 0, iqi_records.ids)],
|
||||
'dc_references': [(6, 0, dc_records.ids)],
|
||||
'grn_references': [(6, 0, grn_records.ids)],
|
||||
'sfg_names': sfg_names,
|
||||
'wo_no': grn.wo_no.id,
|
||||
'received_qty': total_received_qty,
|
||||
'approved_qty': total_approved_qty,
|
||||
'rejected_qty': total_rejected_qty,
|
||||
'delivery_marks': avg_delivery_marks,
|
||||
'responsiveness_marks': avg_responsiveness_marks,
|
||||
'report_marks': avg_report_marks,
|
||||
})
|
||||
class sos_grn_line(models.Model):
|
||||
_name = 'sos_grn_line'
|
||||
_description = 'GRN Material Lines'
|
||||
|
|
@ -169,14 +344,19 @@ class sos_grn_line(models.Model):
|
|||
@api.depends('received_qty', 'rejected_qty')
|
||||
def _compute_quality(self):
|
||||
for record in self:
|
||||
if record.rejected_qty != 0:
|
||||
record.quality_marks = (100 - ((record.rejected_qty / record.received_qty) * 100))
|
||||
if record.quality_marks >= 80 and record.quality_marks <=99:
|
||||
record.quality_marks = 25
|
||||
record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty)
|
||||
|
||||
|
||||
def _calculate_quality_marks(self, received, rejected):
|
||||
if received != 0:
|
||||
q = 100 - ((rejected / received) * 100)
|
||||
if 91 <= q <= 100:
|
||||
return 30
|
||||
elif 80 <= q <= 90:
|
||||
return 25
|
||||
else:
|
||||
record.quality_marks = 10
|
||||
else:
|
||||
record.quality_marks = 100
|
||||
return 10
|
||||
return 0
|
||||
|
||||
@api.model
|
||||
def write(self, vals):
|
||||
|
|
@ -295,14 +475,20 @@ class sos_grn_line_sfg(models.Model):
|
|||
@api.depends('received_qty', 'rejected_qty')
|
||||
def _compute_sfgquality(self):
|
||||
for record in self:
|
||||
if record.rejected_qty != 0:
|
||||
record.quality_marks = (100 - ((record.rejected_qty / record.received_qty) * 100))
|
||||
if record.quality_marks >= 80 and record.quality_marks <=99:
|
||||
record.quality_marks = 25
|
||||
record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty)
|
||||
|
||||
|
||||
|
||||
def _calculate_quality_marks(self, received, rejected):
|
||||
if received != 0:
|
||||
q = 100 - ((rejected / received) * 100)
|
||||
if 91 <= q <= 100:
|
||||
return 30
|
||||
elif 80 <= q <= 90:
|
||||
return 25
|
||||
else:
|
||||
record.quality_marks = 10
|
||||
else:
|
||||
record.quality_marks = 100
|
||||
return 10
|
||||
return 0
|
||||
|
||||
class sos_grn_line_fg(models.Model):
|
||||
_name = 'sos_grn_line_fg'
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@ class SOS_IR(models.Model):
|
|||
|
||||
|
||||
else:
|
||||
if item.qty > 0:
|
||||
iqi_record = self.env['sos_iqi'].create({
|
||||
'iqi_no': sequence_util.generate_sequence('sos_iqi', 'IQI', 'iqi_no'),
|
||||
'material_option':True,
|
||||
|
|
@ -383,7 +384,7 @@ class SOS_IR(models.Model):
|
|||
"""
|
||||
subject = f"Inward Approval Request - {self.ir_no}"
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_ir",self.id,"ramachandran.r@sosaley.in",subject,body_html)
|
||||
send_email.send_direct_email(self.env,"sos_ir",self.id,"ramachandran.r@sosaley.com",subject,body_html)
|
||||
# Email part ends
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.action_assign_signature(
|
||||
|
|
|
|||
|
|
@ -35,11 +35,25 @@ class SOS_Mat_Oursourcing_Monitor_Lines(models.Model):
|
|||
rejection_percentage = fields.Float(string="Rejected Percentage", compute="_compute_rejected_percentage", store=True)
|
||||
rejection_percentage_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display')
|
||||
ppm = fields.Integer(string="PPM",compute="_compute_ppm")
|
||||
|
||||
quality_marks = fields.Float(string="Quality Marks")
|
||||
quality_marks = fields.Float(string="Quality Marks",compute="_compute_sfgquality")
|
||||
delivery_marks = fields.Float(string="Delivery Marks")
|
||||
responsiveness_marks = fields.Float(string="Responsiveness Marks")
|
||||
report_marks = fields.Float(string="Report Marks")
|
||||
def _calculate_quality_marks(self, received, rejected):
|
||||
if received != 0:
|
||||
q = 100 - ((rejected / received) * 100)
|
||||
if 91 <= q <= 100:
|
||||
return 30
|
||||
elif 80 <= q <= 90:
|
||||
return 25
|
||||
else:
|
||||
return 10
|
||||
return 0
|
||||
|
||||
@api.depends('received_qty', 'rejected_qty')
|
||||
def _compute_sfgquality(self):
|
||||
for record in self:
|
||||
record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty)
|
||||
|
||||
@api.depends('rejection_percentage')
|
||||
def _compute_rejected_percentage_display(self):
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class sos__mon(models.Model):
|
|||
filled_by = fields.Many2one('res.users', string='Filled By', readonly=True,required=True,default=lambda self: self.env.user)
|
||||
logged_inuser_group=fields.Boolean(string='Group Name',compute='compute_user_grp',store=True)
|
||||
dept = fields.Many2one('sos_departments',string="Department")
|
||||
customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name")
|
||||
purpose = fields.Char(string="Purpose")
|
||||
product_name = fields.Many2one('sos_fg', string='Material/Product Name & No')
|
||||
indent_ref_no = fields.Many2one('sos_fg_plan',string="Indent Reference No")
|
||||
|
|
@ -33,7 +34,7 @@ class sos__mon(models.Model):
|
|||
top_management_name = fields.Many2one('res.users', string='Top Management Approver')
|
||||
top_management_approval_image = fields.Image(related="top_management_name.signature_image",string='Top Management Approval',readonly=True)
|
||||
top_management_approved_on = fields.Datetime(string="Approved On")
|
||||
|
||||
deliverables_boq_id = fields.Many2one('sos_deliverables_boq', string="Load From Deliverables/BOQ Id")
|
||||
stores_approved_by = fields.Many2one('res.users', string='Stores Approved By')
|
||||
stores_approved_image = fields.Image(related="stores_approved_by.signature_image",string='Stores Approval Sign',readonly=True)
|
||||
stores_approved_on = fields.Datetime(string="Approved On")
|
||||
|
|
@ -43,9 +44,9 @@ class sos__mon(models.Model):
|
|||
('fg', 'FG')
|
||||
], string='Auto Load Items' ,default=False)
|
||||
material_option = fields.Boolean('Materials', default=True)
|
||||
sfg_option = fields.Boolean('Semi-Finished Goods')
|
||||
fg_option = fields.Boolean('Finished Goods')
|
||||
order_type = fields.Char(string='order_type',copy=True)
|
||||
sfg_option = fields.Boolean('Semi-Finished Goods', default=False)
|
||||
fg_option = fields.Boolean('Finished Goods', default=False)
|
||||
order_type = fields.Char(string='order_type',copy=True,compute="_compute_order_type")
|
||||
line_ids_material = fields.One2many('sos_mon_line_material', 'mon_id', string="Materials",copy=True)
|
||||
line_ids_sfg = fields.One2many('sos_mon_line_sfg', 'mon_id', string="Semi-Finished Goods",copy=True)
|
||||
line_ids_fg = fields.One2many('sos_mon_line_fg', 'mon_id', string="Finished Goods",copy=True)
|
||||
|
|
@ -73,6 +74,74 @@ class sos__mon(models.Model):
|
|||
approx_value = fields.Monetary(compute='_compute_approx_value', string="Approximate Value", currency_field='currency_id', readonly=True,store=True)
|
||||
status = fields.Selection([ ('open', 'Open'),('close', 'Closed')], default='open' , string="Status")
|
||||
active = fields.Boolean(default=True)
|
||||
is_ce_user_created = fields.Boolean(
|
||||
compute='_compute_is_ce_user_created',
|
||||
store=True,
|
||||
string='Created by CE User'
|
||||
)
|
||||
@api.depends('create_uid')
|
||||
def _compute_is_ce_user_created(self):
|
||||
ce_groups = [
|
||||
self.env.ref('sos_inventory.sos_ce_user').id
|
||||
]
|
||||
for record in self:
|
||||
record.is_ce_user_created = any(
|
||||
gid in record.create_uid.groups_id.ids for gid in ce_groups
|
||||
)
|
||||
@api.depends('material_option', 'sfg_option', 'fg_option')
|
||||
def _compute_order_type(self):
|
||||
for rec in self:
|
||||
parts = []
|
||||
if rec.material_option:
|
||||
parts.append('Material')
|
||||
if rec.sfg_option:
|
||||
parts.append('SFG')
|
||||
if rec.fg_option:
|
||||
parts.append('FG')
|
||||
rec.order_type = ",".join(parts) if parts else False
|
||||
@api.onchange('deliverables_boq_id')
|
||||
def _onchange_deliverables_boq_id(self):
|
||||
self.material_option = True
|
||||
self.sfg_option = True
|
||||
self.fg_option = True
|
||||
if self.deliverables_boq_id:
|
||||
self.line_ids_material = [(5, 0, 0)] # Clear existing records
|
||||
self.line_ids_material = [
|
||||
(0, 0, {
|
||||
'component_id': line.component_id, # Replace 'field1' with the actual field names
|
||||
'uom': line.uom,
|
||||
'material_code': line.material_code,
|
||||
'quantity': line.quantity
|
||||
|
||||
# Add other fields to copy here
|
||||
}) for line in self.deliverables_boq_id.line_ids_installation_kit
|
||||
]
|
||||
self.line_ids_fg = [(5, 0, 0)] # Clear existing records
|
||||
self.line_ids_fg = [
|
||||
(0, 0, {
|
||||
'component_id': line.component_id, # Replace 'field1' with the actual field names
|
||||
'quantity': line.quantity
|
||||
}) for line in self.deliverables_boq_id.line_ids_fg
|
||||
]
|
||||
self.line_ids_sfg = [(5, 0, 0)] # Clear existing records
|
||||
self.line_ids_sfg = [
|
||||
(0, 0, {
|
||||
'component_id': line.component_id, # Replace 'field1' with the actual field names
|
||||
'quantity': line.quantity
|
||||
# Add other fields to copy here
|
||||
}) for line in self.deliverables_boq_id.line_ids_sfg
|
||||
]
|
||||
self.line_ids_material = [
|
||||
(0, 0, {
|
||||
'uom': line.uom,
|
||||
'material_code': line.material_code,
|
||||
'component_id': line.component_id,
|
||||
'quantity': line.quantity
|
||||
# Add other fields to copy here
|
||||
}) for line in self.deliverables_boq_id.line_ids_material
|
||||
]
|
||||
|
||||
|
||||
@api.constrains('indent_ref_no', 'auto_load_fg_items')
|
||||
def _check_duplicate_fg_items_per_indent(self):
|
||||
for record in self:
|
||||
|
|
@ -461,7 +530,7 @@ class Mon_Line_Material(models.Model):
|
|||
uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram'), ('Packs', 'Packs')], string="Uom")
|
||||
specifications = fields.Char(string="Specifications")
|
||||
quantity = fields.Float(string="Quantity",required=True,default=1)
|
||||
location = fields.Char(string="Location")
|
||||
location = fields.Char(string="Location",related='component_id.location')
|
||||
company_id = fields.Many2one('res.company', store=True, copy=False,
|
||||
string="Company",
|
||||
default=lambda self: self.env.user.company_id.id)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,20 @@ class sos__mrn(models.Model):
|
|||
default=lambda
|
||||
self: self.env.user.company_id.currency_id.id)
|
||||
approx_value = fields.Monetary(compute='_compute_approx_value', string="Approximate Value", currency_field='currency_id', readonly=True)
|
||||
is_ce_user_created = fields.Boolean(
|
||||
compute='_compute_is_ce_user_created',
|
||||
store=True,
|
||||
string='Created by CE User'
|
||||
)
|
||||
@api.depends('create_uid')
|
||||
def _compute_is_ce_user_created(self):
|
||||
ce_groups = [
|
||||
self.env.ref('sos_inventory.sos_ce_user').id
|
||||
]
|
||||
for record in self:
|
||||
record.is_ce_user_created = any(
|
||||
gid in record.create_uid.groups_id.ids for gid in ce_groups
|
||||
)
|
||||
@api.onchange('min_no')
|
||||
def _onchange_min_no(self):
|
||||
if self.min_no:
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@ class NCMR_Model(models.Model):
|
|||
finished_fg_assy = fields.Char()
|
||||
finished_fg_assy_responsibility = fields.Text(string="Production Assy FG Responsibility")
|
||||
description_of_nc = fields.Html(string="Description of Non-Conformities")
|
||||
root_cause_of_nc = fields.Html(string="Root Cause of Non-Conformities")
|
||||
root_cause_of_nc = fields.Html(string="Root Cause",
|
||||
default="<p>Why #1 :</p><p>Why #2 :</p><p>Why #3 :</p><p>Why #4 :</p><p>Why #5 :</p>")
|
||||
containment_action_of_nc = fields.Html(string="Containment Action to close the Non-Conformities")
|
||||
comments_on_capa = fields.Html(string="Comments on Corrective / Preventive Action ")
|
||||
qa_comments=fields.Text(string="QA Comments")
|
||||
|
|
@ -105,6 +106,14 @@ class NCMR_Model(models.Model):
|
|||
rework_rd_approval_sign = fields.Image(related="rework_rd_approval_by.signature_image",string='Rework - R&D In-Charge',readonly=True)
|
||||
rework_rd_approval_on = fields.Datetime(string="Approved On")
|
||||
|
||||
responsible_department = fields.Many2one('sos_departments', string='Department')
|
||||
responsible_name = fields.Many2one('res.users',string='Responsible Person',domain="[('id', 'in', allowed_user_ids)]")
|
||||
allowed_user_ids = fields.Many2many('res.users', compute='_compute_allowed_users')
|
||||
rca_responsible_department = fields.Many2many('sos_departments', string='Department')
|
||||
rca_responsible_name = fields.Many2many('res.users',string='Responsible Person',relation='sos_ncmr_rca_responsible_name_rel',domain="[('id', 'in', rca_allowed_user_ids)]")
|
||||
rca_allowed_user_ids = fields.Many2many('res.users', compute='_rca_compute_allowed_users')
|
||||
|
||||
|
||||
combined_incoming_doc_ref = fields.Reference(
|
||||
selection=[
|
||||
('sos_iqi', 'IQI Ref No'),
|
||||
|
|
@ -120,8 +129,155 @@ class NCMR_Model(models.Model):
|
|||
supplier_name = fields.Many2one('sos_suppliers',string="Supplier Name",compute="_get_supplier_name")
|
||||
service_provider_name = fields.Many2one('sos_service_providers',string="Service Provider Name",compute="_get_service_provider_name")
|
||||
customer_name = fields.Many2one('sos_inventory_customers', string="Customer Name",compute="_get_customer_name")
|
||||
|
||||
outsourcing_return_ref_no = fields.Many2one('sos_sfg_outsourcing_return_register',string="Outsourcing Return Ref No")
|
||||
|
||||
problem_description_line_ids = fields.One2many('sos_ncmr_problem_description_line', 'ncmr_id', string="Problem Description Line Ids",copy=True)
|
||||
|
||||
@api.depends('responsible_department')
|
||||
def _compute_allowed_users(self):
|
||||
for rec in self:
|
||||
rec.allowed_user_ids = rec.responsible_department.users_line_ids.mapped('users')
|
||||
|
||||
@api.depends('rca_responsible_department')
|
||||
def _rca_compute_allowed_users(self):
|
||||
for rec in self:
|
||||
rec.rca_allowed_user_ids = rec.rca_responsible_department.users_line_ids.mapped('users')
|
||||
|
||||
@api.model
|
||||
def get_service_suppliers_by_goods_type(self,goods_type=False,startDate=False, endDate=False):
|
||||
|
||||
if goods_type == 'Material':
|
||||
|
||||
query = """
|
||||
SELECT id,supplier_name
|
||||
FROM sos_suppliers
|
||||
WHERE id IN (
|
||||
SELECT b.supplier_name
|
||||
FROM sos_ncmr a, sos_iqi b
|
||||
WHERE a.incoming_doc_ref = b.id
|
||||
AND a.material_option = 't'
|
||||
AND a.ncmr_date BETWEEN %s AND %s
|
||||
)
|
||||
"""
|
||||
elif goods_type == 'SFG':
|
||||
|
||||
query = """
|
||||
SELECT id,service_provider_name
|
||||
FROM sos_service_providers
|
||||
WHERE id IN (
|
||||
SELECT b.service_provider_name
|
||||
FROM sos_ncmr a, sos_iqi b
|
||||
WHERE a.incoming_doc_ref = b.id
|
||||
AND a.sfg_option = 't'
|
||||
AND a.ncmr_date BETWEEN %s AND %s
|
||||
)
|
||||
"""
|
||||
|
||||
self.env.cr.execute(query,(startDate, endDate))
|
||||
rows = self.env.cr.fetchall()
|
||||
result = [{'id': '', 'supplier_name': 'Select Supplier/Service'}]
|
||||
result += [{'id': row[0], 'supplier_name': row[1]} for row in rows]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@api.model
|
||||
def get_pareto_data(self, start_date=False, end_date=False, goodstype=False, categoryId=False, servicesupplier=False):
|
||||
|
||||
if not start_date or not end_date:
|
||||
raise ValueError("Start date and end date must be provided.")
|
||||
|
||||
base_where_clause = "a.ncmr_date BETWEEN %s AND %s"
|
||||
where_params = [start_date, end_date]
|
||||
base_groupby_clause = "d.name"
|
||||
|
||||
if goodstype == 'Material':
|
||||
join_clause = """
|
||||
JOIN sos_material_defective_line_sos_ncmr_line_rel c ON b.id = c.sos_ncmr_line_id
|
||||
JOIN sos_material_defective_line d ON c.sos_material_defective_line_id = d.id
|
||||
JOIN sos_material_configuration e ON a.material_category = e.id
|
||||
"""
|
||||
if categoryId and categoryId != 'Select Category':
|
||||
join_clause += " JOIN sos_material f ON a.material_name = f.id JOIN sos_material_types g ON f.material_type_id = g.id"
|
||||
base_where_clause += " AND g.name = %s"
|
||||
#base_groupby_clause += ",g.name"
|
||||
where_params.append(categoryId)
|
||||
|
||||
if servicesupplier and servicesupplier !='Select Supplier/Service' and servicesupplier !='undefined':
|
||||
join_clause += " LEFT JOIN sos_iqi h ON a.incoming_doc_ref = h.id LEFT JOIN sos_suppliers i ON h.supplier_name = i.id"
|
||||
base_where_clause += " AND h.supplier_name = %s"
|
||||
base_groupby_clause += ",h.supplier_name"
|
||||
where_params.append(servicesupplier)
|
||||
|
||||
elif goodstype == 'SFG':
|
||||
join_clause = """
|
||||
JOIN sos_ncmr_line_sos_sfg_defective_line_rel c ON b.id = c.sos_ncmr_line_id
|
||||
JOIN sos_sfg_defective_line d ON c.sos_sfg_defective_line_id = d.id
|
||||
JOIN sos_sfg_configuration e ON a.sfg_category = e.id
|
||||
"""
|
||||
if categoryId and categoryId != 'Select Category':
|
||||
join_clause += " JOIN sos_sfg f ON a.sfg_name = f.id"
|
||||
base_where_clause += " AND f.category = %s"
|
||||
base_groupby_clause += ",f.category"
|
||||
where_params.append(categoryId)
|
||||
|
||||
if servicesupplier and servicesupplier !='Select Supplier/Service' and servicesupplier !='undefined':
|
||||
join_clause += " LEFT JOIN sos_iqi g ON a.incoming_doc_ref = g.id LEFT JOIN sos_service_providers h ON g.service_provider_name = h.id"
|
||||
base_where_clause += " AND g.service_provider_name = %s"
|
||||
base_groupby_clause += ",g.service_provider_name"
|
||||
where_params.append(servicesupplier)
|
||||
|
||||
elif goodstype == 'FG':
|
||||
join_clause = """
|
||||
JOIN sos_defectives_sos_ncmr_line_rel c ON b.id = c.sos_ncmr_line_id
|
||||
JOIN sos_defectives d ON c.sos_defectives_id = d.id
|
||||
JOIN sos_testing_parameters f ON a.fg_category = f.id
|
||||
JOIN sos_fg e ON e.id = f.fg_name
|
||||
"""
|
||||
|
||||
if categoryId and categoryId != 'Select Category':
|
||||
base_where_clause += " AND e.fg_type = %s"
|
||||
base_groupby_clause += ",e.fg_type"
|
||||
where_params.append(categoryId)
|
||||
|
||||
# Build the full SQL query dynamically
|
||||
|
||||
query = f"""
|
||||
SELECT
|
||||
defect_name,
|
||||
count,
|
||||
ROUND(count * 100.0 / total, 2) AS individual_percent,
|
||||
ROUND(SUM(count) OVER (ORDER BY count DESC ROWS UNBOUNDED PRECEDING) * 100.0 / total, 2) AS cumulative_percent
|
||||
FROM (
|
||||
SELECT
|
||||
d.name AS defect_name,
|
||||
COUNT(*) AS count,
|
||||
SUM(COUNT(*)) OVER () AS total
|
||||
FROM
|
||||
sos_ncmr a
|
||||
JOIN sos_ncmr_line b ON a.id = b.ncmr_id
|
||||
{join_clause}
|
||||
WHERE
|
||||
{base_where_clause}
|
||||
GROUP BY
|
||||
{base_groupby_clause}
|
||||
) AS sub
|
||||
ORDER BY
|
||||
count DESC;
|
||||
"""
|
||||
|
||||
self.env.cr.execute(query, tuple(where_params))
|
||||
result = self.env.cr.fetchall()
|
||||
|
||||
data = []
|
||||
for row in result:
|
||||
data.append({
|
||||
'defect_name': row[0], # defect_name
|
||||
'count': row[1], # count
|
||||
'cumulative_percent': row[3], # cumulative_percent
|
||||
})
|
||||
|
||||
return data
|
||||
#Excel Write
|
||||
def action_ncmr_report_orm_btn(self, from_date, to_date,force_download=True):
|
||||
output = io.BytesIO()
|
||||
|
|
@ -559,17 +715,18 @@ class NCMR_Model(models.Model):
|
|||
'sos_inventory.sos_qa_user'
|
||||
)
|
||||
else:
|
||||
|
||||
if all_closed or record.aodr_no:
|
||||
self.status = 'closed'
|
||||
if self.rework_action == "inhouse":
|
||||
s_no_list = self.line_ids.mapped('s_no') # Collect all s_no values
|
||||
s_no_str = ', '.join(map(str, s_no_list))
|
||||
iqi_record = self.env['sos_iqi'].create({
|
||||
'iqi_no': send_email.generate_sequence('sos_iqi', 'R-IQI', 'iqi_no'),
|
||||
'material_option':self.material_option,
|
||||
'sfg_option':self.sfg_option,
|
||||
'received_qty': self.rejected_qty,
|
||||
'iqi_type':'rework',
|
||||
'material_name': self.material_name,
|
||||
'material_name': self.material_name.id,
|
||||
'material_code': self.material_name.material_code,
|
||||
'sfg_name': self.sfg_name.id,
|
||||
'sfg_code': self.sfg_name.sfg_code,
|
||||
|
|
@ -577,7 +734,9 @@ class NCMR_Model(models.Model):
|
|||
'service_provider_name':self.incoming_doc_ref.service_provider_name.id,
|
||||
'old_iqi_ref': self.incoming_doc_ref.id,
|
||||
'ir_id_unique_id':self.incoming_doc_ref.ir_id_unique_id.id,
|
||||
'in_tact':self.incoming_doc_ref.in_tact
|
||||
'in_tact':self.incoming_doc_ref.in_tact,
|
||||
'ncmr_ref':self.id,
|
||||
'serial_no':s_no_str
|
||||
# 'test_report':self.incoming_doc_ref.test_report,
|
||||
# 'test_report_no':self.incoming_doc_ref.test_report_no,
|
||||
# 'test_report_doc':self.incoming_doc_ref.test_report_doc,
|
||||
|
|
@ -585,7 +744,7 @@ class NCMR_Model(models.Model):
|
|||
|
||||
|
||||
})
|
||||
|
||||
print(iqi_record)
|
||||
# Rework Iteration starts
|
||||
sos_record = self.env['sos_iqi'].search([('id', '=', self.incoming_doc_ref.id)], limit=1)
|
||||
if sos_record:
|
||||
|
|
@ -622,7 +781,27 @@ class NCMR_Model(models.Model):
|
|||
'sos_inventory.sos_scg_group_user'
|
||||
)
|
||||
if self.rework_action == "inhouse":
|
||||
# Email part
|
||||
if self.material_option:
|
||||
orr_record = self.env['sos_sfg_outsourcing_return_register'].create({
|
||||
'orr_no': orr_no,
|
||||
'iqi_no':self.incoming_doc_ref.id,
|
||||
'iqi_date':self.incoming_doc_ref.iqi_date,
|
||||
'material_name':self.incoming_doc_ref.material_name,
|
||||
'goods_type':'Materials',
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty,
|
||||
'rework_type':'inhouse'
|
||||
})
|
||||
else:
|
||||
orr_record = self.env['sos_sfg_outsourcing_return_register'].create({
|
||||
'orr_no': orr_no,
|
||||
'iqi_no':self.incoming_doc_ref.id,
|
||||
'iqi_date':self.incoming_doc_ref.iqi_date,
|
||||
'sfg_name':self.incoming_doc_ref.sfg_name,
|
||||
'goods_type':'SFG',
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty,
|
||||
'rework_type':'inhouse'
|
||||
})
|
||||
self.outsourcing_return_ref_no = orr_record.id
|
||||
body_html = f"""
|
||||
<p>Below <b>NCMR</b> is waiting for your Action</p>
|
||||
"""
|
||||
|
|
@ -636,7 +815,8 @@ class NCMR_Model(models.Model):
|
|||
'material_name':self.incoming_doc_ref.material_name,
|
||||
'supplier_name':self.incoming_doc_ref.supplier_name.id,
|
||||
'goods_type':'Materials',
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty,
|
||||
'rework_type':'outsourcing'
|
||||
})
|
||||
else:
|
||||
if self.material_option:
|
||||
|
|
@ -647,7 +827,8 @@ class NCMR_Model(models.Model):
|
|||
'material_name':self.incoming_doc_ref.material_name,
|
||||
'supplier_name':self.incoming_doc_ref.supplier_name.id,
|
||||
'goods_type':'Materials',
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty,
|
||||
'rework_type':'outsourcing'
|
||||
})
|
||||
else:
|
||||
orr_record = self.env['sos_sfg_outsourcing_return_register'].create({
|
||||
|
|
@ -657,7 +838,8 @@ class NCMR_Model(models.Model):
|
|||
'service_provider_name':self.incoming_doc_ref.service_provider_name,
|
||||
'sfg_name':self.incoming_doc_ref.sfg_name,
|
||||
'goods_type':'SFG',
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty
|
||||
'returned_qty':self.incoming_doc_ref.rejected_qty,
|
||||
'rework_type':'outsourcing'
|
||||
})
|
||||
self.outsourcing_return_ref_no = orr_record.id
|
||||
|
||||
|
|
@ -685,6 +867,26 @@ class NCMR_Model(models.Model):
|
|||
)
|
||||
|
||||
def action_qa_esign_btn(self):
|
||||
if self.responsible_department:
|
||||
if self.material_option:
|
||||
material = self.material_name.part_no
|
||||
elif self.sfg_option:
|
||||
material = self.sfg_name.name
|
||||
else:
|
||||
material = self.fg_name.name
|
||||
result_col = f"NCMR Ref No :{getattr(self, 'ncmr_no', '')}"
|
||||
|
||||
new_action_record = self.env['sos_brm_action'].create({
|
||||
'cross_dept_action': 'cross_dept',
|
||||
'department': 3,
|
||||
'responsible_person': getattr(self.responsible_name, 'id', False),
|
||||
'assigned_by': getattr(self.qa_by, 'id', False),
|
||||
'assigned_from_dept': 3,
|
||||
'assigned_to_dept': getattr(self.responsible_department, 'id', False),
|
||||
'name': f"NCMR Assigned : {material}",
|
||||
'result': result_col
|
||||
})
|
||||
|
||||
if self.action_group:
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
sequence_util.action_assign_signature(
|
||||
|
|
@ -767,8 +969,9 @@ class NCMR_Model_CAPA_Line(models.Model):
|
|||
|
||||
ncmr_id = fields.Many2one('sos_ncmr', string="NCMR Reference", ondelete="cascade")
|
||||
issue = fields.Char(string="Issue")
|
||||
corrective_action = fields.Html(string="Corrective Action")
|
||||
preventive_action = fields.Html(string="Preventive Action")
|
||||
corrective_action = fields.Html(string="D5: Permanent Corrective Action")
|
||||
implement_validate_corrective_action = fields.Html(string="D6:Implement & Validate Corrective Actions")
|
||||
preventive_action = fields.Html(string="D7:Preventive Recurrence")
|
||||
|
||||
class NCMR_Model_Line(models.Model):
|
||||
_name = 'sos_ncmr_line'
|
||||
|
|
@ -897,3 +1100,18 @@ class NCMR_Model_Line(models.Model):
|
|||
def _compute_fg_defective_count(self):
|
||||
for record in self:
|
||||
record.fg_defective_count = len(record.fg_defectives)
|
||||
|
||||
class NCMR_Model_PROBLEM_DESCRIPTION_Line(models.Model):
|
||||
_name = 'sos_ncmr_problem_description_line'
|
||||
_description = 'Problem Description Lines'
|
||||
|
||||
ncmr_id = fields.Many2one('sos_ncmr', string="NCMR Reference", ondelete="cascade")
|
||||
clear_statement = fields.Html(string="Clear Statement")
|
||||
photos = fields.Html(string="Photos")
|
||||
what = fields.Text(string="What")
|
||||
where = fields.Text(string="Where")
|
||||
when = fields.Text(string="When")
|
||||
why = fields.Text(string="Why")
|
||||
who = fields.Text(string="Who")
|
||||
how = fields.Text(string="How")
|
||||
how_many = fields.Text(string="How many")
|
||||
|
|
@ -182,7 +182,7 @@ class SOS_Order_Delivery_Plan(models.Model):
|
|||
<p>Below <b>Order Delivery Plan</b> is waiting for your Approval</p>
|
||||
"""
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_order_delivery_plan",self.id,"ramachandran.r@sosaley.in","Order Delivery Plan",body_html)
|
||||
send_email.send_direct_email(self.env,"sos_order_delivery_plan",self.id,"ramachandran.r@sosaley.com","Order Delivery Plan",body_html)
|
||||
# Email part ends
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.action_assign_signature(
|
||||
|
|
|
|||
|
|
@ -36,12 +36,25 @@ class SOS_Oursourcing_Monitor_Lines(models.Model):
|
|||
rejection_percentage = fields.Float(string="Rejected Percentage", compute="_compute_rejected_percentage", store=True)
|
||||
rejection_percentage_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display')
|
||||
ppm = fields.Integer(string="PPM",compute="_compute_ppm")
|
||||
|
||||
quality_marks = fields.Float(string="Quality Marks")
|
||||
quality_marks = fields.Float(string="Quality Marks",compute="_compute_sfgquality")
|
||||
delivery_marks = fields.Float(string="Delivery Marks")
|
||||
responsiveness_marks = fields.Float(string="Responsiveness Marks")
|
||||
report_marks = fields.Float(string="Report Marks")
|
||||
def _calculate_quality_marks(self, received, rejected):
|
||||
if received != 0:
|
||||
q = 100 - ((rejected / received) * 100)
|
||||
if 91 <= q <= 100:
|
||||
return 30
|
||||
elif 80 <= q <= 90:
|
||||
return 25
|
||||
else:
|
||||
return 10
|
||||
return 0
|
||||
|
||||
@api.depends('received_qty', 'rejected_qty')
|
||||
def _compute_sfgquality(self):
|
||||
for record in self:
|
||||
record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty)
|
||||
@api.depends('rejection_percentage')
|
||||
def _compute_rejected_percentage_display(self):
|
||||
for record in self:
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class sos__po(models.Model):
|
|||
"""
|
||||
subject = f"Purchase Order Approval Request - {self.po_no}"
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_po",self.id,"ramachandran.r@sosaley.in",subject,body_html)
|
||||
send_email.send_direct_email(self.env,"sos_po",self.id,"ramachandran.r@sosaley.com",subject,body_html)
|
||||
# Email part ends
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.action_assign_signature(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
class SOS_prf(models.Model):
|
||||
_name = 'sos_prf'
|
||||
_description = 'Purchase Requisition Form'
|
||||
_rec_name = 'prf_no'
|
||||
_order = 'id desc'
|
||||
|
||||
prf_no = fields.Char(string="PRF No", readonly= True, required= True, default=lambda self: self._generate_id())
|
||||
prf_date = fields.Date(string="PRF Date", required=True, default=fields.Date.today)
|
||||
line_ids = fields.One2many('sos_prf_lines', 'ref_id', string="PRF Lines",copy=True)
|
||||
requested_by_name = fields.Many2one('res.users', string='Requested by')
|
||||
requested_by_image = fields.Image(related="requested_by_name.signature_image",string='Requested by Sign',readonly=True)
|
||||
requested_on = fields.Datetime(string="Requested On")
|
||||
dept_in_charge_name = fields.Many2one('res.users', string='Department In-Charge')
|
||||
dept_in_charge_image = fields.Image(related="dept_in_charge_name.signature_image",string='Department In-Charge Sign',readonly=True)
|
||||
dept_in_charge_approved_on = fields.Datetime(string="Approved On")
|
||||
top_management_name = fields.Many2one('res.users', string='Top Management Approver')
|
||||
top_management_approval_image = fields.Image(related="top_management_name.signature_image",string='Top Management Approval',readonly=True)
|
||||
top_management_approved_on = fields.Datetime(string="Approved On")
|
||||
status = fields.Selection([('open', 'Open'),('close', 'Closed')], default='open' , string="Status")
|
||||
|
||||
def action_top_esign_btn(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
subject = f"PRF Approved - {self.prf_no}"
|
||||
body_html = f"""
|
||||
<p>Below <b>PRF</b> got approved</p>
|
||||
"""
|
||||
sequence_util.send_direct_email(self.env,"sos_prf",self.id,"praveenkumar.m@sosaley.com",subject,body_html)
|
||||
sequence_util.action_assign_signature(
|
||||
self,
|
||||
'top_management_name',
|
||||
'top_management_approved_on',
|
||||
'sos_inventory.sos_management_user'
|
||||
)
|
||||
def action_dept_esign_btn(self):
|
||||
# Email part
|
||||
body_html = f"""
|
||||
<p>Below <b>PRF</b> is waiting for your Approval</p>
|
||||
"""
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
subject = f"PRF Approval Request - {self.prf_no}"
|
||||
sequence_util.send_direct_email(self.env,"sos_prf",self.id,"ramachandran.r@sosaley.com",subject,body_html)
|
||||
# Email part ends
|
||||
sequence_util.action_assign_signature(
|
||||
self,
|
||||
'dept_in_charge_name',
|
||||
'dept_in_charge_approved_on',
|
||||
'sos_inventory.sos_scg_group_manager'
|
||||
)
|
||||
def action_stores_esign_btn(self):
|
||||
# Email part
|
||||
body_html = f"""
|
||||
<p>Below <b>PRF</b> is waiting for your Approval</p>
|
||||
"""
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
sequence_util.send_group_email(self.env,'sos_prf',self.id,"deenalaura.m@sosaley.in","PRF Approval Request",body_html,'sos_inventory.sos_scg_group_manager')
|
||||
# Email part ends
|
||||
sequence_util.action_assign_signature(
|
||||
self,
|
||||
'requested_by_name',
|
||||
'requested_on',
|
||||
'sos_inventory.sos_scg_group_user'
|
||||
)
|
||||
def _generate_id(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.generate_sequence('sos_prf','PRF', 'prf_no')
|
||||
|
||||
class SOS_prf_Lines(models.Model):
|
||||
_name = 'sos_prf_lines'
|
||||
_description = 'Purchase Requisition Form Lines'
|
||||
|
||||
ref_id = fields.Many2one('sos_prf', string="PRF", ondelete="cascade")
|
||||
com_type = fields.Selection([('exits', 'In-stock'),('new', 'New')], string="Availability", default='exits')
|
||||
component_id = fields.Many2one('sos_material', string="Material Name")
|
||||
new_component_id = fields.Char(string="New Part No")
|
||||
qty = fields.Integer(string="Required Qty")
|
||||
req_date = fields.Date(string="Required Date")
|
||||
mon_ref_no = fields.Many2one('sos_mon',string="MON Ref No")
|
||||
|
|
@ -68,35 +68,35 @@ class SOS_Quote_Generation(models.Model):
|
|||
|
||||
supplier_dict = {}
|
||||
for record in self.line_ids:
|
||||
if record.supplier1_name:
|
||||
supplier = record.supplier1_name
|
||||
if record.final_supplier1_name:
|
||||
supplier = record.final_supplier1_name
|
||||
if supplier not in supplier_dict:
|
||||
supplier_dict[supplier] = []
|
||||
supplier_dict[supplier].append({
|
||||
'name': record.material_name,
|
||||
'product_qty': record.supplier1_qty,
|
||||
'price_unit': record.supplier1_quoted_price
|
||||
'product_qty': record.final_supplier1_qty,
|
||||
'price_unit': record.final_supplier1_quoted_price
|
||||
})
|
||||
|
||||
if record.supplier2_name:
|
||||
supplier = record.supplier2_name
|
||||
if supplier not in supplier_dict:
|
||||
supplier_dict[supplier] = []
|
||||
supplier_dict[supplier].append({
|
||||
'name': record.material_name,
|
||||
'product_qty': record.supplier2_qty,
|
||||
'price_unit': record.supplier2_quoted_price
|
||||
})
|
||||
# if record.supplier2_name:
|
||||
# supplier = record.supplier2_name
|
||||
# if supplier not in supplier_dict:
|
||||
# supplier_dict[supplier] = []
|
||||
# supplier_dict[supplier].append({
|
||||
# 'name': record.material_name,
|
||||
# 'product_qty': record.supplier2_qty,
|
||||
# 'price_unit': record.supplier2_quoted_price
|
||||
# })
|
||||
|
||||
if record.supplier3_name:
|
||||
supplier = record.supplier3_name
|
||||
if supplier not in supplier_dict:
|
||||
supplier_dict[supplier] = []
|
||||
supplier_dict[supplier].append({
|
||||
'name': record.material_name,
|
||||
'product_qty': record.supplier3_qty,
|
||||
'price_unit': record.supplier3_quoted_price
|
||||
})
|
||||
# if record.supplier3_name:
|
||||
# supplier = record.supplier3_name
|
||||
# if supplier not in supplier_dict:
|
||||
# supplier_dict[supplier] = []
|
||||
# supplier_dict[supplier].append({
|
||||
# 'name': record.material_name,
|
||||
# 'product_qty': record.supplier3_qty,
|
||||
# 'price_unit': record.supplier3_quoted_price
|
||||
# })
|
||||
for x, y in supplier_dict.items():
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
po_no = sequence_util.generate_sequence('sos_po','PO', 'po_no')
|
||||
|
|
@ -112,6 +112,7 @@ class SOS_Quote_Generation(models.Model):
|
|||
})
|
||||
message = 'Purchase Order(s) successfully generated.'
|
||||
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ class SOS_SalesOrder(models.Model):
|
|||
string="Product Name",required=True)
|
||||
line_ids = fields.One2many('sos_sales_order_line', 'ref_id',copy=True)
|
||||
customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name")
|
||||
customer_location = fields.Char(string="Customer Location")
|
||||
lead_time = fields.Datetime(string="Lead Time")
|
||||
customer_po_no = fields.Char(string="PO No")
|
||||
customer_po_date = fields.Datetime(string="PO Date")
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class Sequence_Generator(models.AbstractModel):
|
|||
reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)])
|
||||
# email_to = reporting_user.login
|
||||
else:
|
||||
# email_to = "ramachandran.r@sosaley.in"
|
||||
# email_to = "ramachandran.r@sosaley.com"
|
||||
print("test")
|
||||
mail_values = {
|
||||
'subject': subject,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,17 @@ class SOS_Service_Call_Log_Report(models.Model):
|
|||
action_taken = fields.Text(string="Action taken or required action to be taken")
|
||||
Date_of_action_Completed = fields.Datetime(string="Date of action completed")
|
||||
status = fields.Selection([('open', 'Open'),('close', 'Closed')], default='open' , string="Status")
|
||||
dock_audit_ref_no = fields.Many2one('sos_dock_audit',string="Dock Audit Ref No")
|
||||
warranty = fields.Integer(string="Warranty(In Months)")
|
||||
invoice_no = fields.Char(string="Invoice No")
|
||||
invoice_date = fields.Date(string="Invoice Date")
|
||||
|
||||
@api.onchange('dock_audit_ref_no')
|
||||
def _onchange_dock_audit_ref_no(self):
|
||||
if self.dock_audit_ref_no:
|
||||
self.warranty = self.dock_audit_ref_no.warranty
|
||||
self.invoice_no = self.dock_audit_ref_no.invoice_no
|
||||
self.invoice_date = self.dock_audit_ref_no.invoice_date
|
||||
def _generate_id(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.generate_sequence('sos_service_call_log_report','SCLR', 'ref_no')
|
||||
|
|
@ -111,6 +111,6 @@ class SOS_SFG_Line(models.Model):
|
|||
ir_no = fields.Many2one('sos_ir', string="Material Inward Ref No")
|
||||
min_no = fields.Many2one('sos_min', string="Material Issue Ref No")
|
||||
mrn_no = fields.Many2one('sos_mrn', string="Material Return Ref No")
|
||||
iqi_no = fields.Many2one('sos_iqi', string="In-house Inspection Ref No")
|
||||
iqi_no = fields.Many2one('sos_iqi', string="IQI Ref No")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ class sos_Outsourcing_Return(models.Model):
|
|||
material_name = fields.Many2one('sos_material',string="Material Name",related="iqi_no.material_name")
|
||||
returned_qty = fields.Integer(string="Returned Quantity",related="iqi_no.rejected_qty")
|
||||
remarks = fields.Text(string="Remarks")
|
||||
rework_type = fields.Selection([
|
||||
('outsourcing', 'Out-Sourcing'),
|
||||
('inhouse', 'In-House')
|
||||
], string='Rework Type',default="outsourcing",copy=True)
|
||||
def _generate_id(self):
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.generate_sequence('sos_sfg_outsourcing_return_register','ORR', 'orr_no')
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ class sos__wo(models.Model):
|
|||
"""
|
||||
subject = f"Work Order Approval Request - {self.wo_no}"
|
||||
send_email = self.env['sos_common_scripts']
|
||||
send_email.send_direct_email(self.env,"sos_wo",self.id,"ramachandran.r@sosaley.in",subject,body_html)
|
||||
send_email.send_direct_email(self.env,"sos_wo",self.id,"ramachandran.r@sosaley.com",subject,body_html)
|
||||
# Email part ends
|
||||
sequence_util = self.env['sos_common_scripts']
|
||||
return sequence_util.action_assign_signature(
|
||||
|
|
|
|||
|
|
@ -13,126 +13,133 @@
|
|||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.basic_layout">
|
||||
<!-- Include Bootstrap CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"/>
|
||||
|
||||
<style>
|
||||
/* Reset default margins and ensure full-page layout */
|
||||
<style type="text/css">
|
||||
|
||||
/* Avoid clipping; wkhtmltopdf + overflow hidden can break pagination */
|
||||
.page {
|
||||
margin: 3mm; /* Reduced margins */
|
||||
margin: 3mm;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden; /* Prevent content from overflowing */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Container for each set of tables (FG, SFG, Materials) */
|
||||
/* Keep each whole set on one page when possible */
|
||||
.label-set {
|
||||
page-break-inside: avoid; /* Try to keep the entire set on one page */
|
||||
page-break-after: always; /* Force a page break after each set */
|
||||
}
|
||||
|
||||
/* Ensure tables stay within their column */
|
||||
.table-container {
|
||||
width: 100%; /* Full width of the column */
|
||||
page-break-inside: avoid; /* Prevent table from splitting across pages */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Clearfix for the last table if odd number of tables */
|
||||
.label-set::after {
|
||||
content: "";
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Compact table styling */
|
||||
.label-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
page-break-inside: avoid !important;
|
||||
break-inside: avoid !important;
|
||||
page-break-after: always; /* one set per page; remove if not desired */
|
||||
}
|
||||
|
||||
/* Pair wrapper: keep both columns together */
|
||||
.pair-block,
|
||||
.row,
|
||||
.col-6,
|
||||
.table-container,
|
||||
.label-table,
|
||||
.label-table tr,
|
||||
.label-table td {
|
||||
font-size: 15px; /* Smaller font size for compactness */
|
||||
padding: 2mm; /* Reduced padding */
|
||||
border: 1px solid #000;
|
||||
box-sizing: border-box;
|
||||
page-break-inside: avoid !important;
|
||||
break-inside: avoid !important;
|
||||
}
|
||||
|
||||
.label-table .column {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
width:20%
|
||||
}
|
||||
|
||||
/* Adjust Bootstrap row margins for PDF */
|
||||
/* Replace Bootstrap flex with inline-block for PDF reliability */
|
||||
.row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-bottom:40px;
|
||||
display: block !important;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
/* Adjust Bootstrap column padding for PDF */
|
||||
[class*="col-"] {
|
||||
.col-6 {
|
||||
display: inline-block !important;
|
||||
vertical-align: top;
|
||||
width: 49.8%;
|
||||
box-sizing: border-box;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
/* Style for empty column placeholder */
|
||||
.empty-col {
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
/* Table layout and text wrapping for long content */
|
||||
.table-container { width: 100%; box-sizing: border-box; }
|
||||
.label-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed; /* keep widths predictable */
|
||||
}
|
||||
.label-table td {
|
||||
font-size: 15px;
|
||||
padding: 2mm;
|
||||
border: 1px solid #000;
|
||||
box-sizing: border-box;
|
||||
white-space: normal; /* allow wrapping */
|
||||
word-wrap: break-word;
|
||||
word-break: break-word; /* break long tokens */
|
||||
}
|
||||
.label-table .column {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.empty-col { font-size: 10px; text-align: center; padding: 2mm; }
|
||||
|
||||
/* Vendor-ish safety (wkhtmltopdf is WebKit) */
|
||||
@media print {
|
||||
.pair-block, .row, .col-6, .table-container, .label-table, .label-table tr, .label-table td {
|
||||
-webkit-region-break-inside: avoid;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="page">
|
||||
|
||||
|
||||
<t t-set="counter" t-value="0"/>
|
||||
<t t-foreach="range(o.master_quantity)" t-as="line_items">
|
||||
<t t-set="counter" t-value="counter + 1"/>
|
||||
|
||||
<div class="label-set">
|
||||
<!-- Combine all tables into a single list -->
|
||||
|
||||
<!-- Build a single list with: (category_id, record, index) -->
|
||||
<t t-set="all_tables" t-value="[]"/>
|
||||
<!-- Add FG tables -->
|
||||
|
||||
<!-- FG -->
|
||||
<t t-set="fg_counter" t-value="0"/>
|
||||
<t t-foreach="o.line_ids_fg or []" t-as="fg_item">
|
||||
<t t-set="fg_counter" t-value="fg_counter + 1"/>
|
||||
<t t-set="all_tables" t-value="all_tables + [(1, fg_item, fg_counter)]"/>
|
||||
</t>
|
||||
<!-- Add SFG tables -->
|
||||
|
||||
<!-- SFG -->
|
||||
<t t-set="sfg_counter" t-value="0"/>
|
||||
<t t-foreach="o.line_ids_sfg or []" t-as="sfg_item">
|
||||
<t t-set="sfg_counter" t-value="sfg_counter + 1"/>
|
||||
<t t-set="all_tables" t-value="all_tables + [(2, sfg_item, sfg_counter)]"/>
|
||||
</t>
|
||||
<!-- Add Materials tables -->
|
||||
|
||||
<!-- Materials -->
|
||||
<t t-set="material_counter" t-value="0"/>
|
||||
<t t-foreach="o.line_ids_material or []" t-as="material_item">
|
||||
<t t-set="material_counter" t-value="material_counter + 1"/>
|
||||
<t t-set="all_tables" t-value="all_tables + [(3, material_item, material_counter)]"/>
|
||||
</t>
|
||||
<!-- Add Installation Kit tables -->
|
||||
|
||||
<!-- Installation Kit -->
|
||||
<t t-set="installation_kit_counter" t-value="0"/>
|
||||
<t t-foreach="o.line_ids_installation_kit or []" t-as="installation_kit_item">
|
||||
<t t-set="installation_kit_counter" t-value="installation_kit_counter + 1"/>
|
||||
<t t-set="all_tables" t-value="all_tables + [(4, installation_kit_item, installation_kit_counter)]"/>
|
||||
</t>
|
||||
<!-- Add Miscelleneous tables -->
|
||||
|
||||
<!-- Miscellaneous -->
|
||||
<t t-set="miscellaneous_counter" t-value="0"/>
|
||||
<t t-foreach="o.line_ids_miscellaneous or []" t-as="miscellaneous_item">
|
||||
<t t-set="miscellaneous_counter" t-value="miscellaneous_counter + 1"/>
|
||||
<t t-set="all_tables" t-value="all_tables + [(5, miscellaneous_item, miscellaneous_counter)]"/>
|
||||
</t>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Process all tables in pairs -->
|
||||
<!-- Render in pairs -->
|
||||
<t t-foreach="range(0, len(all_tables), 2)" t-as="pair_index">
|
||||
<div class="pair-block">
|
||||
<div class="row">
|
||||
<!-- First table in the pair -->
|
||||
<!-- Left column -->
|
||||
<div class="col-6">
|
||||
<t t-set="table_info" t-value="all_tables[pair_index]"/>
|
||||
<t t-set="category" t-value="table_info[0]"/>
|
||||
|
|
@ -157,7 +164,7 @@
|
|||
<tr>
|
||||
<td class="column">Name</td>
|
||||
<td>
|
||||
<t t-if="'component_id' in item">
|
||||
<t t-if="'component_id' in item._fields">
|
||||
<t t-esc="item.component_id.name or item.component_id.part_no or item.name or 'N/A'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
|
|
@ -168,7 +175,7 @@
|
|||
<tr>
|
||||
<td class="column">UOM</td>
|
||||
<td>
|
||||
<t t-if="'uom' in item">
|
||||
<t t-if="'uom' in item._fields">
|
||||
<t t-esc="item.uom or 'N/A'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
|
|
@ -179,16 +186,20 @@
|
|||
<tr>
|
||||
<td class="column">Qty</td>
|
||||
<td>
|
||||
<t t-if="'singet_set_qty' in item">
|
||||
<t t-if="category == 5">
|
||||
<t t-esc="(item.quantity or 0) // (o.master_quantity or 1)"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-if="'singet_set_qty' in item._fields">
|
||||
<t t-esc="item.singet_set_qty or 'N/A'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="item.quantity or 'N/A'"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td class="column">S.No</td>
|
||||
<td></td>
|
||||
|
|
@ -198,13 +209,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Second table in the pair (if exists) -->
|
||||
<!-- Right column -->
|
||||
<div class="col-6">
|
||||
<t t-if="pair_index + 1 < len(all_tables)">
|
||||
<t t-set="table_info" t-value="all_tables[pair_index + 1]"/>
|
||||
<t t-set="category" t-value="table_info[0]"/>
|
||||
<t t-set="item" t-value="table_info[1]"/>
|
||||
<t t-set="index" t-value="table_info[2]"/>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="label-table">
|
||||
<tbody>
|
||||
|
|
@ -223,18 +235,24 @@
|
|||
<tr>
|
||||
<td class="column">Name</td>
|
||||
<td>
|
||||
<t t-if="'component_id' in item">
|
||||
<t t-esc="item.component_id.name or item.component_id.part_no or item.name or 'N/A'"/>
|
||||
<t t-if="'component_id' in item._fields">
|
||||
<t t-if="category in [3, 4]">
|
||||
<t t-esc="item.component_id.part_no or item.name or 'N/A'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="item.component_id.name or item.name or 'N/A'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="item.name or 'N/A'"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="column">UOM</td>
|
||||
<td>
|
||||
<t t-if="'uom' in item">
|
||||
<t t-if="'uom' in item._fields">
|
||||
<t t-esc="item.uom or 'N/A'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
|
|
@ -245,14 +263,20 @@
|
|||
<tr>
|
||||
<td class="column">Qty</td>
|
||||
<td>
|
||||
<t t-if="'singet_set_qty' in item">
|
||||
<t t-if="category == 5">
|
||||
<t t-esc="(item.quantity or 0) // (o.master_quantity or 1)"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-if="'singet_set_qty' in item._fields">
|
||||
<t t-esc="item.singet_set_qty or 'N/A'"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="item.quantity or 'N/A'"/>
|
||||
</t>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="column">S.No</td>
|
||||
<td></td>
|
||||
|
|
@ -265,13 +289,16 @@
|
|||
<div class="empty-col"></div>
|
||||
</t>
|
||||
</div>
|
||||
</div> <!-- Close row -->
|
||||
</div> <!-- /row -->
|
||||
</div> <!-- /pair-block -->
|
||||
</t>
|
||||
</div>
|
||||
</div> <!-- /label-set -->
|
||||
</t>
|
||||
</div>
|
||||
</div> <!-- /page -->
|
||||
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<odoo>
|
||||
<record id="action_material_budget_summary" model="ir.actions.report">
|
||||
<field name="name">Material Budget Summary</field>
|
||||
<field name="model">sos_budget_plan</field> <!-- Adjust model if needed -->
|
||||
<field name="report_type">qweb-html</field>
|
||||
<field name="report_name">sos_inventory.report_material_budget_summary</field>
|
||||
<field name="print_report_name">Material_Budget_Summary</field>
|
||||
</record>
|
||||
<template id="report_material_budget_summary">
|
||||
<t t-call="web.basic_layout">
|
||||
<t t-call="web.html_container">
|
||||
<style>
|
||||
body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; color: #1f2937; }
|
||||
.page { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||||
.header { border-bottom: 2px solid #e5e7eb; padding-bottom: 16px; margin-bottom: 24px; }
|
||||
.header h3 { font-size: 24px; font-weight: 700; margin: 0; color: #1e40af; }
|
||||
.header .info { font-size: 14px; color: #4b5563; margin-top: 8px; }
|
||||
.header .info strong { font-weight: 600; }
|
||||
.section-title { font-size: 18px; font-weight: 600; color: #1e40af; margin: 24px 0 12px; }
|
||||
.count { font-size: 14px; color: #4b5563; margin-bottom: 8px; }
|
||||
.table-custom { width: 100%; border-collapse: separate; border-spacing: 0; background-color: #fff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; }
|
||||
.table-custom th { background-color: #e4e0f9; color: #1e40af; font-weight: 600; padding: 12px; text-align: left; border-bottom: 1px solid #d1d5db; }
|
||||
.table-custom td { padding: 12px; border-bottom: 1px solid #e5e7eb; }
|
||||
.table-custom tr:last-child td { border-bottom: none; }
|
||||
.table-custom th.center, .table-custom td.center { text-align: center; }
|
||||
.no-data { text-align: center; color: #6b7280; padding: 16px; font-style: italic; }
|
||||
.total-cost{
|
||||
display:flex; justify-content:flex-end; align-items:center; gap:8px;
|
||||
padding:8px 10px; margin:10px 0 6px;
|
||||
background:#fff; border:1px solid #e5e7eb; border-radius:8px;
|
||||
font-size:14px;
|
||||
}
|
||||
.total-cost .label{ color:#4b5563; }
|
||||
.total-cost .amount{ font-weight:700; font-size:16px; }
|
||||
</style>
|
||||
<div class="page">
|
||||
<div class="header">
|
||||
<h3>Budget Summary</h3>
|
||||
<div class="info">
|
||||
<strong>Name:</strong> <t t-esc="fg_name or '—'"/>
|
||||
| <strong>Planned Quantity:</strong> <t t-esc="total_quantity or '—'"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Material Quantities Table -->
|
||||
<h4 class="section-title">Materials to Purchase</h4>
|
||||
<div class="count">Total Materials: <t t-esc="len(materials)"/></div>
|
||||
<div class="total-cost">
|
||||
<span class="label">Estimated Purchase Cost:</span>
|
||||
<span class="amount">
|
||||
<t t-esc="currency_symbol"/> <t t-esc="'{:,.2f}'.format(total_cost)"/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<table class="table-custom">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Material Name</th>
|
||||
<th class="center">Needed Qty</th>
|
||||
<th class="center">In-Hand Qty</th>
|
||||
<th class="center">Actual Needed Qty</th>
|
||||
<th class="center">Standard Packing Qty</th>
|
||||
<th class="center">To Purchase</th>
|
||||
<th class="center">cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-if="materials">
|
||||
<t t-foreach="materials" t-as="material">
|
||||
<tr>
|
||||
<td><t t-esc="material['material_name']"/></td>
|
||||
<td class="center"><t t-esc="material['needed_quantity']"/></td>
|
||||
<td class="center"><t t-esc="material['inhand_quantity']"/></td>
|
||||
<td class="center"><t t-esc="material['actual_needed']"/></td>
|
||||
<td class="center"><t t-esc="material['std_packing_qty']"/></td>
|
||||
<td class="center"><t t-esc="material['to_purchase']"/></td>
|
||||
<td class="center"><t t-esc="material['cost']"/></td>
|
||||
|
||||
</tr>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="not materials or len(materials) == 0">
|
||||
<tr><td colspan="4" class="no-data">No materials need to be purchased.</td></tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
|
|
@ -72,15 +72,17 @@
|
|||
<table class="table table-bordered">
|
||||
<tbody>
|
||||
<t t-if="o.material_option == True">
|
||||
<tr style="background-color:#ccc"><td class="column" colspan="6">Materials</td></tr>
|
||||
<tr style="background-color:#ccc"><td class="column" colspan="7">Materials</td></tr>
|
||||
<tr class="column">
|
||||
<td>S.No</td>
|
||||
<td class="column">Material Code</td>
|
||||
<td class="column">Name</td>
|
||||
<td class="column">Location</td>
|
||||
<td class="column">In-Hand Quantity</td>
|
||||
<td class="column">Requested Quantity</td>
|
||||
<td class="column">Given Quantity</td>
|
||||
|
||||
|
||||
</tr>
|
||||
|
||||
<t t-foreach="o.line_ids_material" t-as="line_items">
|
||||
|
|
@ -95,6 +97,9 @@
|
|||
<td><t t-esc="line_items.new_component_id"/></td>
|
||||
</t>
|
||||
<t t-if="line_items.com_type == 'exits'">
|
||||
<td><t t-esc="line_items.location"/></td>
|
||||
</t>
|
||||
<t t-if="line_items.com_type == 'exits'">
|
||||
<td><t t-esc="line_items.inhand_stock_qty"/></td>
|
||||
</t>
|
||||
<t t-if="line_items.com_type == 'new'">
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ access_ir_act_window_healthcare_user,Access Action Window for Health Care Users,
|
|||
access_ir_act_window_sos_inside_sales_user,Access Action Window for Inside sales Users,base.model_ir_actions_act_window,sos_inventory.sos_inside_sales_user,1,0,0,0
|
||||
access_ir_act_window_hr_user,Access Action Window for Human Care Resources Users,base.model_ir_actions_act_window,sos_inventory.sos_hr_user,1,0,0,0
|
||||
access_ir_act_window_sos_marketing_user,Access Action Window for Marketing Users,base.model_ir_actions_act_window,sos_inventory.sos_marketing_user,1,0,0,0
|
||||
access_ir_act_window_sos_sales_reviewer,Access Action Window for Sales Reviewer,base.model_ir_actions_act_window,sos_inventory.sos_sales_reviewer,1,0,0,0
|
||||
access_ir_act_window_finance_head_user,Access Action Window for Finance Head,base.model_ir_actions_act_window,sos_inventory.sos_finance_head_user,1,0,0,0
|
||||
access_ir_act_window_sales_sapl_user,Access Action Window for Sales SAPL Users,base.model_ir_actions_act_window,sos_inventory.sos_sales_sapl_user,1,0,0,0
|
||||
access_sos_return_fir,sos_return_fir access,model_sos_return_fir,base.group_user,1,1,1,1
|
||||
access_sos_return_fir_line,sos_return_fir_line access,model_sos_return_fir_line,base.group_user,1,1,1,1
|
||||
access_sos_return_calibration_devices_fir,sos_return_calibration_devices_fir access,model_sos_return_calibration_devices_fir,base.group_user,1,1,1,1
|
||||
|
|
@ -182,6 +185,7 @@ access_sos_transfer_challan_specification_lines,sos_transfer_challan_specificati
|
|||
access_sos_fir_serial_no_lines,sos_fir_serial_no_lines access,model_sos_fir_serial_no_lines,base.group_user,1,1,1,1
|
||||
access_sos_miscellaneous_deliverables,sos_miscellaneous_deliverables access,model_sos_miscellaneous_deliverables,base.group_user,1,1,1,1
|
||||
access_sos_mat_outsourcing_vendor_register,sos_mat_outsourcing_vendor_register access,model_sos_mat_outsourcing_vendor_register,base.group_user,1,1,1,1
|
||||
access_sos_mat_outsourcing_vendor_register_lines,sos_mat_outsourcing_vendor_register_lines access,model_sos_mat_outsourcing_vendor_register_lines,base.group_user,1,1,1,1
|
||||
access_stock_movement_report_wizard,stock_movement_report_wizard access,model_stock_movement_report_wizard,base.group_user,1,1,1,1
|
||||
access_sos_inventory_customers,sos_inventory_customers access,model_sos_inventory_customers,base.group_user,1,1,1,1
|
||||
access_sos_master_list_customer,sos_master_list_customer access,model_sos_master_list_customer,base.group_user,1,1,1,1
|
||||
|
|
@ -197,5 +201,11 @@ access_sos_parameter_fir,sos_parameter_fir access,model_sos_parameter_fir,base.g
|
|||
access_sos_transfer_challan_return_from_customer_lines,sos_transfer_challan_return_from_customer_lines access,model_sos_transfer_challan_return_from_customer_lines,base.group_user,1,1,1,1
|
||||
access_sos_transfer_challan_return_from_customer,sos_transfer_challan_return_from_customer access,model_sos_transfer_challan_return_from_customer,base.group_user,1,1,1,1
|
||||
access_sos_shelflife_register,sos_shelflife_register access,model_sos_shelflife_register,base.group_user,1,1,1,1
|
||||
|
||||
|
||||
access_sos_budget_plan,sos_budget_plan access,model_sos_budget_plan,base.group_user,1,1,1,1
|
||||
access_sos_prf,sos_prf access,model_sos_prf,base.group_user,1,1,1,1
|
||||
access_sos_prf_lines,sos_prf_lines access,model_sos_prf_lines,base.group_user,1,1,1,1
|
||||
access_sos_departments_user_lines,sos_departments_user_lines access,model_sos_departments_user_lines,base.group_user,1,1,1,1
|
||||
access_sos_ncmr_problem_description_line,access_sos_ncmr_problem_description_line access,model_sos_ncmr_problem_description_line,base.group_user,1,1,1,1
|
||||
access_sos_ccrf_capa_line,access_sos_ccrf_capa_line access,model_sos_ccrf_capa_line,base.group_user,1,1,1,1
|
||||
access_sos_ccrf_team_formation_line,access_sos_ccrf_team_formation_line access,model_sos_ccrf_team_formation_line,base.group_user,1,1,1,1
|
||||
access_sos_ccrf_problem_description_line,access_sos_ccrf_problem_description_line access,model_sos_ccrf_problem_description_line,base.group_user,1,1,1,1
|
||||
|
|
|
@ -153,4 +153,24 @@
|
|||
</record>
|
||||
|
||||
|
||||
<record id="sos_mon_ce_team_rule" model="ir.rule">
|
||||
<field name="name">MON: CE Team View CE Records</field>
|
||||
<field name="model_id" ref="model_sos_mon"/>
|
||||
<field name="domain_force">[('is_ce_user_created', '=', True)]</field>
|
||||
<field name="groups" eval="[(4, ref('sos_inventory.sos_ce_user'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
</record>
|
||||
<record id="sos_mrn_ce_team_rule" model="ir.rule">
|
||||
<field name="name">MRN: CE Team View CE Records</field>
|
||||
<field name="model_id" ref="model_sos_mrn"/>
|
||||
<field name="domain_force">[('is_ce_user_created', '=', True)]</field>
|
||||
<field name="groups" eval="[(4, ref('sos_inventory.sos_ce_user'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@
|
|||
<field name="category_id"
|
||||
ref="sos_inventory.module_category_sos_inventory"/>
|
||||
</record>
|
||||
<!-- Finance Head Group -->
|
||||
<record id="sos_finance_head_user" model="res.groups">
|
||||
<field name="name">Finance Head</field>
|
||||
<field name="category_id"
|
||||
ref="sos_inventory.module_category_sos_inventory"/>
|
||||
</record>
|
||||
<!-- Logistics Group -->
|
||||
<record id="sos_logistics_user" model="res.groups">
|
||||
<field name="name">Logistics User</field>
|
||||
|
|
@ -97,7 +103,7 @@
|
|||
<field name="category_id"
|
||||
ref="sos_inventory.module_category_sos_inventory"/>
|
||||
</record>
|
||||
<!-- Finance Group -->
|
||||
<!-- Haelthcare Group -->
|
||||
<record id="sos_healthcare_user" model="res.groups">
|
||||
<field name="name">Healthcare User</field>
|
||||
<field name="category_id"
|
||||
|
|
@ -121,6 +127,17 @@
|
|||
<field name="category_id"
|
||||
ref="sos_inventory.module_category_sos_inventory"/>
|
||||
</record>
|
||||
|
||||
<!-- Sales/Inside Reviewer -->
|
||||
<record id="sos_sales_reviewer" model="res.groups">
|
||||
<field name="name">Sales Reviewer</field>
|
||||
<field name="category_id"
|
||||
ref="sos_inventory.module_category_sos_inventory"/>
|
||||
</record>
|
||||
<!-- Sales/Inside Reviewer -->
|
||||
<record id="sos_sales_sapl_user" model="res.groups">
|
||||
<field name="name">Sales SAPL</field>
|
||||
<field name="category_id"
|
||||
ref="sos_inventory.module_category_sos_inventory"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@ body{
|
|||
font-size: 15px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.modal.o_technical_modal .modal-content .modal-header .modal-title {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #FFFFFF;
|
||||
background-color: #71639e !important;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,344 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Component, onWillStart, useRef, onMounted } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ParetoChartWidget extends Component {
|
||||
|
||||
static template = "sos_inventory.ParetoChartWidget";
|
||||
|
||||
setup() {
|
||||
this.orm = useService("orm");
|
||||
this.action = useService("action");
|
||||
this.chartRef = useRef("paretoChart");
|
||||
this.startDateRef = useRef("startDate");
|
||||
this.endDateRef = useRef("endDate");
|
||||
this.noDataMessageRef = useRef("endDate");
|
||||
this.goodsTypeRef = useRef("goodsType");
|
||||
this.categoryRef = useRef("category");
|
||||
this.servicesupplierRef = useRef("servicesupplier");
|
||||
|
||||
this.chartInstance = null; // Track chart instance
|
||||
|
||||
// 👉 Pre-fill start & end date when component is mounted
|
||||
onMounted(() => {
|
||||
|
||||
const today = new Date();
|
||||
// First and last day of the month
|
||||
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
||||
|
||||
const formatDate = (date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
this.startDateRef.el.value = formatDate(firstDay);
|
||||
this.endDateRef.el.value = formatDate(lastDay);
|
||||
|
||||
//this.onGoodsTypeChange();
|
||||
//this.onServiceSupplierChange();
|
||||
this.loadData();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async onServiceSupplierChange() {
|
||||
const goods_type = this.goodsTypeRef.el.value;
|
||||
const servicesupplierSelect = this.servicesupplierRef.el;
|
||||
const startDate = this.startDateRef.el.value || false;
|
||||
const endDate = this.endDateRef.el.value || false;
|
||||
//alert(selectedGoodsType+" FFFF :::: "+categorySelect);
|
||||
// Clear existing options
|
||||
servicesupplierSelect.innerHTML = '';//last 12.16 22/08/2025
|
||||
const defaultOption = document.createElement('option');
|
||||
defaultOption.text = 'Select Supplier/Service';
|
||||
defaultOption.disabled = true;
|
||||
defaultOption.selected = true;
|
||||
servicesupplierSelect.appendChild(defaultOption);
|
||||
//alert(" selectedGoodsType :::: "+selectedGoodsType);
|
||||
|
||||
if (goods_type != "FG"){
|
||||
try {
|
||||
// Call backend route to get categories by goodsType
|
||||
const categories = await this.orm.call('sos_ncmr', 'get_service_suppliers_by_goods_type', [goods_type, startDate, endDate]);
|
||||
if (!categories.length) {
|
||||
servicesupplierSelect.innerHTML = '';
|
||||
const option = document.createElement('option');
|
||||
option.text = 'No Data Found';
|
||||
option.disabled = true;
|
||||
servicesupplierSelect.appendChild(option);
|
||||
} else {
|
||||
servicesupplierSelect.innerHTML = '';
|
||||
categories.forEach(cat => {
|
||||
const option = document.createElement('option');
|
||||
option.text = cat.supplier_name;
|
||||
option.value = cat.id;
|
||||
servicesupplierSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch categories:', error);
|
||||
const option = document.createElement('option');
|
||||
option.text = 'Error loading categories';
|
||||
option.disabled = true;
|
||||
servicesupplierSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onGoodsTypeChange() {
|
||||
this.onServiceSupplierChange();
|
||||
const selectedGoodsType = this.goodsTypeRef.el.value;
|
||||
const categorySelect = this.categoryRef.el;
|
||||
categorySelect.innerHTML = '';
|
||||
const defaultOption = document.createElement('option');
|
||||
defaultOption.text = 'Select Category';
|
||||
defaultOption.disabled = true;
|
||||
defaultOption.selected = true;
|
||||
categorySelect.appendChild(defaultOption);
|
||||
|
||||
let categories = [];
|
||||
|
||||
if (selectedGoodsType == 'Material'){
|
||||
categories = [
|
||||
{'id': '', 'name': 'Select Category'},
|
||||
{'id': 'Active Components', 'name': 'Active Components'},
|
||||
{'id': 'Passive Components', 'name': 'Passive Components'},
|
||||
{'id': 'Fabrication', 'name': 'Fabrication'},
|
||||
{'id': 'Accessories', 'name': 'Accessories'},
|
||||
{'id': 'Cables & Connectors', 'name': 'Cables & Connectors'},
|
||||
{'id': 'Metal', 'name': 'Metal'},
|
||||
]
|
||||
}else if (selectedGoodsType == 'SFG'){
|
||||
categories = [
|
||||
{'id': '', 'name': 'Select Category'},
|
||||
{'id': 'pcba', 'name': 'PCB Board'},
|
||||
{'id': 'cables', 'name': 'Cables & Connectors'},
|
||||
{'id': 'others', 'name': 'Others'},
|
||||
]
|
||||
}else if (selectedGoodsType == 'FG'){
|
||||
categories = [
|
||||
{'id': '', 'name': 'Select Category'},
|
||||
{'id': 'Health Care', 'name': 'Health Care'},
|
||||
{'id': 'BHMS', 'name': 'BHMS'},
|
||||
{'id': 'LV-BMS', 'name': 'BMS-LV'},
|
||||
{'id': 'HV-BMS', 'name': 'BMS-HV'},
|
||||
{'id': 'SBMS', 'name': 'SBMS'},
|
||||
{'id': 'Motor Controller', 'name': 'Motor Controller'},
|
||||
]
|
||||
}
|
||||
|
||||
try {
|
||||
// Call backend route to get categories by goodsType
|
||||
if (!categories.length) {
|
||||
const option = document.createElement('option');
|
||||
option.text = 'No Categories Found';
|
||||
option.disabled = true;
|
||||
categorySelect.appendChild(option);
|
||||
} else {
|
||||
categorySelect.innerHTML = '';
|
||||
categories.forEach(cat => {
|
||||
const option = document.createElement('option');
|
||||
option.text = cat.name;
|
||||
option.value = cat.id;
|
||||
categorySelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch categories:', error);
|
||||
const option = document.createElement('option');
|
||||
option.text = 'Error loading categories';
|
||||
option.disabled = true;
|
||||
categorySelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
async onFilterClick() {
|
||||
await this.loadData();
|
||||
}
|
||||
|
||||
showNoDataMessage() {
|
||||
this.noDataMessageRef.el.style.display = "block";
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.destroy();
|
||||
this.chartInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
hideNoDataMessage() {
|
||||
this.noDataMessageRef.el.style.display = "none";
|
||||
}
|
||||
|
||||
async loadData() {
|
||||
|
||||
const startDate = this.startDateRef.el.value || false;
|
||||
const endDate = this.endDateRef.el.value || false;
|
||||
const goodstype = this.goodsTypeRef.el.value || false;
|
||||
const categoryId = this.categoryRef.el.value || false;
|
||||
const servicesupplier = this.servicesupplierRef.el.value || false;
|
||||
//alert(" categoryId :: "+categoryId);
|
||||
if (!startDate || !endDate) {
|
||||
// Optionally show a message or just return
|
||||
console.warn("Start and end date are required.");
|
||||
return;
|
||||
}
|
||||
|
||||
const domain = [];
|
||||
if (startDate) domain.push(['date', '>=', startDate]);
|
||||
if (endDate) domain.push(['date', '<=', endDate]);
|
||||
|
||||
let records = [];
|
||||
|
||||
try {
|
||||
records = await this.orm.call("sos_ncmr", "get_pareto_data", [startDate, endDate, goodstype, categoryId, servicesupplier]); //, goodstype , categoryId
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch pareto data:", error);
|
||||
//this.showNoDataMessage();
|
||||
//return;
|
||||
}
|
||||
|
||||
// if (!records || records.length === 0) {
|
||||
// this.showNoDataMessage();
|
||||
// return;
|
||||
// }
|
||||
|
||||
//this.hideNoDataMessage();
|
||||
this.renderChart(records); // ✅ cleaner and reusable
|
||||
|
||||
// const records = await this.orm.searchRead("pareto.defect.report", [], [
|
||||
// "defect_name", "count", "individual_percent", "cumulative_percent"
|
||||
// ]);
|
||||
}
|
||||
|
||||
renderChart(records){
|
||||
|
||||
const labels = records.map(r => r.defect_name);
|
||||
const counts = records.map(r => r.count);
|
||||
const cumulative = records.map(r => r.cumulative_percent);
|
||||
|
||||
//const ctx = this.el.querySelector("#paretoChart").getContext("2d");
|
||||
|
||||
const threshold = 80;
|
||||
let thresholdLine = [];
|
||||
let thresholdReached = false;
|
||||
|
||||
for (let i = 0; i < cumulative.length; i++) {
|
||||
|
||||
if (!thresholdReached) {
|
||||
thresholdLine.push(threshold);
|
||||
|
||||
if (cumulative[i] >= threshold) {
|
||||
// Insert drop to bottom after this
|
||||
thresholdLine.push(0); // drop vertically
|
||||
thresholdLine.push(null); // stop the line
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const ctx = this.chartRef.el.getContext("2d");
|
||||
|
||||
// Plugin to draw lines after bars
|
||||
const drawLinesLastPlugin = {
|
||||
id: 'drawLinesLast',
|
||||
afterDatasetsDraw(chart) {
|
||||
const ctx = chart.ctx;
|
||||
chart.data.datasets.forEach((dataset, index) => {
|
||||
if (dataset.type === 'line') {
|
||||
const meta = chart.getDatasetMeta(index);
|
||||
meta.controller.draw(ctx);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ Destroy previous chart if exists
|
||||
if (this.chartInstance) {
|
||||
this.chartInstance.destroy();
|
||||
}
|
||||
|
||||
// ✅ Create new chart and store it
|
||||
this.chartInstance = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Defect Qty",
|
||||
data: counts,
|
||||
backgroundColor: "steelblue",
|
||||
yAxisID: 'y',
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
label: "Cumulative %",
|
||||
data: cumulative,
|
||||
type: "line",
|
||||
borderColor: "green",
|
||||
yAxisID: 'y1',
|
||||
tension: 0.4,
|
||||
order: 2,
|
||||
},
|
||||
{
|
||||
label: "80% Threshold",
|
||||
data: new Array(records.length).fill(80),
|
||||
//data: thresholdLine,
|
||||
type: "line",
|
||||
borderColor: "red",
|
||||
borderDash: [5, 5],
|
||||
pointRadius: 0,
|
||||
fill: false,
|
||||
yAxisID: 'y1',
|
||||
spanGaps: false,
|
||||
stepped: true,
|
||||
order: 3,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: "bottom" },
|
||||
title: {
|
||||
display: true,
|
||||
text: "" //Pareto Chart - cables, Cables & Connectors
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
title: { display: true, text: 'Defect Count' },
|
||||
beginAtZero: true
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
title: { display: true, text: 'Percentage' },
|
||||
beginAtZero: true,
|
||||
min: 0,
|
||||
max: 120,
|
||||
ticks: {
|
||||
callback: function (value) {
|
||||
return value + "%";
|
||||
}
|
||||
},
|
||||
grid: { drawOnChartArea: false }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [drawLinesLastPlugin],
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//ParetoChartWidget.template = "sos_inventory.ParetoChartWidget";
|
||||
|
||||
registry.category("actions").add("pareto_chart_widget", ParetoChartWidget);
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="sos_inventory.ParetoChartWidget">
|
||||
<!-- <div class="container mt-3">
|
||||
<canvas t-ref="paretoChart" id="paretoChart" width="100%" height="40"></canvas>
|
||||
</div> -->
|
||||
<div class="o_pareto_chart_container">
|
||||
<div class="o_date_filter_controls" style="margin-left:20px;">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-group">
|
||||
<label>Start Date:</label><br />
|
||||
<input type="date" t-ref="startDate" class="form-control" style="min-width: 150px;" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-group">
|
||||
<label>End Date:</label><br />
|
||||
<input type="date" t-ref="endDate" class="form-control" style="min-width: 150px;" />
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-group">
|
||||
<label>Goods Type:</label><br />
|
||||
<select t-ref="goodsType" t-on-change="onGoodsTypeChange" class="form-select" style="min-width: 150px;">
|
||||
<option selected="selected">Material</option>
|
||||
<option>SFG</option>
|
||||
<option>FG</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-group">
|
||||
<label>Category:</label><br />
|
||||
<select t-ref="category" class="form-select" style="min-width: 150px;">
|
||||
<option selected="selected" disabled="disabled">Select Category</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-group">
|
||||
<label>Supplier/Service:</label><br />
|
||||
<select t-ref="servicesupplier" class="form-select" style="min-width: 150px;">
|
||||
<option selected="selected" disabled="disabled">Select Supplier/Service</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="form-group" style="margin-top: 1.5rem;">
|
||||
<button t-on-click="onFilterClick" class="btn btn-primary">Graph</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Placeholder for No Data -->
|
||||
<div t-ref="noDataMessage" class="o_no_data_message" style="display: none; text-align: center; font-weight: bold; color: red; margin: 2rem;">
|
||||
No Data Available for Selected Dates
|
||||
</div>
|
||||
<!-- <button class="btn btn-primary o-default-button" style="display:none;">Ok</button> -->
|
||||
<canvas t-ref="paretoChart" id="paretoChart" width="1600" height="600"></canvas>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
|
@ -46,10 +46,7 @@
|
|||
<menuitem id="mop_forms_menu_root"
|
||||
name="MOP FORMS"
|
||||
parent="forms_menu_root"/>
|
||||
<!-- CE Forms -->
|
||||
<menuitem id="ce_forms_menu_root"
|
||||
name="CE FORMS"
|
||||
parent="forms_menu_root"/>
|
||||
|
||||
<!-- End CE Forms -->
|
||||
<menuitem id="mme_forms_menu_root"
|
||||
name="MME FORMS"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<odoo>
|
||||
<record id="action_pareto_chart" model="ir.actions.client">
|
||||
<field name="name">Pareto Chart Analysis</field>
|
||||
<field name="tag">pareto_chart_widget</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
|
||||
|
||||
<record id="action_budget_plan_list" model="ir.actions.act_window">
|
||||
<field name="name">Budget Plan</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">sos_budget_plan</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No Plan Found
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="sos_budget_plan_view_tree" model="ir.ui.view">
|
||||
<field name="name">sos_budget_plan.view.tree</field>
|
||||
<field name="model">sos_budget_plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
|
||||
<field name="name"/>
|
||||
<field name="quantity"/>
|
||||
|
||||
<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="sos_budget_plan_form_view" model="ir.ui.view">
|
||||
<field name="name">Form</field>
|
||||
<field name="model">sos_budget_plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Budget Plan">
|
||||
<sheet>
|
||||
<h2 style="text-align: center;text-transform: uppercase;text-shadow: 1px 1p 1px #140718;color: #65407c;padding:5px;">Budget Plan</h2><hr></hr><br></br>
|
||||
<table class="table table" style="box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;">
|
||||
<tr><td><group><field name="sfg_option"/></group></td>
|
||||
<td><group><field name="fg_option"/></group></td></tr>
|
||||
</table>
|
||||
<br></br>
|
||||
<group>
|
||||
<!-- Left Column -->
|
||||
<group>
|
||||
<field name="name"/>
|
||||
|
||||
<field name="quantity"/>
|
||||
</group>
|
||||
|
||||
<!-- Right Column -->
|
||||
<group>
|
||||
<field name="fg_name" invisible="sfg_option"/>
|
||||
<field name="sfg_name" invisible="fg_option"/>
|
||||
|
||||
</group>
|
||||
<button class="btn btn-primary" type="object" name="create_budget_plan" string="Execute"/>
|
||||
|
||||
</group>
|
||||
|
||||
|
||||
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="budget_plan_menu"
|
||||
name="Budget Plan"
|
||||
parent="indent_menu_root"
|
||||
action = "action_budget_plan_list"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -59,22 +59,24 @@
|
|||
<field name="helper_field" invisible="1"/>
|
||||
<field name="sub_fg_name" domain="[('id', 'in', helper_field)]"/>
|
||||
<field name="batch_no"/>
|
||||
|
||||
<field name="invoice_no"/>
|
||||
<field name="batch_release_date"/>
|
||||
<field name="quantity_dispatched"/>
|
||||
</group>
|
||||
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Data Collection">
|
||||
<group>
|
||||
<page string="D0: Defective Details/Symptom">
|
||||
<group>
|
||||
<!-- <group>
|
||||
<field name="invoice_no"/>
|
||||
<field name="batch_release_date"/>
|
||||
<field name="quantity_dispatched"/>
|
||||
</group>
|
||||
</group> -->
|
||||
<group>
|
||||
<field name="brcoa_no"/>
|
||||
<field name="brcoa_date"/>
|
||||
<!-- <field name="brcoa_no"/>
|
||||
<field name="brcoa_date"/> -->
|
||||
<field name="other_complaints"/>
|
||||
</group>
|
||||
</group>
|
||||
|
|
@ -102,13 +104,13 @@
|
|||
</div>
|
||||
</templates>
|
||||
</page>
|
||||
<page string="Investigation Details">
|
||||
<page string="D0:Investigation Details">
|
||||
<group>
|
||||
<group>
|
||||
<field name="device_failed_to_meet_specification"/>
|
||||
<field name="labelling_error"/>
|
||||
<field name="ifu"/>
|
||||
<field name="interin_containment_actions"/>
|
||||
<!-- <field name="interin_containment_actions"/> -->
|
||||
</group>
|
||||
<group>
|
||||
<field name="packaging_details"/>
|
||||
|
|
@ -140,8 +142,59 @@
|
|||
</div>
|
||||
</templates>
|
||||
</page>
|
||||
<page string="Root Cause">
|
||||
<page string="D1:Team Formation & Document Reference">
|
||||
<!-- <field name="team_formation" /> -->
|
||||
|
||||
<field name="team_formation_line_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="department"/>
|
||||
<field name="role"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Form a cross-functional team with process knowledge and authority. Assign roles and responsibilities.</div>
|
||||
</div>
|
||||
</page>
|
||||
<page string="D2:Problem Description">
|
||||
<!-- <field name="problem_description"/> -->
|
||||
|
||||
<field name="problem_description_line_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="clear_statement"/>
|
||||
<field name="photos"/>
|
||||
<field name="what"/>
|
||||
<field name="where"/>
|
||||
<field name="when"/>
|
||||
<field name="why"/>
|
||||
<field name="who"/>
|
||||
<field name="how"/>
|
||||
<field name="how_many"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Clearly define the problem: What, Where, When, Why, Who, How,How Much.Use measurable facts(not assumptions).</div>
|
||||
</div>
|
||||
</page>
|
||||
|
||||
<page string="D3:Containment Actions">
|
||||
<group>
|
||||
<group>
|
||||
<field name="interin_containment_actions"/>
|
||||
</group>
|
||||
</group>
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Temporary actions to isolate the problem and protect the customer.Example: Stop shipment,sort defective parts,replace or rework.</div>
|
||||
</div>
|
||||
</page>
|
||||
|
||||
<page string="D4:Root Cause Analysis">
|
||||
<field name="root_cause"/>
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Identify the true root cause.Tools:5 Whys,Fishbone Diagram,Fault Tree Analysis,FMEA. </div>
|
||||
</div>
|
||||
<templates>
|
||||
<div class="row">
|
||||
<div class="col-4">
|
||||
|
|
@ -183,7 +236,35 @@
|
|||
</templates>
|
||||
</page>
|
||||
<page string="CAPA">
|
||||
<group>
|
||||
|
||||
<field name="capa_line_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="issue"/>
|
||||
<field name="corrective_action"/>
|
||||
<field name="implement_validate_corrective_action"/>
|
||||
<field name="preventive_action"/>
|
||||
</tree>
|
||||
</field>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong><div> Define and verify permanent corrective actions to eliminate the root cause.Validate effectiveness before full implementation.</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Apply the corrective actions,monitor effectiveness, and confirm the issue is resolved.</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Update processes, procedures, training, FMEA, control plans, Process Automation flows.Ensure similar issues don't happen elsewhere. </div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- <group>
|
||||
<group>
|
||||
<field name="corrective_action"/>
|
||||
</group>
|
||||
|
|
@ -193,7 +274,7 @@
|
|||
<group>
|
||||
<field name="capa_status"/>
|
||||
</group>
|
||||
</group>
|
||||
</group> -->
|
||||
<templates>
|
||||
<div class="row">
|
||||
<div class="col-6"></div>
|
||||
|
|
@ -217,7 +298,7 @@
|
|||
</div>
|
||||
</templates>
|
||||
</page>
|
||||
<page string="Response">
|
||||
<page string="D8: Team Recognition">
|
||||
<group>
|
||||
<group>
|
||||
<field name="response_sent_date"/>
|
||||
|
|
@ -228,7 +309,16 @@
|
|||
<group>
|
||||
<field name="qa_comments"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="capa_status"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="team_recognition" />
|
||||
</group>
|
||||
</group>
|
||||
<div class="oe_note">
|
||||
<strong>Note:</strong> <div>Appreciate and recognize the team's effort.Helps motivation and organizational learning. </div>
|
||||
</div>
|
||||
<templates>
|
||||
<div class="row">
|
||||
<div class="col-6"></div>
|
||||
|
|
@ -266,9 +356,5 @@
|
|||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem id="ccrf_menu"
|
||||
name="Customer Complaint Registration Form(CCRF)"
|
||||
parent="ce_forms_menu_root" action="action_ccrf_list" groups="sos_inventory.sos_production_user,sos_inventory.sos_rd_user,sos_inventory.sos_healthcare_user,sos_inventory.sos_qa_user,sos_inventory.sos_qc_user,sos_inventory.sos_ce_user,sos_inventory.sos_scg_group_user,sos_inventory.sos_management_user,sos_inventory.sos_sales_user"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -84,7 +84,7 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
|
|||
|
||||
<notebook>
|
||||
<page string="Outsourcing" invisible="dc_type != 'out_souce_return' and dc_type != 'out_souce_noreturn'">
|
||||
<field name="line_ids" readonly="top_management_name">
|
||||
<field name="line_ids" widget="one2many_search" readonly="top_management_name">
|
||||
<tree editable="bottom">
|
||||
<field name="component_id"/>
|
||||
<field name="display_name"/>
|
||||
|
|
@ -97,7 +97,7 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
|
|||
</field>
|
||||
</page>
|
||||
<page string="Materials" invisible="dc_type == 'out_souce_return' or dc_type == 'out_souce_noreturn'">
|
||||
<field name="line_ids_materials" readonly="top_management_name">
|
||||
<field name="line_ids_materials" widget="one2many_search" readonly="top_management_name">
|
||||
<tree editable="bottom">
|
||||
<field name="component_id"/>
|
||||
<field name="display_name"/>
|
||||
|
|
@ -143,7 +143,10 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
|
|||
|
||||
</field>
|
||||
</page>
|
||||
|
||||
<page string="Logistice Details">
|
||||
<field name="courier"/>
|
||||
<field name="lr_no"/>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
<br></br>
|
||||
|
|
@ -217,7 +220,7 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
|
|||
</record>
|
||||
<menuitem id="delivery_challan_menu"
|
||||
name="Delivery Challan (DC)"
|
||||
parent="scg_forms_menu_root" action="action_dc_form_list" groups="sos_inventory.sos_healthcare_user,sos_inventory.sos_scg_group_manager,sos_inventory.sos_logistics_user,sos_inventory.sos_scg_group_user,sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_sys_admin_user"/>
|
||||
parent="scg_forms_menu_root" action="action_dc_form_list" groups="sos_inventory.sos_healthcare_user,sos_inventory.sos_scg_group_manager,sos_inventory.sos_logistics_user,sos_inventory.sos_scg_group_user,sos_inventory.sos_management_user,sos_inventory.sos_finance_user,sos_inventory.sos_sys_admin_user,sos_inventory.sos_ce_user,sos_inventory.sos_sales_user"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -363,7 +363,7 @@
|
|||
</record>
|
||||
<menuitem id="sos_deliverables_boq_menu"
|
||||
name="Deliverables List / BOQ"
|
||||
parent="sales_root_menu" action="action_sos_deliverables_boq_form_list" groups="sos_inventory.sos_scg_group_user,sos_inventory.sos_ce_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_manager,sos_inventory.sos_rd_user,sos_inventory.sos_production_user,sos_inventory.sos_qc_user,sos_inventory.sos_qa_user" />
|
||||
parent="sales_root_menu" action="action_sos_deliverables_boq_form_list" groups="sos_inventory.sos_scg_group_user,sos_inventory.sos_ce_user,sos_inventory.sos_finance_user,sos_inventory.sos_scg_group_manager,sos_inventory.sos_rd_user,sos_inventory.sos_production_user,sos_inventory.sos_qc_user,sos_inventory.sos_qa_user,sos_inventory.sos_logistics_user" />
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
<sheet>
|
||||
<group>
|
||||
<field name="fg_name"/>
|
||||
<field name="communication_type"/>
|
||||
<field name="communication_type" invisible="fg_name != 'BHMS 12V'"/>
|
||||
<field name="slave_type" invisible="fg_name != 'BHMS 1.2V'"/>
|
||||
<!-- <field name="customer_name"/>
|
||||
<field name="customer_location"/> -->
|
||||
</group>
|
||||
|
|
@ -91,6 +92,7 @@
|
|||
<tree string="Deliverables">
|
||||
<field name="fg_name"/>
|
||||
<field name="communication_type"/>
|
||||
<field name="slave_type"/>
|
||||
<field name="fg_ids"/>
|
||||
<field name="sfg_ids"/>
|
||||
<field name="material_ids"/>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
<field name="model">sos_departments</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="short_form"/>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
|
@ -22,6 +23,15 @@
|
|||
<form string="Model Form">
|
||||
<sheet>
|
||||
<group><field name="name"/></group>
|
||||
<group><field name="short_form"/></group>
|
||||
<group><field name="process_incharge"/></group>
|
||||
|
||||
<field name="users_line_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="users"/>
|
||||
</tree>
|
||||
|
||||
</field>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -216,13 +216,15 @@
|
|||
|
||||
<field name="payment_status"/>
|
||||
<field name="billing_address"/>
|
||||
<field name="shipping_address"/>
|
||||
|
||||
</group>
|
||||
<group>
|
||||
<field name="invoice_no"/>
|
||||
<field name="invoice_date"/>
|
||||
<field name="warranty"/>
|
||||
<field name="gst_no"/>
|
||||
|
||||
<field name="shipping_address"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
decoration-warning="indent_status == 'hold'"
|
||||
decoration-danger="indent_status == 'open' or indent_status == 'cancel'"
|
||||
/>
|
||||
<field name="prepared_by" string="Prepared By" widget="many2one_avatar_user"/>
|
||||
|
||||
<field name="approved_by" string="Approved By" widget="many2one_avatar_user"/>
|
||||
|
||||
</tree>
|
||||
|
|
@ -184,7 +186,8 @@
|
|||
<templates>
|
||||
<div class="row" style="margin-top:100px">
|
||||
|
||||
<div class="col-4"> <table class="table_custom">
|
||||
<div class="col-4"> <table class="table_custom" style="box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;background-color: #fff;border: solid 4px #9689c1;">
|
||||
|
||||
<tr style="border-bottom: solid 1px #ccc;">
|
||||
<td style="padding: 8px;" class="column"><b>Prepared By Sign</b>
|
||||
<button string="Approve" invisible="prepared_image" class="btn-primary custom_btn" type="object" name="action_approve_esign_btn"></button>
|
||||
|
|
@ -200,9 +203,26 @@
|
|||
<td><field name="prepared_by" readonly="1"/></td>
|
||||
</tr>
|
||||
</table> </div>
|
||||
<div class="col-4"> </div>
|
||||
<div class="col-4"> <table class="table_custom" style="box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;background-color: #fff;border: solid 4px #9689c1;">
|
||||
|
||||
<tr style="border-bottom: solid 1px #ccc;">
|
||||
<td style="padding: 8px;" class="column"><b>Accounts Approval Sign</b>
|
||||
<button string="Approve" invisible="accounts_approved_by_image" class="btn-primary custom_btn" type="object" name="action_acc_approver_esign_btn"></button>
|
||||
</td>
|
||||
<td><field name="accounts_approved_by_image" widget="image"/></td>
|
||||
</tr>
|
||||
<tr invisible="accounts_approved_by_image == False">
|
||||
<td style="padding: 8px;" class="column"><b>Approved On</b></td>
|
||||
<td><field name="accounts_approved_on" readonly="1"/></td>
|
||||
</tr>
|
||||
<tr invisible="accounts_approved_by_image == False">
|
||||
<td style="padding: 8px;" class="column"><b>Approved By</b></td>
|
||||
<td><field name="accounts_approved_name" readonly="1"/></td>
|
||||
</tr>
|
||||
</table> </div>
|
||||
<div class="col-4">
|
||||
<table class="table_custom">
|
||||
<table class="table_custom" style="box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px;background-color: #fff;border: solid 4px #9689c1;">
|
||||
|
||||
<tr style="border-bottom: solid 1px #ccc;">
|
||||
<td style="padding: 8px;" class="column"><b>Top Management Approval Sign</b>
|
||||
<button string="Approve" invisible="approval_image" class="btn-primary custom_btn" type="object" name="action_top_approver_esign_btn"></button>
|
||||
|
|
@ -234,4 +254,5 @@
|
|||
parent="indent_menu_root"
|
||||
action = "action_fg_plan_list"/>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -10,8 +10,11 @@
|
|||
<field name="fg_type" string="Type" icon="fa-list-ul" enable_counters="1"/>
|
||||
|
||||
</searchpanel>
|
||||
<field name="display_name" string="Display Name"/>
|
||||
<field name="name" string="Name"/>
|
||||
|
||||
|
||||
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@
|
|||
</field>
|
||||
</record>
|
||||
<!-- Menuitem -->
|
||||
<menuitem id="menu_sos_fir" sequence="5" name="Final Inspection Report (FIR)" parent="mop_forms_menu_root" action="action_sos_fir" groups="sos_inventory.sos_healthcare_user,sos_inventory.sos_scg_group_user,sos_inventory.sos_qc_user,sos_inventory.sos_management_user,sos_inventory.sos_ce_user"/>
|
||||
<menuitem id="menu_sos_fir" sequence="5" name="Final Inspection Report (FIR)" parent="mop_forms_menu_root" action="action_sos_fir" groups="sos_inventory.sos_healthcare_user,sos_inventory.sos_scg_group_user,sos_inventory.sos_qc_user,sos_inventory.sos_management_user,sos_inventory.sos_ce_user,sos_inventory.sos_sales_user"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue