Compare commits

...

2 Commits

Author SHA1 Message Date
Deena ceb5e58e3e Reference #11 2025-09-17 15:08:29 +05:30
Deena ebdc1d3ca1 Reference #14 2025-09-17 15:06:41 +05:30
111 changed files with 3862 additions and 460 deletions

6
sos_brm/__init__.py Executable file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import wizard
from . import report

35
sos_brm/__manifest__.py Executable file
View File

@ -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'
}

View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View File

@ -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
# })

30
sos_brm/demo/demo.xml Executable file
View File

@ -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>

3
sos_brm/models/__init__.py Executable file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import sos_brm_action

225
sos_brm/models/sos_brm_action.py Executable file
View File

@ -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")

3
sos_brm/report/__init__.py Executable file
View File

@ -0,0 +1,3 @@
from .import sos_brm_summary_report
from .import sos_cross_dept_report

View File

@ -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 '—'"/>
&#160;|&#160;
<strong>Status:</strong> <t t-esc="status"/>
<t t-if="department">
&#160;|&#160;
<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 &lt; 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 &lt;= 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&amp;model=sos_brm_action&amp;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) &gt; 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 &lt; 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 &lt;= 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&amp;model=sos_brm_action&amp;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) &gt; 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>

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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&amp;model=sos_brm_action&amp;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 &gt; 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>

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sos_brm_action sos_brm_action access model_sos_brm_action base.group_user 1 1 1 1
3 access_sos_brm_action_lines sos_brm_action_lines access model_sos_brm_action_lines base.group_user 1 1 1 1
4 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
5 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
6 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

View File

@ -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>

6
sos_brm/views/menu.xml Executable file
View File

@ -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>

View File

@ -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>

2
sos_brm/wizard/__init__.py Executable file
View File

@ -0,0 +1,2 @@
from .import sos_brm_report_wizard
from .import sos_cross_dept_report_wizard

View File

@ -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
}
)

View File

@ -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>

View File

@ -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'
}
)

View File

@ -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>

View File

@ -11,7 +11,7 @@
'version': '17.0.1.0.0', 'version': '17.0.1.0.0',
# any module necessary for this one to work correctly # any module necessary for this one to work correctly
'depends': ['base','web','mail'], 'depends': ['base','web','mail','one2many_search_widget'],
# always loaded # always loaded
'data': [ 'data': [
@ -19,6 +19,7 @@
'security/record_rules.xml', 'security/record_rules.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/cron_jobs.xml', 'data/cron_jobs.xml',
'views/pareto_chart_widget_view.xml',
'views/sos_audit_log_view.xml', 'views/sos_audit_log_view.xml',
'views/menu.xml', 'views/menu.xml',
'views/sos_quote_generation.xml', 'views/sos_quote_generation.xml',
@ -26,6 +27,7 @@
'views/sos_fg_view.xml', 'views/sos_fg_view.xml',
'views/sos_sfg_view.xml', 'views/sos_sfg_view.xml',
'views/sos_material_view.xml', 'views/sos_material_view.xml',
'views/sos_budget_plan_view.xml',
'views/sos_material_bom_view.xml', 'views/sos_material_bom_view.xml',
'views/sos_sfg_bom_view.xml', 'views/sos_sfg_bom_view.xml',
'views/sos_fg_bom_view.xml', 'views/sos_fg_bom_view.xml',
@ -81,6 +83,8 @@
'views/sos_service_call_log_report.xml', 'views/sos_service_call_log_report.xml',
'views/sos_transfer_challan_return_from_customer_view.xml', 'views/sos_transfer_challan_return_from_customer_view.xml',
'views/sos_shelflife_register_view.xml', 'views/sos_shelflife_register_view.xml',
'views/sos_prf_view.xml',
'wizard/sfg_bom_bulk_upload_view.xml', 'wizard/sfg_bom_bulk_upload_view.xml',
'wizard/mon_bulk_upload_view.xml', 'wizard/mon_bulk_upload_view.xml',
'wizard/missing_component_wizard.xml', 'wizard/missing_component_wizard.xml',
@ -115,6 +119,7 @@
'report/sos_boq_labels.xml', 'report/sos_boq_labels.xml',
'report/shelflife_report.xml', 'report/shelflife_report.xml',
'report/sos_boq_report.xml', 'report/sos_boq_report.xml',
'report/sos_budget_plan_summary.xml',
'data/send_indent_plan_email_template.xml', 'data/send_indent_plan_email_template.xml',
'data/selection_item.xml' 'data/selection_item.xml'
@ -130,6 +135,8 @@
'sos_inventory/static/src/components/**/*.js', 'sos_inventory/static/src/components/**/*.js',
'sos_inventory/static/src/components/**/*.xml', 'sos_inventory/static/src/components/**/*.xml',
'sos_inventory/static/src/components/**/*.scss', 'sos_inventory/static/src/components/**/*.scss',
'sos_inventory/static/src/js/pareto_chart_widget.js',
'sos_inventory/static/src/xml/pareto_chart_template.xml',

View File

@ -4,7 +4,9 @@ import io
import xlsxwriter import xlsxwriter
class MaterialBackupExportController(http.Controller): 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') @http.route(['/download/material/backup/<string:year>/<string:item_type>'], type='http', auth='user')
def download_backup_by_year(self, year, item_type, **kwargs): def download_backup_by_year(self, year, item_type, **kwargs):
# Safely map table names # Safely map table names

View File

@ -58,4 +58,6 @@ from . import sos_master_customer_property
from . import sos_service_call_log_report from . import sos_service_call_log_report
from . import sos_inhouse_validation_reports_files from . import sos_inhouse_validation_reports_files
from . import sos_transfer_challan_return_from_customer from . import sos_transfer_challan_return_from_customer
from . import sos_shelflife_register from . import sos_shelflife_register
from . import sos_budget_plan
from . import sos_prf

View File

@ -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 '',
}
)

View File

@ -41,7 +41,7 @@ class SOS_CCRF(models.Model):
batch_release_date = fields.Date(string="Invoice Date") batch_release_date = fields.Date(string="Invoice Date")
brcoa_no = fields.Char(string="BRCOA No") brcoa_no = fields.Char(string="BRCOA No")
brcoa_date = fields.Date(string="BRCOA Date") 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_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_image = fields.Image(related="data_collected_by.signature_image",string='Data Collected Sign',readonly=True)
data_collected_on = fields.Datetime(string="Data Collected On") 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_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") investigation_carriedout_on = fields.Datetime(string="Investigation Carried Out On")
root_cause = fields.Html(string="Root Cause", 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_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_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") rootcause_carriedout_on = fields.Datetime(string="Root Cause Carried Out On")
corrective_action = fields.Html(string="Corrective Action") corrective_action = fields.Html(string="D5:Permanent Corrective Action")
preventive_action = fields.Html(string="Preventive 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") 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_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) 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_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") rootcause_verifiedout_on = fields.Datetime(string="Root Cause Verified On")
helper_field = fields.Many2many('sos_fg', string="Helper Field") 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') @api.onchange('fg_name')
def _onchange_fg_name(self): def _onchange_fg_name(self):
if self.fg_name: if self.fg_name:
@ -140,4 +150,38 @@ class SOS_CCRF(models.Model):
'rootcause_verifiedout_by', 'rootcause_verifiedout_by',
'rootcause_verifiedout_on' '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")

View File

@ -288,7 +288,7 @@ class Sequence_Generator(models.AbstractModel):
reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)]) reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)])
email_to = reporting_user.login email_to = reporting_user.login
else: else:
email_to = "ramachandran.r@sosaley.in" email_to = "ramachandran.r@sosaley.com"
mail_values = { mail_values = {
'subject': subject, 'subject': subject,
'body_html': body_html, 'body_html': body_html,

View File

@ -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_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") dept_in_charge_approved_on = fields.Datetime(string="Approved On")
remarks = fields.Text(string="Remarks") remarks = fields.Text(string="Remarks")
courier = fields.Char(string="Courier Name")
lr_no = fields.Char(string="LR No")
@api.onchange('dock_audit_no') @api.onchange('dock_audit_no')
def _onchange_dock_audit_no(self): def _onchange_dock_audit_no(self):
if self.dock_audit_no: if self.dock_audit_no:
@ -220,7 +221,7 @@ class sos__dc(models.Model):
""" """
subject = f"Delivery Challan Approval Request - {self.dc_no}" subject = f"Delivery Challan Approval Request - {self.dc_no}"
send_email = self.env['sos_common_scripts'] 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 # Email part ends
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
sequence_util.action_assign_signature( sequence_util.action_assign_signature(

View File

@ -22,7 +22,12 @@ class SosTestingParameters(models.Model):
communication_type = fields.Selection([ communication_type = fields.Selection([
('wired', 'Wired'), ('wired', 'Wired'),
('wireless', 'Wireless') ('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) 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) 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) 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([ item_type = fields.Selection([
('Master Panel', 'Master Panel'), ('Master Panel', 'Master Panel'),
('CT Module', 'CT Module'), ('CT Module', 'CT Module'),
('Battery Count', 'Battery Count'),
('Slave Module', 'Slave Module'), ('Slave Module', 'Slave Module'),
('Internet Module', 'Internet Module') ('Internet Module', 'Internet Module')
], string="Type",default="Master Panel") ], string="Type",default="Master Panel")
@ -82,6 +88,7 @@ class SOS_Material_Deliverables(models.Model):
item_type = fields.Selection([ item_type = fields.Selection([
('Master Panel', 'Master Panel'), ('Master Panel', 'Master Panel'),
('CT Module', 'CT Module'), ('CT Module', 'CT Module'),
('Battery Count', 'Battery Count'),
('Slave Module', 'Slave Module'), ('Slave Module', 'Slave Module'),
('Internet Module', 'Internet Module') ('Internet Module', 'Internet Module')
], string="Type",default="Master Panel") ], string="Type",default="Master Panel")

View File

@ -8,4 +8,34 @@ class sos_department(models.Model):
name = fields.Char(string="Department Name") 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}."
# }
# }

View File

@ -96,7 +96,7 @@ class sos_Disposal_Register(models.Model):
<p>Below Item is waiting for your approval to Dispose</p> <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 # Email part ends
return sequence_util.action_assign_signature( return sequence_util.action_assign_signature(
self, self,

View File

@ -30,7 +30,9 @@ class SOS_Dock_Audit(models.Model):
], ],
string="Product Type",required=True) string="Product Type",required=True)
quantity = fields.Integer(string="Quantity") quantity = fields.Integer(string="Quantity")
warranty = fields.Integer(string="Warranty(In Months)")
invoice_no = fields.Char(string="Invoice No") invoice_no = fields.Char(string="Invoice No")
invoice_date = fields.Date(string="Invoice Date")
customer_name = fields.Char(string="Customer Name") customer_name = fields.Char(string="Customer Name")
lead_time = fields.Datetime(string="Lead Time") lead_time = fields.Datetime(string="Lead Time")
customer_po_no = fields.Char(string="PO No") customer_po_no = fields.Char(string="PO No")
@ -140,6 +142,18 @@ class SOS_Dock_Audit(models.Model):
}) })
def action_acc_esign_btn(self): 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 = self.env['sos_common_scripts']
sequence_util.action_assign_signature( sequence_util.action_assign_signature(
self, self,
@ -151,7 +165,7 @@ class SOS_Dock_Audit(models.Model):
body_html = f""" body_html = f"""
<p>Below Dock Audit is waiting for your Approval</p> <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 # Email part ends
def action_auditor_esign_btn(self): def action_auditor_esign_btn(self):
sequence_util = self.env['sos_common_scripts'] 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.shipping_address = self.deliverables_boq_id.sales_id.shipping_address
self.gst_no = self.deliverables_boq_id.sales_id.gst_no self.gst_no = self.deliverables_boq_id.sales_id.gst_no
self.payment_status = self.deliverables_boq_id.sales_id.payment_status 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.fg_name = self.deliverables_boq_id.sales_id.fg_name
self.quantity = self.deliverables_boq_id.sales_id.qty self.quantity = self.deliverables_boq_id.sales_id.qty
self.lead_time = self.deliverables_boq_id.sales_id.lead_time self.lead_time = self.deliverables_boq_id.sales_id.lead_time

View File

@ -30,6 +30,11 @@ class SOS_FG_Plan(models.Model):
prepared_by = fields.Many2one('res.users', string='Planned By') 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_image = fields.Image(related="prepared_by.signature_image",string='Prepared By Sign',readonly=True)
prepared_on = fields.Datetime(string="Planned On") 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) blowup = fields.Boolean(string="Blowup Done",default=False)
target_date = fields.Date(string="Target Date",required=True) 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)) indent_start_date = fields.Date(string="Indent Start Date", default=lambda self: fields.Date.context_today(self))
@ -116,29 +121,45 @@ class SOS_FG_Plan(models.Model):
month = today.strftime('%m') month = today.strftime('%m')
base_sequence_prefix = f"SOS/{form_name}/{fy}/{month}/" 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( 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", order=f"{field_name} desc",
limit=1 limit=1
) )
if records: if records:
last_sequence = records[0][field_name] last_sequence = records[0][field_name]
last_suffix = last_sequence.split('/')[-1] parts = last_sequence.split('/')
if last_suffix.isdigit(): last_month = parts[-2]
new_suffix = f"{last_suffix}a" 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: else:
base_num = last_suffix[:-1] # Same month: check for alphabet suffix
last_alpha = last_suffix[-1] alpha_part = ''.join(filter(str.isalpha, last_suffix))
next_alpha = chr(ord(last_alpha) + 1) if last_alpha < 'z' else 'a' if alpha_part:
new_suffix = f"{base_num}{next_alpha}" next_alpha = chr(ord(alpha_part) + 1) if alpha_part < 'z' else 'a'
new_suffix = f"{numeric_part}{next_alpha}"
else:
# First duplicate in same month
new_suffix = f"{numeric_part}a"
else: else:
new_suffix = '016' # No previous records at all
new_suffix = '001'
return f"{base_sequence_prefix}{new_suffix}" return f"{base_sequence_prefix}{new_suffix}"
def _get_default_lines(self,model,column): def _get_default_lines(self,model,column):
products = self.env[model].search([]) products = self.env[model].search([])
@ -170,9 +191,10 @@ class SOS_FG_Plan(models.Model):
def send_indent_plan_email(self, email_ids): 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') template = self.env.ref('sos_inventory.send_indent_plan_email_template')
if template: if template:
template.email_to = ','.join(email_ids) template.email_to = ','.join(valid_emails)
template.send_mail(self.id, force_send=True) template.send_mail(self.id, force_send=True)
def get_unique_emails(self): def get_unique_emails(self):
group_refs = [ group_refs = [
@ -290,7 +312,22 @@ class SOS_FG_Plan(models.Model):
else: else:
worksheet.write(row, 1, value) 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): def action_top_approver_esign_btn(self):
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
result = sequence_util.action_assign_signature( result = sequence_util.action_assign_signature(
@ -334,11 +371,9 @@ class SOS_FG_Plan(models.Model):
body_html = f""" body_html = f"""
<p>Below <b>Indent Budget</b> is waiting for your Approval</p> <p>Below <b>Indent Budget</b> is waiting for your Approval</p>
""" """
send_email = self.env['sos_common_scripts'] 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) 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')
sequence_util = self.env['sos_common_scripts'] result = send_email.action_assign_signature(
result = sequence_util.action_assign_signature(
self, self,
'prepared_by', 'prepared_by',
'prepared_on' 'prepared_on'

View File

@ -125,6 +125,7 @@ class FIR_BRR(models.Model):
sos_record.target_date , sos_record.target_date ,
date.today() date.today()
) )
week_number = min(week_number, 8)
field_name = f'qc_week_{week_number}' field_name = f'qc_week_{week_number}'
fgplan_field_name = f'planned_week_{week_number}' fgplan_field_name = f'planned_week_{week_number}'
# FG Plan update # FG Plan update

View File

@ -77,6 +77,7 @@ class sos__grn(models.Model):
print(f"Failed to find report action: {e}") print(f"Failed to find report action: {e}")
def action_report_esign_btn(self): def action_report_esign_btn(self):
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
sequence_util.action_assign_signature( sequence_util.action_assign_signature(
self, self,
@ -84,6 +85,7 @@ class sos__grn(models.Model):
'stores_approval_on', 'stores_approval_on',
'sos_inventory.sos_scg_group_user' 'sos_inventory.sos_scg_group_user'
) )
self.generate_supplier_service()
if self.received_goods_type == "Materials": if self.received_goods_type == "Materials":
for item in self.line_ids: for item in self.line_ids:
component = self.env['sos_material'].browse(item.component_id.id) component = self.env['sos_material'].browse(item.component_id.id)
@ -148,7 +150,180 @@ class sos__grn(models.Model):
def _compute_sequence(self): def _compute_sequence(self):
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.generate_sequence('sos_grn','GRN', 'grn_no') 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): class sos_grn_line(models.Model):
_name = 'sos_grn_line' _name = 'sos_grn_line'
_description = 'GRN Material Lines' _description = 'GRN Material Lines'
@ -169,14 +344,19 @@ class sos_grn_line(models.Model):
@api.depends('received_qty', 'rejected_qty') @api.depends('received_qty', 'rejected_qty')
def _compute_quality(self): def _compute_quality(self):
for record in self: for record in self:
if record.rejected_qty != 0: record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty)
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 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: else:
record.quality_marks = 10 return 10
else: return 0
record.quality_marks = 100
@api.model @api.model
def write(self, vals): def write(self, vals):
@ -295,14 +475,20 @@ class sos_grn_line_sfg(models.Model):
@api.depends('received_qty', 'rejected_qty') @api.depends('received_qty', 'rejected_qty')
def _compute_sfgquality(self): def _compute_sfgquality(self):
for record in self: for record in self:
if record.rejected_qty != 0: record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty)
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
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: else:
record.quality_marks = 10 return 10
else: return 0
record.quality_marks = 100
class sos_grn_line_fg(models.Model): class sos_grn_line_fg(models.Model):
_name = 'sos_grn_line_fg' _name = 'sos_grn_line_fg'

View File

@ -276,25 +276,26 @@ class SOS_IR(models.Model):
else: else:
iqi_record = self.env['sos_iqi'].create({ if item.qty > 0:
'iqi_no': sequence_util.generate_sequence('sos_iqi', 'IQI', 'iqi_no'), iqi_record = self.env['sos_iqi'].create({
'material_option':True, 'iqi_no': sequence_util.generate_sequence('sos_iqi', 'IQI', 'iqi_no'),
'sfg_option':False, 'material_option':True,
'supplier_name': self.supplier_name.id, 'sfg_option':False,
'batch_no':item.batch_no, 'supplier_name': self.supplier_name.id,
'received_qty':item.qty, 'batch_no':item.batch_no,
'uom': item.component_id.uom, 'received_qty':item.qty,
'material_name': item.component_id.id, 'uom': item.component_id.uom,
'material_code': item.component_id.material_code, 'material_name': item.component_id.id,
'in_tact':self.boxes_sealing, 'material_code': item.component_id.material_code,
'invoice_no':self.dc_no_char, 'in_tact':self.boxes_sealing,
'invoice_date':self.dc_date, 'invoice_no':self.dc_no_char,
'ir_id_unique_id':self.id, 'invoice_date':self.dc_date,
'from_origin':"Vendor", 'ir_id_unique_id':self.id,
'unit_price':item.unit_price 'from_origin':"Vendor",
'unit_price':item.unit_price
}) })
# Email part # Email part
body_html = f""" body_html = f"""
<p>Below <b>IQI</b> is waiting for your Approval</p> <p>Below <b>IQI</b> is waiting for your Approval</p>
@ -383,7 +384,7 @@ class SOS_IR(models.Model):
""" """
subject = f"Inward Approval Request - {self.ir_no}" subject = f"Inward Approval Request - {self.ir_no}"
send_email = self.env['sos_common_scripts'] 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 # Email part ends
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.action_assign_signature( return sequence_util.action_assign_signature(

View File

@ -35,12 +35,26 @@ class SOS_Mat_Oursourcing_Monitor_Lines(models.Model):
rejection_percentage = fields.Float(string="Rejected Percentage", compute="_compute_rejected_percentage", store=True) 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') rejection_percentage_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display')
ppm = fields.Integer(string="PPM",compute="_compute_ppm") ppm = fields.Integer(string="PPM",compute="_compute_ppm")
quality_marks = fields.Float(string="Quality Marks",compute="_compute_sfgquality")
quality_marks = fields.Float(string="Quality Marks")
delivery_marks = fields.Float(string="Delivery Marks") delivery_marks = fields.Float(string="Delivery Marks")
responsiveness_marks = fields.Float(string="Responsiveness Marks") responsiveness_marks = fields.Float(string="Responsiveness Marks")
report_marks = fields.Float(string="Report 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') @api.depends('rejection_percentage')
def _compute_rejected_percentage_display(self): def _compute_rejected_percentage_display(self):
for record in self: for record in self:

View File

@ -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) 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) logged_inuser_group=fields.Boolean(string='Group Name',compute='compute_user_grp',store=True)
dept = fields.Many2one('sos_departments',string="Department") dept = fields.Many2one('sos_departments',string="Department")
customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name")
purpose = fields.Char(string="Purpose") purpose = fields.Char(string="Purpose")
product_name = fields.Many2one('sos_fg', string='Material/Product Name & No') product_name = fields.Many2one('sos_fg', string='Material/Product Name & No')
indent_ref_no = fields.Many2one('sos_fg_plan',string="Indent Reference 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_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_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") 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_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_image = fields.Image(related="stores_approved_by.signature_image",string='Stores Approval Sign',readonly=True)
stores_approved_on = fields.Datetime(string="Approved On") stores_approved_on = fields.Datetime(string="Approved On")
@ -43,9 +44,9 @@ class sos__mon(models.Model):
('fg', 'FG') ('fg', 'FG')
], string='Auto Load Items' ,default=False) ], string='Auto Load Items' ,default=False)
material_option = fields.Boolean('Materials', default=True) material_option = fields.Boolean('Materials', default=True)
sfg_option = fields.Boolean('Semi-Finished Goods') sfg_option = fields.Boolean('Semi-Finished Goods', default=False)
fg_option = fields.Boolean('Finished Goods') fg_option = fields.Boolean('Finished Goods', default=False)
order_type = fields.Char(string='order_type',copy=True) 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_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_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) 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) 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") status = fields.Selection([ ('open', 'Open'),('close', 'Closed')], default='open' , string="Status")
active = fields.Boolean(default=True) 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') @api.constrains('indent_ref_no', 'auto_load_fg_items')
def _check_duplicate_fg_items_per_indent(self): def _check_duplicate_fg_items_per_indent(self):
for record in 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") uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram'), ('Packs', 'Packs')], string="Uom")
specifications = fields.Char(string="Specifications") specifications = fields.Char(string="Specifications")
quantity = fields.Float(string="Quantity",required=True,default=1) 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, company_id = fields.Many2one('res.company', store=True, copy=False,
string="Company", string="Company",
default=lambda self: self.env.user.company_id.id) default=lambda self: self.env.user.company_id.id)

View File

@ -46,6 +46,20 @@ class sos__mrn(models.Model):
default=lambda default=lambda
self: self.env.user.company_id.currency_id.id) 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) 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') @api.onchange('min_no')
def _onchange_min_no(self): def _onchange_min_no(self):
if self.min_no: if self.min_no:

View File

@ -52,7 +52,8 @@ class NCMR_Model(models.Model):
finished_fg_assy = fields.Char() finished_fg_assy = fields.Char()
finished_fg_assy_responsibility = fields.Text(string="Production Assy FG Responsibility") finished_fg_assy_responsibility = fields.Text(string="Production Assy FG Responsibility")
description_of_nc = fields.Html(string="Description of Non-Conformities") 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") 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 ") comments_on_capa = fields.Html(string="Comments on Corrective / Preventive Action ")
qa_comments=fields.Text(string="QA Comments") qa_comments=fields.Text(string="QA Comments")
@ -104,6 +105,14 @@ class NCMR_Model(models.Model):
rework_rd_approval_by = fields.Many2one('res.users',string='Rework - R&D In-Charge',readonly=True) rework_rd_approval_by = fields.Many2one('res.users',string='Rework - R&D In-Charge',readonly=True)
rework_rd_approval_sign = fields.Image(related="rework_rd_approval_by.signature_image",string='Rework - R&D In-Charge',readonly=True) 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") 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( combined_incoming_doc_ref = fields.Reference(
selection=[ selection=[
@ -120,8 +129,155 @@ class NCMR_Model(models.Model):
supplier_name = fields.Many2one('sos_suppliers',string="Supplier Name",compute="_get_supplier_name") 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") 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") 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") 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 #Excel Write
def action_ncmr_report_orm_btn(self, from_date, to_date,force_download=True): def action_ncmr_report_orm_btn(self, from_date, to_date,force_download=True):
output = io.BytesIO() output = io.BytesIO()
@ -559,17 +715,18 @@ class NCMR_Model(models.Model):
'sos_inventory.sos_qa_user' 'sos_inventory.sos_qa_user'
) )
else: else:
if all_closed or record.aodr_no: if all_closed or record.aodr_no:
self.status = 'closed' self.status = 'closed'
if self.rework_action == "inhouse": 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_record = self.env['sos_iqi'].create({
'iqi_no': send_email.generate_sequence('sos_iqi', 'R-IQI', 'iqi_no'), 'iqi_no': send_email.generate_sequence('sos_iqi', 'R-IQI', 'iqi_no'),
'material_option':self.material_option, 'material_option':self.material_option,
'sfg_option':self.sfg_option, 'sfg_option':self.sfg_option,
'received_qty': self.rejected_qty, 'received_qty': self.rejected_qty,
'iqi_type':'rework', 'iqi_type':'rework',
'material_name': self.material_name, 'material_name': self.material_name.id,
'material_code': self.material_name.material_code, 'material_code': self.material_name.material_code,
'sfg_name': self.sfg_name.id, 'sfg_name': self.sfg_name.id,
'sfg_code': self.sfg_name.sfg_code, '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, 'service_provider_name':self.incoming_doc_ref.service_provider_name.id,
'old_iqi_ref': self.incoming_doc_ref.id, 'old_iqi_ref': self.incoming_doc_ref.id,
'ir_id_unique_id':self.incoming_doc_ref.ir_id_unique_id.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':self.incoming_doc_ref.test_report,
# 'test_report_no':self.incoming_doc_ref.test_report_no, # 'test_report_no':self.incoming_doc_ref.test_report_no,
# 'test_report_doc':self.incoming_doc_ref.test_report_doc, # '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 # Rework Iteration starts
sos_record = self.env['sos_iqi'].search([('id', '=', self.incoming_doc_ref.id)], limit=1) sos_record = self.env['sos_iqi'].search([('id', '=', self.incoming_doc_ref.id)], limit=1)
if sos_record: if sos_record:
@ -622,7 +781,27 @@ class NCMR_Model(models.Model):
'sos_inventory.sos_scg_group_user' 'sos_inventory.sos_scg_group_user'
) )
if self.rework_action == "inhouse": 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""" body_html = f"""
<p>Below <b>NCMR</b> is waiting for your Action</p> <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, 'material_name':self.incoming_doc_ref.material_name,
'supplier_name':self.incoming_doc_ref.supplier_name.id, 'supplier_name':self.incoming_doc_ref.supplier_name.id,
'goods_type':'Materials', 'goods_type':'Materials',
'returned_qty':self.incoming_doc_ref.rejected_qty 'returned_qty':self.incoming_doc_ref.rejected_qty,
'rework_type':'outsourcing'
}) })
else: else:
if self.material_option: if self.material_option:
@ -647,7 +827,8 @@ class NCMR_Model(models.Model):
'material_name':self.incoming_doc_ref.material_name, 'material_name':self.incoming_doc_ref.material_name,
'supplier_name':self.incoming_doc_ref.supplier_name.id, 'supplier_name':self.incoming_doc_ref.supplier_name.id,
'goods_type':'Materials', 'goods_type':'Materials',
'returned_qty':self.incoming_doc_ref.rejected_qty 'returned_qty':self.incoming_doc_ref.rejected_qty,
'rework_type':'outsourcing'
}) })
else: else:
orr_record = self.env['sos_sfg_outsourcing_return_register'].create({ 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, 'service_provider_name':self.incoming_doc_ref.service_provider_name,
'sfg_name':self.incoming_doc_ref.sfg_name, 'sfg_name':self.incoming_doc_ref.sfg_name,
'goods_type':'SFG', '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 self.outsourcing_return_ref_no = orr_record.id
@ -685,6 +867,26 @@ class NCMR_Model(models.Model):
) )
def action_qa_esign_btn(self): 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: if self.action_group:
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
sequence_util.action_assign_signature( 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") ncmr_id = fields.Many2one('sos_ncmr', string="NCMR Reference", ondelete="cascade")
issue = fields.Char(string="Issue") issue = fields.Char(string="Issue")
corrective_action = fields.Html(string="Corrective Action") corrective_action = fields.Html(string="D5: Permanent Corrective Action")
preventive_action = fields.Html(string="Preventive 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): class NCMR_Model_Line(models.Model):
_name = 'sos_ncmr_line' _name = 'sos_ncmr_line'
@ -897,3 +1100,18 @@ class NCMR_Model_Line(models.Model):
def _compute_fg_defective_count(self): def _compute_fg_defective_count(self):
for record in self: for record in self:
record.fg_defective_count = len(record.fg_defectives) 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")

View File

@ -182,7 +182,7 @@ class SOS_Order_Delivery_Plan(models.Model):
<p>Below <b>Order Delivery Plan</b> is waiting for your Approval</p> <p>Below <b>Order Delivery Plan</b> is waiting for your Approval</p>
""" """
send_email = self.env['sos_common_scripts'] 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 # Email part ends
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.action_assign_signature( return sequence_util.action_assign_signature(

View File

@ -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 = fields.Float(string="Rejected Percentage", compute="_compute_rejected_percentage", store=True)
rejection_percentage_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display') rejection_percentage_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display')
ppm = fields.Integer(string="PPM",compute="_compute_ppm") ppm = fields.Integer(string="PPM",compute="_compute_ppm")
quality_marks = fields.Float(string="Quality Marks",compute="_compute_sfgquality")
quality_marks = fields.Float(string="Quality Marks")
delivery_marks = fields.Float(string="Delivery Marks") delivery_marks = fields.Float(string="Delivery Marks")
responsiveness_marks = fields.Float(string="Responsiveness Marks") responsiveness_marks = fields.Float(string="Responsiveness Marks")
report_marks = fields.Float(string="Report 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') @api.depends('rejection_percentage')
def _compute_rejected_percentage_display(self): def _compute_rejected_percentage_display(self):
for record in self: for record in self:

View File

@ -143,7 +143,7 @@ class sos__po(models.Model):
""" """
subject = f"Purchase Order Approval Request - {self.po_no}" subject = f"Purchase Order Approval Request - {self.po_no}"
send_email = self.env['sos_common_scripts'] 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 # Email part ends
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.action_assign_signature( return sequence_util.action_assign_signature(

79
sos_inventory/models/sos_prf.py Executable file
View File

@ -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")

View File

@ -68,35 +68,35 @@ class SOS_Quote_Generation(models.Model):
supplier_dict = {} supplier_dict = {}
for record in self.line_ids: for record in self.line_ids:
if record.supplier1_name: if record.final_supplier1_name:
supplier = record.supplier1_name supplier = record.final_supplier1_name
if supplier not in supplier_dict: if supplier not in supplier_dict:
supplier_dict[supplier] = [] supplier_dict[supplier] = []
supplier_dict[supplier].append({ supplier_dict[supplier].append({
'name': record.material_name, 'name': record.material_name,
'product_qty': record.supplier1_qty, 'product_qty': record.final_supplier1_qty,
'price_unit': record.supplier1_quoted_price 'price_unit': record.final_supplier1_quoted_price
}) })
if record.supplier2_name: # if record.supplier2_name:
supplier = record.supplier2_name # supplier = record.supplier2_name
if supplier not in supplier_dict: # if supplier not in supplier_dict:
supplier_dict[supplier] = [] # supplier_dict[supplier] = []
supplier_dict[supplier].append({ # supplier_dict[supplier].append({
'name': record.material_name, # 'name': record.material_name,
'product_qty': record.supplier2_qty, # 'product_qty': record.supplier2_qty,
'price_unit': record.supplier2_quoted_price # 'price_unit': record.supplier2_quoted_price
}) # })
if record.supplier3_name: # if record.supplier3_name:
supplier = record.supplier3_name # supplier = record.supplier3_name
if supplier not in supplier_dict: # if supplier not in supplier_dict:
supplier_dict[supplier] = [] # supplier_dict[supplier] = []
supplier_dict[supplier].append({ # supplier_dict[supplier].append({
'name': record.material_name, # 'name': record.material_name,
'product_qty': record.supplier3_qty, # 'product_qty': record.supplier3_qty,
'price_unit': record.supplier3_quoted_price # 'price_unit': record.supplier3_quoted_price
}) # })
for x, y in supplier_dict.items(): for x, y in supplier_dict.items():
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
po_no = sequence_util.generate_sequence('sos_po','PO', 'po_no') po_no = sequence_util.generate_sequence('sos_po','PO', 'po_no')
@ -111,6 +111,7 @@ class SOS_Quote_Generation(models.Model):
'quantity': components['product_qty'],'unit_price': components['price_unit'], 'total_price': components['product_qty'] * components['price_unit'] 'quantity': components['product_qty'],'unit_price': components['price_unit'], 'total_price': components['product_qty'] * components['price_unit']
}) })
message = 'Purchase Order(s) successfully generated.' message = 'Purchase Order(s) successfully generated.'
return { return {
'type': 'ir.actions.client', 'type': 'ir.actions.client',

View File

@ -26,6 +26,7 @@ class SOS_SalesOrder(models.Model):
string="Product Name",required=True) string="Product Name",required=True)
line_ids = fields.One2many('sos_sales_order_line', 'ref_id',copy=True) line_ids = fields.One2many('sos_sales_order_line', 'ref_id',copy=True)
customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name") customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name")
customer_location = fields.Char(string="Customer Location")
lead_time = fields.Datetime(string="Lead Time") lead_time = fields.Datetime(string="Lead Time")
customer_po_no = fields.Char(string="PO No") customer_po_no = fields.Char(string="PO No")
customer_po_date = fields.Datetime(string="PO Date") customer_po_date = fields.Datetime(string="PO Date")

View File

@ -106,7 +106,7 @@ class Sequence_Generator(models.AbstractModel):
reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)]) reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)])
# email_to = reporting_user.login # email_to = reporting_user.login
else: else:
# email_to = "ramachandran.r@sosaley.in" # email_to = "ramachandran.r@sosaley.com"
print("test") print("test")
mail_values = { mail_values = {
'subject': subject, 'subject': subject,

View File

@ -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") action_taken = fields.Text(string="Action taken or required action to be taken")
Date_of_action_Completed = fields.Datetime(string="Date of action completed") Date_of_action_Completed = fields.Datetime(string="Date of action completed")
status = fields.Selection([('open', 'Open'),('close', 'Closed')], default='open' , string="Status") 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): def _generate_id(self):
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.generate_sequence('sos_service_call_log_report','SCLR', 'ref_no') return sequence_util.generate_sequence('sos_service_call_log_report','SCLR', 'ref_no')

View File

@ -111,6 +111,6 @@ class SOS_SFG_Line(models.Model):
ir_no = fields.Many2one('sos_ir', string="Material Inward Ref No") ir_no = fields.Many2one('sos_ir', string="Material Inward Ref No")
min_no = fields.Many2one('sos_min', string="Material Issue Ref No") min_no = fields.Many2one('sos_min', string="Material Issue Ref No")
mrn_no = fields.Many2one('sos_mrn', string="Material Return 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")

View File

@ -22,6 +22,10 @@ class sos_Outsourcing_Return(models.Model):
material_name = fields.Many2one('sos_material',string="Material Name",related="iqi_no.material_name") 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") returned_qty = fields.Integer(string="Returned Quantity",related="iqi_no.rejected_qty")
remarks = fields.Text(string="Remarks") 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): def _generate_id(self):
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.generate_sequence('sos_sfg_outsourcing_return_register','ORR', 'orr_no') return sequence_util.generate_sequence('sos_sfg_outsourcing_return_register','ORR', 'orr_no')

View File

@ -138,7 +138,7 @@ class sos__wo(models.Model):
""" """
subject = f"Work Order Approval Request - {self.wo_no}" subject = f"Work Order Approval Request - {self.wo_no}"
send_email = self.env['sos_common_scripts'] 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 # Email part ends
sequence_util = self.env['sos_common_scripts'] sequence_util = self.env['sos_common_scripts']
return sequence_util.action_assign_signature( return sequence_util.action_assign_signature(

View File

@ -9,269 +9,296 @@
<field name="binding_type">report</field> <field name="binding_type">report</field>
</record> </record>
<template id="labels_print"> <template id="labels_print">
<t t-call="web.html_container"> <t t-call="web.html_container">
<t t-foreach="docs" t-as="o"> <t t-foreach="docs" t-as="o">
<t t-call="web.basic_layout"> <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> <style type="text/css">
/* Reset default margins and ensure full-page layout */
.page {
margin: 3mm; /* Reduced margins */
padding: 0;
width: 100%;
box-sizing: border-box;
overflow: hidden; /* Prevent content from overflowing */
}
/* Container for each set of tables (FG, SFG, Materials) */ /* Avoid clipping; wkhtmltopdf + overflow hidden can break pagination */
.label-set { .page {
page-break-inside: avoid; /* Try to keep the entire set on one page */ margin: 3mm;
page-break-after: always; /* Force a page break after each set */ padding: 0;
} width: 100%;
box-sizing: border-box;
overflow: visible;
}
/* Ensure tables stay within their column */ /* Keep each whole set on one page when possible */
.table-container { .label-set {
width: 100%; /* Full width of the column */ page-break-inside: avoid !important;
page-break-inside: avoid; /* Prevent table from splitting across pages */ break-inside: avoid !important;
box-sizing: border-box; page-break-after: always; /* one set per page; remove if not desired */
} }
/* Clearfix for the last table if odd number of tables */ /* Pair wrapper: keep both columns together */
.label-set::after { .pair-block,
content: ""; .row,
display: block; .col-6,
clear: both; .table-container,
} .label-table,
.label-table tr,
.label-table td {
page-break-inside: avoid !important;
break-inside: avoid !important;
}
/* Compact table styling */ /* Replace Bootstrap flex with inline-block for PDF reliability */
.label-table { .row {
width: 100%; display: block !important;
border-collapse: collapse; margin: 0 0 12px 0;
} }
.col-6 {
display: inline-block !important;
vertical-align: top;
width: 49.8%;
box-sizing: border-box;
padding-left: 5px;
padding-right: 5px;
}
.label-table td { /* Table layout and text wrapping for long content */
font-size: 15px; /* Smaller font size for compactness */ .table-container { width: 100%; box-sizing: border-box; }
padding: 2mm; /* Reduced padding */ .label-table {
border: 1px solid #000; width: 100%;
box-sizing: border-box; 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%;
}
.label-table .column { .empty-col { font-size: 10px; text-align: center; padding: 2mm; }
font-weight: bold;
font-size: 14px;
width:20%
}
/* Adjust Bootstrap row margins for PDF */ /* Vendor-ish safety (wkhtmltopdf is WebKit) */
.row { @media print {
margin-left: 0; .pair-block, .row, .col-6, .table-container, .label-table, .label-table tr, .label-table td {
margin-right: 0; -webkit-region-break-inside: avoid;
margin-bottom:40px; }
} }
</style>
/* Adjust Bootstrap column padding for PDF */ <div class="page">
[class*="col-"] { <t t-set="counter" t-value="0"/>
padding-left: 5px; <t t-foreach="range(o.master_quantity)" t-as="line_items">
padding-right: 5px; <t t-set="counter" t-value="counter + 1"/>
}
/* Style for empty column placeholder */ <div class="label-set">
.empty-col {
font-size: 10px;
text-align: center;
padding: 2mm;
}
</style>
<div class="page"> <!-- Build a single list with: (category_id, record, index) -->
<t t-set="all_tables" t-value="[]"/>
<t t-set="counter" t-value="0"/> <!-- FG -->
<t t-foreach="range(o.master_quantity)" t-as="line_items"> <t t-set="fg_counter" t-value="0"/>
<t t-set="counter" t-value="counter + 1"/> <t t-foreach="o.line_ids_fg or []" t-as="fg_item">
<div class="label-set"> <t t-set="fg_counter" t-value="fg_counter + 1"/>
<!-- Combine all tables into a single list --> <t t-set="all_tables" t-value="all_tables + [(1, fg_item, fg_counter)]"/>
<t t-set="all_tables" t-value="[]"/> </t>
<!-- Add FG tables -->
<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 -->
<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 -->
<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 -->
<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 -->
<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>
<!-- 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>
<!-- 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>
<!-- Process all tables in pairs --> <!-- Installation Kit -->
<t t-foreach="range(0, len(all_tables), 2)" t-as="pair_index"> <t t-set="installation_kit_counter" t-value="0"/>
<div class="row"> <t t-foreach="o.line_ids_installation_kit or []" t-as="installation_kit_item">
<!-- First table in the pair --> <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>
<!-- 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>
<!-- Render in pairs -->
<t t-foreach="range(0, len(all_tables), 2)" t-as="pair_index">
<div class="pair-block">
<div class="row">
<!-- Left column -->
<div class="col-6"> <div class="col-6">
<t t-set="table_info" t-value="all_tables[pair_index]"/> <t t-set="table_info" t-value="all_tables[pair_index]"/>
<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>
<tr>
<td class="column">Set No</td>
<td>#<t t-esc="counter"/>/<t t-esc="o.master_quantity"/></td>
</tr>
<tr>
<td class="column">Battery</td>
<td><t t-esc="o.batter_or_cells"/></td>
</tr>
<tr>
<td class="column">No</td>
<td><t t-esc="category"/>.<t t-esc="index"/></td>
</tr>
<tr>
<td class="column">Name</td>
<td>
<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="">
<t t-esc="item.name or 'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">UOM</td>
<td>
<t t-if="'uom' in item._fields">
<t t-esc="item.uom or 'N/A'"/>
</t>
<t t-else="">
<t t-esc="'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">Qty</td>
<td>
<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>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Right column -->
<div class="col-6">
<t t-if="pair_index + 1 &lt; 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="category" t-value="table_info[0]"/>
<t t-set="item" t-value="table_info[1]"/> <t t-set="item" t-value="table_info[1]"/>
<t t-set="index" t-value="table_info[2]"/> <t t-set="index" t-value="table_info[2]"/>
<div class="table-container">
<table class="label-table">
<tbody>
<tr>
<td class="column">Set No</td>
<td>#<t t-esc="counter"/>/<t t-esc="o.master_quantity"/></td>
</tr>
<tr>
<td class="column">Battery</td>
<td><t t-esc="o.batter_or_cells"/></td>
</tr>
<tr>
<td class="column">No</td>
<td><t t-esc="category"/>.<t t-esc="index"/></td>
</tr>
<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 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-esc="item.uom or 'N/A'"/>
</t>
<t t-else="">
<t t-esc="'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">Qty</td>
<td>
<t t-if="'singet_set_qty' in item">
<t t-esc="item.singet_set_qty or 'N/A'"/>
</t>
<t t-else="">
<t t-esc="item.quantity or 'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">S.No</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Second table in the pair (if exists) --> <div class="table-container">
<div class="col-6"> <table class="label-table">
<t t-if="pair_index + 1 &lt; len(all_tables)"> <tbody>
<t t-set="table_info" t-value="all_tables[pair_index + 1]"/> <tr>
<t t-set="category" t-value="table_info[0]"/> <td class="column">Set No</td>
<t t-set="item" t-value="table_info[1]"/> <td>#<t t-esc="counter"/>/<t t-esc="o.master_quantity"/></td>
<t t-set="index" t-value="table_info[2]"/> </tr>
<div class="table-container"> <tr>
<table class="label-table"> <td class="column">Battery</td>
<tbody> <td><t t-esc="o.batter_or_cells"/></td>
<tr> </tr>
<td class="column">Set No</td> <tr>
<td>#<t t-esc="counter"/>/<t t-esc="o.master_quantity"/></td> <td class="column">No</td>
</tr> <td><t t-esc="category"/>.<t t-esc="index"/></td>
<tr> </tr>
<td class="column">Battery</td> <tr>
<td><t t-esc="o.batter_or_cells"/></td> <td class="column">Name</td>
</tr> <td>
<tr> <t t-if="'component_id' in item._fields">
<td class="column">No</td> <t t-if="category in [3, 4]">
<td><t t-esc="category"/>.<t t-esc="index"/></td> <t t-esc="item.component_id.part_no or item.name or 'N/A'"/>
</tr> </t>
<tr> <t t-else="">
<td class="column">Name</td> <t t-esc="item.component_id.name or item.name or 'N/A'"/>
<td> </t>
<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 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-esc="item.uom or 'N/A'"/>
</t>
<t t-else="">
<t t-esc="'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">Qty</td>
<td>
<t t-if="'singet_set_qty' in item">
<t t-esc="item.singet_set_qty or 'N/A'"/>
</t>
<t t-else="">
<t t-esc="item.quantity or 'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">S.No</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</t>
<t t-else="">
<div class="empty-col"></div>
</t>
</div>
</div> <!-- Close row -->
</t>
</div>
</t> </t>
</div> <t t-else="">
</t> <t t-esc="item.name or 'N/A'"/>
</t> </t>
</t> </td>
</tr>
<tr>
<td class="column">UOM</td>
<td>
<t t-if="'uom' in item._fields">
<t t-esc="item.uom or 'N/A'"/>
</t>
<t t-else="">
<t t-esc="'N/A'"/>
</t>
</td>
</tr>
<tr>
<td class="column">Qty</td>
<td>
<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>
</tr>
</tbody>
</table>
</div>
</t>
<t t-else="">
<div class="empty-col"></div>
</t>
</div>
</div> <!-- /row -->
</div> <!-- /pair-block -->
</t>
</div> <!-- /label-set -->
</t>
</div> <!-- /page -->
</t>
</t>
</t>
</template> </template>
</odoo> </odoo>

View File

@ -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>

View File

@ -72,14 +72,16 @@
<table class="table table-bordered"> <table class="table table-bordered">
<tbody> <tbody>
<t t-if="o.material_option == True"> <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"> <tr class="column">
<td>S.No</td> <td>S.No</td>
<td class="column">Material Code</td> <td class="column">Material Code</td>
<td class="column">Name</td> <td class="column">Name</td>
<td class="column">Location</td>
<td class="column">In-Hand Quantity</td> <td class="column">In-Hand Quantity</td>
<td class="column">Requested Quantity</td> <td class="column">Requested Quantity</td>
<td class="column">Given Quantity</td> <td class="column">Given Quantity</td>
</tr> </tr>
@ -95,6 +97,9 @@
<td><t t-esc="line_items.new_component_id"/></td> <td><t t-esc="line_items.new_component_id"/></td>
</t> </t>
<t t-if="line_items.com_type == 'exits'"> <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> <td><t t-esc="line_items.inhand_stock_qty"/></td>
</t> </t>
<t t-if="line_items.com_type == 'new'"> <t t-if="line_items.com_type == 'new'">

View File

@ -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_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_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_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,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_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 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_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_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,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_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_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 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_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_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_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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
56 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
57 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
58 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
59 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
60 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
61 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
62 access_sos_return_fir sos_return_fir access model_sos_return_fir base.group_user 1 1 1 1
63 access_sos_return_fir_line sos_return_fir_line access model_sos_return_fir_line base.group_user 1 1 1 1
64 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
185 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
186 access_sos_miscellaneous_deliverables sos_miscellaneous_deliverables access model_sos_miscellaneous_deliverables base.group_user 1 1 1 1
187 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
188 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
189 access_stock_movement_report_wizard stock_movement_report_wizard access model_stock_movement_report_wizard base.group_user 1 1 1 1
190 access_sos_inventory_customers sos_inventory_customers access model_sos_inventory_customers base.group_user 1 1 1 1
191 access_sos_master_list_customer sos_master_list_customer access model_sos_master_list_customer base.group_user 1 1 1 1
201 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
202 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
203 access_sos_shelflife_register sos_shelflife_register access model_sos_shelflife_register base.group_user 1 1 1 1
204 access_sos_budget_plan sos_budget_plan access model_sos_budget_plan base.group_user 1 1 1 1
205 access_sos_prf sos_prf access model_sos_prf base.group_user 1 1 1 1
206 access_sos_prf_lines sos_prf_lines access model_sos_prf_lines base.group_user 1 1 1 1
207 access_sos_departments_user_lines sos_departments_user_lines access model_sos_departments_user_lines base.group_user 1 1 1 1
208 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
209 access_sos_ccrf_capa_line access_sos_ccrf_capa_line access model_sos_ccrf_capa_line base.group_user 1 1 1 1
210 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
211 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

View File

@ -152,5 +152,25 @@
<field name="perm_unlink" eval="0"/> <field name="perm_unlink" eval="0"/>
</record> </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> </odoo>

View File

@ -75,6 +75,12 @@
<field name="category_id" <field name="category_id"
ref="sos_inventory.module_category_sos_inventory"/> ref="sos_inventory.module_category_sos_inventory"/>
</record> </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 --> <!-- Logistics Group -->
<record id="sos_logistics_user" model="res.groups"> <record id="sos_logistics_user" model="res.groups">
<field name="name">Logistics User</field> <field name="name">Logistics User</field>
@ -97,7 +103,7 @@
<field name="category_id" <field name="category_id"
ref="sos_inventory.module_category_sos_inventory"/> ref="sos_inventory.module_category_sos_inventory"/>
</record> </record>
<!-- Finance Group --> <!-- Haelthcare Group -->
<record id="sos_healthcare_user" model="res.groups"> <record id="sos_healthcare_user" model="res.groups">
<field name="name">Healthcare User</field> <field name="name">Healthcare User</field>
<field name="category_id" <field name="category_id"
@ -121,6 +127,17 @@
<field name="category_id" <field name="category_id"
ref="sos_inventory.module_category_sos_inventory"/> ref="sos_inventory.module_category_sos_inventory"/>
</record> </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> </data>
</odoo> </odoo>

View File

@ -2,6 +2,13 @@ body{
font-size: 15px; font-size: 15px;
font-family: sans-serif; 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 { .btn-primary {
color: #FFFFFF; color: #FFFFFF;
background-color: #71639e !important; background-color: #71639e !important;

View File

@ -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);

View File

@ -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>

View File

@ -46,10 +46,7 @@
<menuitem id="mop_forms_menu_root" <menuitem id="mop_forms_menu_root"
name="MOP FORMS" name="MOP FORMS"
parent="forms_menu_root"/> parent="forms_menu_root"/>
<!-- CE Forms -->
<menuitem id="ce_forms_menu_root"
name="CE FORMS"
parent="forms_menu_root"/>
<!-- End CE Forms --> <!-- End CE Forms -->
<menuitem id="mme_forms_menu_root" <menuitem id="mme_forms_menu_root"
name="MME FORMS" name="MME FORMS"

View File

@ -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>

View File

@ -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>

View File

@ -59,22 +59,24 @@
<field name="helper_field" invisible="1"/> <field name="helper_field" invisible="1"/>
<field name="sub_fg_name" domain="[('id', 'in', helper_field)]"/> <field name="sub_fg_name" domain="[('id', 'in', helper_field)]"/>
<field name="batch_no"/> <field name="batch_no"/>
<field name="invoice_no"/>
<field name="batch_release_date"/>
<field name="quantity_dispatched"/>
</group> </group>
</group> </group>
<notebook> <notebook>
<page string="Data Collection"> <page string="D0: Defective Details/Symptom">
<group>
<group> <group>
<!-- <group>
<field name="invoice_no"/> <field name="invoice_no"/>
<field name="batch_release_date"/> <field name="batch_release_date"/>
<field name="quantity_dispatched"/> <field name="quantity_dispatched"/>
</group> </group> -->
<group> <group>
<field name="brcoa_no"/> <!-- <field name="brcoa_no"/>
<field name="brcoa_date"/> <field name="brcoa_date"/> -->
<field name="other_complaints"/> <field name="other_complaints"/>
</group> </group>
</group> </group>
@ -102,13 +104,13 @@
</div> </div>
</templates> </templates>
</page> </page>
<page string="Investigation Details"> <page string="D0:Investigation Details">
<group> <group>
<group> <group>
<field name="device_failed_to_meet_specification"/> <field name="device_failed_to_meet_specification"/>
<field name="labelling_error"/> <field name="labelling_error"/>
<field name="ifu"/> <field name="ifu"/>
<field name="interin_containment_actions"/> <!-- <field name="interin_containment_actions"/> -->
</group> </group>
<group> <group>
<field name="packaging_details"/> <field name="packaging_details"/>
@ -140,8 +142,59 @@
</div> </div>
</templates> </templates>
</page> </page>
<page string="Root Cause"> <page string="D1:Team Formation &amp; 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"/> <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> <templates>
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4">
@ -183,7 +236,35 @@
</templates> </templates>
</page> </page>
<page string="CAPA"> <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> <group>
<field name="corrective_action"/> <field name="corrective_action"/>
</group> </group>
@ -193,7 +274,7 @@
<group> <group>
<field name="capa_status"/> <field name="capa_status"/>
</group> </group>
</group> </group> -->
<templates> <templates>
<div class="row"> <div class="row">
<div class="col-6"></div> <div class="col-6"></div>
@ -217,7 +298,7 @@
</div> </div>
</templates> </templates>
</page> </page>
<page string="Response"> <page string="D8: Team Recognition">
<group> <group>
<group> <group>
<field name="response_sent_date"/> <field name="response_sent_date"/>
@ -228,7 +309,16 @@
<group> <group>
<field name="qa_comments"/> <field name="qa_comments"/>
</group> </group>
<group>
<field name="capa_status"/>
</group> </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> <templates>
<div class="row"> <div class="row">
<div class="col-6"></div> <div class="col-6"></div>
@ -266,9 +356,5 @@
</form> </form>
</field> </field>
</record> </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> </odoo>

View File

@ -84,7 +84,7 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
<notebook> <notebook>
<page string="Outsourcing" invisible="dc_type != 'out_souce_return' and dc_type != 'out_souce_noreturn'"> <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"> <tree editable="bottom">
<field name="component_id"/> <field name="component_id"/>
<field name="display_name"/> <field name="display_name"/>
@ -97,7 +97,7 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
</field> </field>
</page> </page>
<page string="Materials" invisible="dc_type == 'out_souce_return' or dc_type == 'out_souce_noreturn'"> <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"> <tree editable="bottom">
<field name="component_id"/> <field name="component_id"/>
<field name="display_name"/> <field name="display_name"/>
@ -143,7 +143,10 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
</field> </field>
</page> </page>
<page string="Logistice Details">
<field name="courier"/>
<field name="lr_no"/>
</page>
</notebook> </notebook>
<br></br> <br></br>
@ -217,7 +220,7 @@ name="action_report_dc_btn"><i class="fa fa-print"></i> Print</button>
</record> </record>
<menuitem id="delivery_challan_menu" <menuitem id="delivery_challan_menu"
name="Delivery Challan (DC)" 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> </odoo>

View File

@ -363,7 +363,7 @@
</record> </record>
<menuitem id="sos_deliverables_boq_menu" <menuitem id="sos_deliverables_boq_menu"
name="Deliverables List / BOQ" 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> </odoo>

View File

@ -8,7 +8,8 @@
<sheet> <sheet>
<group> <group>
<field name="fg_name"/> <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_name"/>
<field name="customer_location"/> --> <field name="customer_location"/> -->
</group> </group>
@ -91,6 +92,7 @@
<tree string="Deliverables"> <tree string="Deliverables">
<field name="fg_name"/> <field name="fg_name"/>
<field name="communication_type"/> <field name="communication_type"/>
<field name="slave_type"/>
<field name="fg_ids"/> <field name="fg_ids"/>
<field name="sfg_ids"/> <field name="sfg_ids"/>
<field name="material_ids"/> <field name="material_ids"/>

View File

@ -11,6 +11,7 @@
<field name="model">sos_departments</field> <field name="model">sos_departments</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="short_form"/>
<field name="name"/> <field name="name"/>
</tree> </tree>
</field> </field>
@ -20,9 +21,18 @@
<field name="model">sos_departments</field> <field name="model">sos_departments</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="Model Form"> <form string="Model Form">
<sheet> <sheet>
<group><field name="name"/></group> <group><field name="name"/></group>
</sheet> <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> </form>
</field> </field>
</record> </record>

View File

@ -216,13 +216,15 @@
<field name="payment_status"/> <field name="payment_status"/>
<field name="billing_address"/> <field name="billing_address"/>
<field name="shipping_address"/>
</group> </group>
<group> <group>
<field name="invoice_no"/> <field name="invoice_no"/>
<field name="invoice_date"/>
<field name="warranty"/>
<field name="gst_no"/> <field name="gst_no"/>
<field name="shipping_address"/>
</group> </group>
</group> </group>

View File

@ -12,6 +12,8 @@
decoration-warning="indent_status == 'hold'" decoration-warning="indent_status == 'hold'"
decoration-danger="indent_status == 'open' or indent_status == 'cancel'" 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"/> <field name="approved_by" string="Approved By" widget="many2one_avatar_user"/>
</tree> </tree>
@ -184,7 +186,8 @@
<templates> <templates>
<div class="row" style="margin-top:100px"> <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;"> <tr style="border-bottom: solid 1px #ccc;">
<td style="padding: 8px;" class="column"><b>Prepared By Sign</b> <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> <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> <td><field name="prepared_by" readonly="1"/></td>
</tr> </tr>
</table> </div> </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"> <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;"> <tr style="border-bottom: solid 1px #ccc;">
<td style="padding: 8px;" class="column"><b>Top Management Approval Sign</b> <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> <button string="Approve" invisible="approval_image" class="btn-primary custom_btn" type="object" name="action_top_approver_esign_btn"></button>
@ -233,5 +253,6 @@
name="Indent Plan" name="Indent Plan"
parent="indent_menu_root" parent="indent_menu_root"
action = "action_fg_plan_list"/> action = "action_fg_plan_list"/>
</odoo> </odoo>

View File

@ -10,7 +10,10 @@
<field name="fg_type" string="Type" icon="fa-list-ul" enable_counters="1"/> <field name="fg_type" string="Type" icon="fa-list-ul" enable_counters="1"/>
</searchpanel> </searchpanel>
<field name="display_name" string="Display Name"/>
<field name="name" string="Name"/> <field name="name" string="Name"/>
</search> </search>
</field> </field>

View File

@ -248,7 +248,7 @@
</field> </field>
</record> </record>
<!-- Menuitem --> <!-- 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> </data>
</odoo> </odoo>

Some files were not shown because too many files have changed in this diff Show More