diff --git a/sos_inventory/__manifest__.py b/sos_inventory/__manifest__.py index 5408d67..693eeaa 100755 --- a/sos_inventory/__manifest__.py +++ b/sos_inventory/__manifest__.py @@ -11,7 +11,7 @@ 'version': '17.0.1.0.0', # any module necessary for this one to work correctly - 'depends': ['base','web','mail'], + 'depends': ['base','web','mail','one2many_search_widget'], # always loaded 'data': [ @@ -19,6 +19,7 @@ 'security/record_rules.xml', 'security/ir.model.access.csv', 'data/cron_jobs.xml', + 'views/pareto_chart_widget_view.xml', 'views/sos_audit_log_view.xml', 'views/menu.xml', 'views/sos_quote_generation.xml', @@ -26,6 +27,7 @@ 'views/sos_fg_view.xml', 'views/sos_sfg_view.xml', 'views/sos_material_view.xml', + 'views/sos_budget_plan_view.xml', 'views/sos_material_bom_view.xml', 'views/sos_sfg_bom_view.xml', 'views/sos_fg_bom_view.xml', @@ -81,6 +83,8 @@ 'views/sos_service_call_log_report.xml', 'views/sos_transfer_challan_return_from_customer_view.xml', 'views/sos_shelflife_register_view.xml', + 'views/sos_prf_view.xml', + 'wizard/sfg_bom_bulk_upload_view.xml', 'wizard/mon_bulk_upload_view.xml', 'wizard/missing_component_wizard.xml', @@ -115,6 +119,7 @@ 'report/sos_boq_labels.xml', 'report/shelflife_report.xml', 'report/sos_boq_report.xml', + 'report/sos_budget_plan_summary.xml', 'data/send_indent_plan_email_template.xml', 'data/selection_item.xml' @@ -130,6 +135,8 @@ 'sos_inventory/static/src/components/**/*.js', 'sos_inventory/static/src/components/**/*.xml', 'sos_inventory/static/src/components/**/*.scss', + 'sos_inventory/static/src/js/pareto_chart_widget.js', + 'sos_inventory/static/src/xml/pareto_chart_template.xml', diff --git a/sos_inventory/controllers/__pycache__/controllers.cpython-310.pyc b/sos_inventory/controllers/__pycache__/controllers.cpython-310.pyc index 4af816c..ce16904 100644 Binary files a/sos_inventory/controllers/__pycache__/controllers.cpython-310.pyc and b/sos_inventory/controllers/__pycache__/controllers.cpython-310.pyc differ diff --git a/sos_inventory/controllers/controllers.py b/sos_inventory/controllers/controllers.py index d281927..625b1ad 100755 --- a/sos_inventory/controllers/controllers.py +++ b/sos_inventory/controllers/controllers.py @@ -4,7 +4,9 @@ import io import xlsxwriter class MaterialBackupExportController(http.Controller): - + @http.route('/supplier/form', auth='public') + def index(self, **kw): + return "Hello, world" @http.route(['/download/material/backup//'], type='http', auth='user') def download_backup_by_year(self, year, item_type, **kwargs): # Safely map table names diff --git a/sos_inventory/models/__init__.py b/sos_inventory/models/__init__.py index 3537e14..f507b75 100755 --- a/sos_inventory/models/__init__.py +++ b/sos_inventory/models/__init__.py @@ -58,4 +58,6 @@ from . import sos_master_customer_property from . import sos_service_call_log_report from . import sos_inhouse_validation_reports_files from . import sos_transfer_challan_return_from_customer -from . import sos_shelflife_register \ No newline at end of file +from . import sos_shelflife_register +from . import sos_budget_plan +from . import sos_prf \ No newline at end of file diff --git a/sos_inventory/models/__pycache__/__init__.cpython-310.pyc b/sos_inventory/models/__pycache__/__init__.cpython-310.pyc index e4b2598..39fff92 100644 Binary files a/sos_inventory/models/__pycache__/__init__.cpython-310.pyc and b/sos_inventory/models/__pycache__/__init__.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_ccrf.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_ccrf.cpython-310.pyc index 57c09ab..74d05d9 100644 Binary files a/sos_inventory/models/__pycache__/sos_ccrf.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_ccrf.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_common_scripts.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_common_scripts.cpython-310.pyc index 24caafa..0c7c29b 100644 Binary files a/sos_inventory/models/__pycache__/sos_common_scripts.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_common_scripts.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_dc.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_dc.cpython-310.pyc index 78a2dae..ce3ef1c 100644 Binary files a/sos_inventory/models/__pycache__/sos_dc.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_dc.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_deliverables_config.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_deliverables_config.cpython-310.pyc index 709054c..9c378dc 100644 Binary files a/sos_inventory/models/__pycache__/sos_deliverables_config.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_deliverables_config.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_departments.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_departments.cpython-310.pyc index b97ef6d..a58111f 100644 Binary files a/sos_inventory/models/__pycache__/sos_departments.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_departments.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_disposal_register.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_disposal_register.cpython-310.pyc index 2030961..72236a7 100644 Binary files a/sos_inventory/models/__pycache__/sos_disposal_register.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_disposal_register.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_dock_audit.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_dock_audit.cpython-310.pyc index 5f36228..d623045 100644 Binary files a/sos_inventory/models/__pycache__/sos_dock_audit.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_dock_audit.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_fg_plan.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_fg_plan.cpython-310.pyc index 9eb078c..503f044 100644 Binary files a/sos_inventory/models/__pycache__/sos_fg_plan.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_fg_plan.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_fir_brr.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_fir_brr.cpython-310.pyc index e3a5a10..4d7d038 100644 Binary files a/sos_inventory/models/__pycache__/sos_fir_brr.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_fir_brr.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_grn.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_grn.cpython-310.pyc index 328c1d9..335fa01 100644 Binary files a/sos_inventory/models/__pycache__/sos_grn.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_grn.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_ir.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_ir.cpython-310.pyc index 1cb55d4..c701614 100644 Binary files a/sos_inventory/models/__pycache__/sos_ir.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_ir.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_mat_outsourcing_vendor_register.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_mat_outsourcing_vendor_register.cpython-310.pyc index 090f2db..68491d9 100644 Binary files a/sos_inventory/models/__pycache__/sos_mat_outsourcing_vendor_register.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_mat_outsourcing_vendor_register.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_mon.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_mon.cpython-310.pyc index e58ea79..cb4f6fa 100644 Binary files a/sos_inventory/models/__pycache__/sos_mon.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_mon.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_mrn.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_mrn.cpython-310.pyc index 50a8b9d..bacd314 100644 Binary files a/sos_inventory/models/__pycache__/sos_mrn.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_mrn.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_ncmr.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_ncmr.cpython-310.pyc index 22742de..f205301 100644 Binary files a/sos_inventory/models/__pycache__/sos_ncmr.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_ncmr.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_order_delivery_plan.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_order_delivery_plan.cpython-310.pyc index 02db84d..c13fab9 100644 Binary files a/sos_inventory/models/__pycache__/sos_order_delivery_plan.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_order_delivery_plan.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_outsourcing_vendor_monitoring_register.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_outsourcing_vendor_monitoring_register.cpython-310.pyc index 952cdf0..1b260b5 100644 Binary files a/sos_inventory/models/__pycache__/sos_outsourcing_vendor_monitoring_register.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_outsourcing_vendor_monitoring_register.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_po.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_po.cpython-310.pyc index 2854de9..e53347a 100644 Binary files a/sos_inventory/models/__pycache__/sos_po.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_po.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_prf.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_prf.cpython-310.pyc index f1ed75f..3afdd6b 100755 Binary files a/sos_inventory/models/__pycache__/sos_prf.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_prf.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_quote_generation.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_quote_generation.cpython-310.pyc index 7992cd2..7217122 100644 Binary files a/sos_inventory/models/__pycache__/sos_quote_generation.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_quote_generation.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_sales_order.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_sales_order.cpython-310.pyc index b5bd074..7c449a6 100644 Binary files a/sos_inventory/models/__pycache__/sos_sales_order.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_sales_order.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_service_call_log_report.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_service_call_log_report.cpython-310.pyc index dc1ed0a..c447277 100644 Binary files a/sos_inventory/models/__pycache__/sos_service_call_log_report.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_service_call_log_report.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_sfg.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_sfg.cpython-310.pyc index 0bd559c..4fc940e 100644 Binary files a/sos_inventory/models/__pycache__/sos_sfg.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_sfg.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_sfg_outsourcing_return_register.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_sfg_outsourcing_return_register.cpython-310.pyc index 7271a52..cad3c1f 100644 Binary files a/sos_inventory/models/__pycache__/sos_sfg_outsourcing_return_register.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_sfg_outsourcing_return_register.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_wo.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_wo.cpython-310.pyc index 1b151b5..d82393e 100644 Binary files a/sos_inventory/models/__pycache__/sos_wo.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_wo.cpython-310.pyc differ diff --git a/sos_inventory/models/sos_budget_plan.py b/sos_inventory/models/sos_budget_plan.py new file mode 100755 index 0000000..383790a --- /dev/null +++ b/sos_inventory/models/sos_budget_plan.py @@ -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 '', + } + ) \ No newline at end of file diff --git a/sos_inventory/models/sos_ccrf.py b/sos_inventory/models/sos_ccrf.py index 07f3a75..3ad23c7 100755 --- a/sos_inventory/models/sos_ccrf.py +++ b/sos_inventory/models/sos_ccrf.py @@ -41,7 +41,7 @@ class SOS_CCRF(models.Model): batch_release_date = fields.Date(string="Invoice Date") brcoa_no = fields.Char(string="BRCOA No") brcoa_date = fields.Date(string="BRCOA Date") - other_complaints = fields.Text(string="Details of any other Complaints received from this Production run") + other_complaints = fields.Text(string="Defective Details/Symptom") data_collected_by = fields.Many2one('res.users', string='Data Collected By') data_collected_image = fields.Image(related="data_collected_by.signature_image",string='Data Collected Sign',readonly=True) data_collected_on = fields.Datetime(string="Data Collected On") @@ -56,12 +56,17 @@ class SOS_CCRF(models.Model): investigation_carriedout_image = fields.Image(related="investigation_carriedout_by.signature_image",string='Investigation Carried Out Sign',readonly=True) investigation_carriedout_on = fields.Datetime(string="Investigation Carried Out On") root_cause = fields.Html(string="Root Cause", - default="

Why #1

Why #2

Why #3

") + default="

Why #1 :

Why #2 :

Why #3 :

Why #4 :

Why #5 :

") + #team_formation = fields.Html(string="Team Formation", + #default="

Name :


Department :


Roles :

") + #problem_description = fields.Html(string="Problem Description", + #default="

Clear Statement :


Photo :


What :


Where :


When :


Why :


Who :


How :


How many :

") rootcause_carriedout_by = fields.Many2one('res.users', string='Root Cause Carried Out By') rootcause_carriedout_image = fields.Image(related="rootcause_carriedout_by.signature_image",string='Root Cause Carried Out Sign',readonly=True) rootcause_carriedout_on = fields.Datetime(string="Root Cause Carried Out On") - corrective_action = fields.Html(string="Corrective Action") - preventive_action = fields.Html(string="Preventive Action") + corrective_action = fields.Html(string="D5:Permanent Corrective Action") + implement_validate_corrective_action = fields.Html(string="D6:Implement & Validate Corrective Actions") + preventive_action = fields.Html(string="D7:Preventive Recurrence") capa_status = fields.Selection([ ('open', 'OPEN'),('close', 'Close')], default='open' , string="Status") status_reviewed_by = fields.Many2one('res.users', string='Status Reviewed By') status_reviewed_image = fields.Image(related="status_reviewed_by.signature_image",string='Status Reviewed Out Sign',readonly=True) @@ -76,6 +81,11 @@ class SOS_CCRF(models.Model): rootcause_verifiedout_image = fields.Image(related="rootcause_verifiedout_by.signature_image",string='Root Cause Verified By Sign',readonly=True) rootcause_verifiedout_on = fields.Datetime(string="Root Cause Verified On") helper_field = fields.Many2many('sos_fg', string="Helper Field") + team_recognition = fields.Html(string="Team Recognition (Image)") + capa_line_ids = fields.One2many('sos_ccrf_capa_line', 'ccrf_id', string="CAPA Line Ids",copy=True) + team_formation_line_ids = fields.One2many('sos_ccrf_team_formation_line', 'ccrf_id', string="Team Formation Line Ids",copy=True) + problem_description_line_ids = fields.One2many('sos_ccrf_problem_description_line', 'ccrf_id', string="Problem Description Line Ids",copy=True) + @api.onchange('fg_name') def _onchange_fg_name(self): if self.fg_name: @@ -140,4 +150,38 @@ class SOS_CCRF(models.Model): 'rootcause_verifiedout_by', 'rootcause_verifiedout_on' ) - \ No newline at end of file + +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") \ No newline at end of file diff --git a/sos_inventory/models/sos_common_scripts.py b/sos_inventory/models/sos_common_scripts.py index b6b30f6..bce4650 100755 --- a/sos_inventory/models/sos_common_scripts.py +++ b/sos_inventory/models/sos_common_scripts.py @@ -288,7 +288,7 @@ class Sequence_Generator(models.AbstractModel): reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)]) email_to = reporting_user.login else: - email_to = "ramachandran.r@sosaley.in" + email_to = "ramachandran.r@sosaley.com" mail_values = { 'subject': subject, 'body_html': body_html, diff --git a/sos_inventory/models/sos_dc.py b/sos_inventory/models/sos_dc.py index 69703f4..48a3276 100755 --- a/sos_inventory/models/sos_dc.py +++ b/sos_inventory/models/sos_dc.py @@ -60,7 +60,8 @@ class sos__dc(models.Model): dept_in_charge_image = fields.Image(related="dept_in_charge_name.signature_image",string='Department In-Charge Sign',readonly=True) dept_in_charge_approved_on = fields.Datetime(string="Approved On") remarks = fields.Text(string="Remarks") - + courier = fields.Char(string="Courier Name") + lr_no = fields.Char(string="LR No") @api.onchange('dock_audit_no') def _onchange_dock_audit_no(self): if self.dock_audit_no: @@ -220,7 +221,7 @@ class sos__dc(models.Model): """ subject = f"Delivery Challan Approval Request - {self.dc_no}" send_email = self.env['sos_common_scripts'] - send_email.send_direct_email(self.env,"sos_dc",self.id,"ramachandran.r@sosaley.in",subject,body_html) + send_email.send_direct_email(self.env,"sos_dc",self.id,"ramachandran.r@sosaley.com",subject,body_html) # Email part ends sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( diff --git a/sos_inventory/models/sos_deliverables_config.py b/sos_inventory/models/sos_deliverables_config.py index 07b66f2..33159f8 100755 --- a/sos_inventory/models/sos_deliverables_config.py +++ b/sos_inventory/models/sos_deliverables_config.py @@ -22,7 +22,12 @@ class SosTestingParameters(models.Model): communication_type = fields.Selection([ ('wired', 'Wired'), ('wireless', 'Wireless') - ], string="Communication Type", default='wired') + ], string="Communication Type") + slave_type = fields.Selection([ + ('15S Individual Slave', '15S Individual Slave'), + ('90S Electrical Panel', '90S Electrical Panel'), + ('60S Electrical Panel', '60S Electrical Panel') + ], string="Slave Type") fg_ids = fields.One2many('sos_fg_deliverables', 'ref_id', string= 'FG Deliverables',copy=True) sfg_ids = fields.One2many('sos_sfg_deliverables', 'ref_id', string= 'SFG Deliverables',copy=True) material_ids = fields.One2many('sos_material_deliverables', 'ref_id', string='Material Deliverables',copy=True) @@ -57,6 +62,7 @@ class SOS_SFG_Deliverables(models.Model): item_type = fields.Selection([ ('Master Panel', 'Master Panel'), ('CT Module', 'CT Module'), + ('Battery Count', 'Battery Count'), ('Slave Module', 'Slave Module'), ('Internet Module', 'Internet Module') ], string="Type",default="Master Panel") @@ -82,6 +88,7 @@ class SOS_Material_Deliverables(models.Model): item_type = fields.Selection([ ('Master Panel', 'Master Panel'), ('CT Module', 'CT Module'), + ('Battery Count', 'Battery Count'), ('Slave Module', 'Slave Module'), ('Internet Module', 'Internet Module') ], string="Type",default="Master Panel") diff --git a/sos_inventory/models/sos_departments.py b/sos_inventory/models/sos_departments.py index 69cd10e..7996278 100755 --- a/sos_inventory/models/sos_departments.py +++ b/sos_inventory/models/sos_departments.py @@ -8,4 +8,34 @@ class sos_department(models.Model): name = fields.Char(string="Department Name") - \ No newline at end of file + 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}." + # } + # } + + + \ No newline at end of file diff --git a/sos_inventory/models/sos_disposal_register.py b/sos_inventory/models/sos_disposal_register.py index 5d6fa02..6e0bca2 100755 --- a/sos_inventory/models/sos_disposal_register.py +++ b/sos_inventory/models/sos_disposal_register.py @@ -96,7 +96,7 @@ class sos_Disposal_Register(models.Model):

Below Item is waiting for your approval to Dispose

""" - sequence_util.send_direct_email(self.env,"sos_disposal_register",self.id,"ramachandran.r@sosaley.in","Disposal Approved",body_html) + sequence_util.send_direct_email(self.env,"sos_disposal_register",self.id,"ramachandran.r@sosaley.com","Disposal Approved",body_html) # Email part ends return sequence_util.action_assign_signature( self, diff --git a/sos_inventory/models/sos_dock_audit.py b/sos_inventory/models/sos_dock_audit.py index 83d6754..6f56144 100755 --- a/sos_inventory/models/sos_dock_audit.py +++ b/sos_inventory/models/sos_dock_audit.py @@ -30,7 +30,9 @@ class SOS_Dock_Audit(models.Model): ], string="Product Type",required=True) quantity = fields.Integer(string="Quantity") + warranty = fields.Integer(string="Warranty(In Months)") invoice_no = fields.Char(string="Invoice No") + invoice_date = fields.Date(string="Invoice Date") customer_name = fields.Char(string="Customer Name") lead_time = fields.Datetime(string="Lead Time") customer_po_no = fields.Char(string="PO No") @@ -140,6 +142,18 @@ class SOS_Dock_Audit(models.Model): }) def action_acc_esign_btn(self): + required_fields = ['payment_status', 'invoice_date','invoice_no', 'billing_address', 'gst_no', 'shipping_address'] + missing_fields = [] + + for field in required_fields: + if not self[field]: # Check if field is empty/False + missing_fields.append(field) + + # If any fields are missing, show warning + if missing_fields: + warning_msg = "Please fill the following required fields before proceeding:\n" + warning_msg += "\n".join([f"👉 {field.replace('_', ' ').title()}" for field in missing_fields]) + raise UserError(warning_msg) sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( self, @@ -151,7 +165,7 @@ class SOS_Dock_Audit(models.Model): body_html = f"""

Below Dock Audit is waiting for your Approval

""" - sequence_util.send_direct_email(self.env,"sos_dock_audit",self.id,"ramachandran.r@sosaley.in","Dock Audit Approval",body_html) + sequence_util.send_direct_email(self.env,"sos_dock_audit",self.id,"ramachandran.r@sosaley.com","Dock Audit Approval",body_html) # Email part ends def action_auditor_esign_btn(self): sequence_util = self.env['sos_common_scripts'] @@ -206,7 +220,7 @@ class SOS_Dock_Audit(models.Model): self.shipping_address = self.deliverables_boq_id.sales_id.shipping_address self.gst_no = self.deliverables_boq_id.sales_id.gst_no self.payment_status = self.deliverables_boq_id.sales_id.payment_status - self.customer_name = self.deliverables_boq_id.sales_id.customer_name + self.customer_name = self.deliverables_boq_id.sales_id.customer_name.customer_name self.fg_name = self.deliverables_boq_id.sales_id.fg_name self.quantity = self.deliverables_boq_id.sales_id.qty self.lead_time = self.deliverables_boq_id.sales_id.lead_time diff --git a/sos_inventory/models/sos_fg_plan.py b/sos_inventory/models/sos_fg_plan.py index 8a0972a..04d977c 100755 --- a/sos_inventory/models/sos_fg_plan.py +++ b/sos_inventory/models/sos_fg_plan.py @@ -30,6 +30,11 @@ class SOS_FG_Plan(models.Model): prepared_by = fields.Many2one('res.users', string='Planned By') prepared_image = fields.Image(related="prepared_by.signature_image",string='Prepared By Sign',readonly=True) prepared_on = fields.Datetime(string="Planned On") + + accounts_approved_on = fields.Datetime(string="Accounts Approved On") + accounts_approved_name = fields.Many2one('res.users', string='Accounts Sign') + accounts_approved_by_image = fields.Image(related="accounts_approved_name.signature_image",string='Accounts Sign',readonly=True) + blowup = fields.Boolean(string="Blowup Done",default=False) target_date = fields.Date(string="Target Date",required=True) indent_start_date = fields.Date(string="Indent Start Date", default=lambda self: fields.Date.context_today(self)) @@ -116,29 +121,45 @@ class SOS_FG_Plan(models.Model): month = today.strftime('%m') base_sequence_prefix = f"SOS/{form_name}/{fy}/{month}/" + # Search for latest record for this fiscal year (not just this month) records = self.env[model_name].sudo().search( - [(field_name, 'like', f"{base_sequence_prefix}%")], + [(field_name, 'like', f"SOS/{form_name}/{fy}/%")], order=f"{field_name} desc", limit=1 ) if records: last_sequence = records[0][field_name] - last_suffix = last_sequence.split('/')[-1] - if last_suffix.isdigit(): - new_suffix = f"{last_suffix}a" + parts = last_sequence.split('/') + last_month = parts[-2] + last_suffix = parts[-1] + + # Extract numeric part only (ignore trailing alphabet) + numeric_part = ''.join(filter(str.isdigit, last_suffix)) + + if last_month != month: + # New month: increment numeric part, no suffix + new_number = int(numeric_part) + 1 + new_suffix = f"{new_number:03d}" else: - base_num = last_suffix[:-1] - last_alpha = last_suffix[-1] - next_alpha = chr(ord(last_alpha) + 1) if last_alpha < 'z' else 'a' - new_suffix = f"{base_num}{next_alpha}" + # Same month: check for alphabet suffix + alpha_part = ''.join(filter(str.isalpha, last_suffix)) + if alpha_part: + next_alpha = chr(ord(alpha_part) + 1) if alpha_part < 'z' else 'a' + new_suffix = f"{numeric_part}{next_alpha}" + else: + # First duplicate in same month + new_suffix = f"{numeric_part}a" else: - new_suffix = '016' + # No previous records at all + new_suffix = '001' return f"{base_sequence_prefix}{new_suffix}" + + def _get_default_lines(self,model,column): products = self.env[model].search([]) @@ -170,9 +191,10 @@ class SOS_FG_Plan(models.Model): def send_indent_plan_email(self, email_ids): + valid_emails = [email for email in email_ids if email] template = self.env.ref('sos_inventory.send_indent_plan_email_template') if template: - template.email_to = ','.join(email_ids) + template.email_to = ','.join(valid_emails) template.send_mail(self.id, force_send=True) def get_unique_emails(self): group_refs = [ @@ -290,7 +312,22 @@ class SOS_FG_Plan(models.Model): else: worksheet.write(row, 1, value) - + def action_acc_approver_esign_btn(self): + sequence_util = self.env['sos_common_scripts'] + body_html = f""" +

Below Indent Budget is waiting for your Approval

+ """ + + send_email = self.env['sos_common_scripts'] + send_email.send_direct_email(self.env,"sos_fg_plan",self.id,"ramachandran.r@sosaley.com","Indent Budget Approval",body_html) + result = sequence_util.action_assign_signature( + self, + 'accounts_approved_name', + 'accounts_approved_on', + 'sos_inventory.sos_finance_user' + ) + + def action_top_approver_esign_btn(self): sequence_util = self.env['sos_common_scripts'] result = sequence_util.action_assign_signature( @@ -334,11 +371,9 @@ class SOS_FG_Plan(models.Model): body_html = f"""

Below Indent Budget is waiting for your Approval

""" - send_email = self.env['sos_common_scripts'] - send_email.send_direct_email(self.env,"sos_fg_plan",self.id,"ramachandran.r@sosaley.in","Indent Budget Approval",body_html) - sequence_util = self.env['sos_common_scripts'] - result = sequence_util.action_assign_signature( + send_email.send_group_email(self.env,'sos_fg_plan',self.id,"deenalaura.m@sosaley.in","Indent Budget Approval",body_html,'sos_inventory.sos_finance_user') + result = send_email.action_assign_signature( self, 'prepared_by', 'prepared_on' diff --git a/sos_inventory/models/sos_fir_brr.py b/sos_inventory/models/sos_fir_brr.py index a7d26c6..dd245f6 100755 --- a/sos_inventory/models/sos_fir_brr.py +++ b/sos_inventory/models/sos_fir_brr.py @@ -125,6 +125,7 @@ class FIR_BRR(models.Model): sos_record.target_date , date.today() ) + week_number = min(week_number, 8) field_name = f'qc_week_{week_number}' fgplan_field_name = f'planned_week_{week_number}' # FG Plan update diff --git a/sos_inventory/models/sos_grn.py b/sos_inventory/models/sos_grn.py index 76c8f44..2e5d3b8 100755 --- a/sos_inventory/models/sos_grn.py +++ b/sos_inventory/models/sos_grn.py @@ -77,6 +77,7 @@ class sos__grn(models.Model): print(f"Failed to find report action: {e}") def action_report_esign_btn(self): + sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( self, @@ -84,6 +85,7 @@ class sos__grn(models.Model): 'stores_approval_on', 'sos_inventory.sos_scg_group_user' ) + self.generate_supplier_service() if self.received_goods_type == "Materials": for item in self.line_ids: component = self.env['sos_material'].browse(item.component_id.id) @@ -148,7 +150,180 @@ class sos__grn(models.Model): def _compute_sequence(self): sequence_util = self.env['sos_common_scripts'] return sequence_util.generate_sequence('sos_grn','GRN', 'grn_no') + def generate_supplier_service(self): + grn = self.env['sos_grn'].browse(self.id) + + if self.received_goods_type == "Materials": + # Check for existing register line + already_created = self.env['sos_mat_outsourcing_vendor_register_lines'].search([ + ('ref_id.supplier_name', '=', grn.supplier_name.id), + ('po_no', '=', grn.po_no.id) + ], limit=1) + # Get or create the main register + outsource = self.env['sos_mat_outsourcing_vendor_register'].search( + [('supplier_name', '=', grn.supplier_name.id)], limit=1) + if not outsource: + outsource = self.env['sos_mat_outsourcing_vendor_register'].create({ + 'supplier_name': grn.supplier_name.id, + }) + ir_records = self.env['sos_ir'].search([('po_no', '=', grn.po_no.id)]) + iqi_records = self.env['sos_iqi'].search([('ir_id_unique_id', 'in', ir_records.ids)]) + grn_records = self.env['sos_grn'].search([('po_no', '=', grn.po_no.id)]) + grn_line_records = self.env['sos_grn_line'].search([('grn_id', 'in', grn_records.ids)]) + + # Prepare material_names list + mat_record = [line.component_id.id for line in grn_line_records if line.component_id and line.component_id.part_no] + material_names = [(6, 0, mat_record)] + + # Sum all GRN line quantities including current + total_received_qty = sum(grn_line_records.mapped('received_qty')) #+ new_received + total_approved_qty = sum(grn_line_records.mapped('approved_qty')) #+ new_approved + total_rejected_qty = sum(grn_line_records.mapped('rejected_qty')) #+ new_rejected + + delivery_values = grn_records.mapped('delivery') + delivery_numbers = [float(value) for value in delivery_values if value] + existing_count = len(delivery_numbers) + + responsive_values = grn_records.mapped('responsiveness') + responsive_numbers = [float(value) for value in responsive_values if value] + + reports_values = grn_records.mapped('reports') + reports_numbers = [float(value) for value in reports_values if value] + + avg_delivery_marks = sum(delivery_numbers) / (existing_count) + delivery = ( + 30 if 27.5 < avg_delivery_marks <= 30 else + 25 if 22.5 < avg_delivery_marks <= 27.5 else + 10 if avg_delivery_marks < 20 else + 0 + ) + + avg_report_marks = sum(reports_numbers) / (existing_count) + report = ( + 30 if 27.5 < avg_report_marks <= 30 else + 25 if 22.5 < avg_report_marks <= 27.5 else + 10 if avg_report_marks < 20 else + 0 + ) + + avg_responsiveness_marks = sum(responsive_numbers) / (existing_count) + + responsiveness = ( + 10 if 5 <= avg_responsiveness_marks <= 10 else + 5 if avg_responsiveness_marks < 5 else + 0 + ) + if already_created: + updated_vals = { + 'received_qty': total_received_qty, + 'approved_qty': total_approved_qty, + 'rejected_qty': total_rejected_qty, + 'delivery_marks': delivery, + 'responsiveness_marks': responsiveness, + 'report_marks': report, + 'iqi_references': [(6, 0, iqi_records.ids)], + 'grn_references': [(6, 0, grn_records.ids)], + 'material_names': material_names, + } + already_created.write(updated_vals) + else: + self.env['sos_mat_outsourcing_vendor_register_lines'].create({ + 'ref_id': outsource.id, + 'po_no': grn.po_no.id, + 'received_qty': total_received_qty, + 'approved_qty': total_approved_qty, + 'rejected_qty': total_rejected_qty, + 'iqi_references': [(6, 0, iqi_records.ids)], + 'grn_references': [(6, 0, grn_records.ids)], + 'material_names': material_names, + 'delivery_marks': delivery, + 'responsiveness_marks': responsiveness, + 'report_marks': report, + }) + + elif self.received_goods_type == "SFG": + + # Check for existing register line + already_created = self.env['sos_outsourcing_vendor_monitoring_register_lines'].search([ + ('ref_id.service_provider_name', '=', grn.service_provider_name.id), + ('wo_no', '=', grn.wo_no.id) + ], limit=1) + + # Get or create the main register + outsource = self.env['sos_outsourcing_vendor_monitoring_register'].search( + [('service_provider_name', '=', grn.service_provider_name.id)], limit=1) + if not outsource: + outsource = self.env['sos_outsourcing_vendor_monitoring_register'].create({ + 'service_provider_name': grn.service_provider_name.id, + }) + # Related documents + ir_records = self.env['sos_ir'].search([('wo_no', '=', grn.wo_no.id)]) + iqi_records = self.env['sos_iqi'].search([('ir_id_unique_id', 'in', ir_records.ids)]) + dc_records = self.env['sos_dc'].search([('wo_no', '=', grn.wo_no.id)]) + grn_records = self.env['sos_grn'].search([('wo_no', '=', grn.wo_no.id)]) + + grn_line_records = self.env['sos_grn_line_sfg'].search([('grn_id', 'in', grn_records.ids)]) + + # Prepare material_names list + sfg_ids = [line.component_id.id for line in grn_line_records if line.component_id and line.component_id.name] + + # Include current line component manually if not yet saved in DB + current_component_id = self.line_ids.component_id + if current_component_id and current_component_id not in sfg_ids: + sfg_ids.append(current_component_id) + + sfg_names = [(6, 0, sfg_ids)] + + total_received_qty = sum(grn_line_records.mapped('received_qty')) + total_approved_qty = sum(grn_line_records.mapped('approved_qty')) + total_rejected_qty = sum(grn_line_records.mapped('rejected_qty')) + + delivery_values = grn_line_records.mapped('delivery') + delivery_numbers = [float(value) for value in delivery_values if value] + existing_count = len(delivery_numbers) + + responsive_values = grn_line_records.mapped('responsiveness') + responsive_numbers = [float(value) for value in responsive_values if value] + + reports_values = grn_line_records.mapped('reports') + reports_numbers = [float(value) for value in reports_values if value] + + avg_delivery_marks = sum(delivery_numbers) / (existing_count) + avg_responsiveness_marks = sum(responsive_numbers) / (existing_count) + avg_report_marks = sum(reports_numbers) / (existing_count) + + if already_created: + #print(f" sfg_names : if {sfg_names}") + updated_vals = { + 'received_qty': total_received_qty, + 'approved_qty': total_approved_qty, + 'rejected_qty': total_rejected_qty, + 'delivery_marks': avg_delivery_marks, + 'responsiveness_marks': avg_responsiveness_marks, + 'report_marks': avg_report_marks, + 'iqi_references': [(6, 0, iqi_records.ids)], + 'dc_references': [(6, 0, dc_records.ids)], + 'grn_references': [(6, 0, grn_records.ids)], + 'sfg_names': sfg_names, + } + already_created.write(updated_vals) + else: + #print(f" sfg_names : else {sfg_names}") + self.env['sos_outsourcing_vendor_monitoring_register_lines'].create({ + 'ref_id': outsource.id, + 'iqi_references': [(6, 0, iqi_records.ids)], + 'dc_references': [(6, 0, dc_records.ids)], + 'grn_references': [(6, 0, grn_records.ids)], + 'sfg_names': sfg_names, + 'wo_no': grn.wo_no.id, + 'received_qty': total_received_qty, + 'approved_qty': total_approved_qty, + 'rejected_qty': total_rejected_qty, + 'delivery_marks': avg_delivery_marks, + 'responsiveness_marks': avg_responsiveness_marks, + 'report_marks': avg_report_marks, + }) class sos_grn_line(models.Model): _name = 'sos_grn_line' _description = 'GRN Material Lines' @@ -169,14 +344,19 @@ class sos_grn_line(models.Model): @api.depends('received_qty', 'rejected_qty') def _compute_quality(self): for record in self: - if record.rejected_qty != 0: - record.quality_marks = (100 - ((record.rejected_qty / record.received_qty) * 100)) - if record.quality_marks >= 80 and record.quality_marks <=99: - record.quality_marks = 25 + record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty) + + + def _calculate_quality_marks(self, received, rejected): + if received != 0: + q = 100 - ((rejected / received) * 100) + if 91 <= q <= 100: + return 30 + elif 80 <= q <= 90: + return 25 else: - record.quality_marks = 10 - else: - record.quality_marks = 100 + return 10 + return 0 @api.model def write(self, vals): @@ -295,14 +475,20 @@ class sos_grn_line_sfg(models.Model): @api.depends('received_qty', 'rejected_qty') def _compute_sfgquality(self): for record in self: - if record.rejected_qty != 0: - record.quality_marks = (100 - ((record.rejected_qty / record.received_qty) * 100)) - if record.quality_marks >= 80 and record.quality_marks <=99: - record.quality_marks = 25 + record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty) + + + + def _calculate_quality_marks(self, received, rejected): + if received != 0: + q = 100 - ((rejected / received) * 100) + if 91 <= q <= 100: + return 30 + elif 80 <= q <= 90: + return 25 else: - record.quality_marks = 10 - else: - record.quality_marks = 100 + return 10 + return 0 class sos_grn_line_fg(models.Model): _name = 'sos_grn_line_fg' diff --git a/sos_inventory/models/sos_ir.py b/sos_inventory/models/sos_ir.py index 6a19daa..8f043a0 100755 --- a/sos_inventory/models/sos_ir.py +++ b/sos_inventory/models/sos_ir.py @@ -276,25 +276,26 @@ class SOS_IR(models.Model): else: - iqi_record = self.env['sos_iqi'].create({ - 'iqi_no': sequence_util.generate_sequence('sos_iqi', 'IQI', 'iqi_no'), - 'material_option':True, - 'sfg_option':False, - 'supplier_name': self.supplier_name.id, - 'batch_no':item.batch_no, - 'received_qty':item.qty, - 'uom': item.component_id.uom, - 'material_name': item.component_id.id, - 'material_code': item.component_id.material_code, - 'in_tact':self.boxes_sealing, - 'invoice_no':self.dc_no_char, - 'invoice_date':self.dc_date, - 'ir_id_unique_id':self.id, - 'from_origin':"Vendor", - 'unit_price':item.unit_price - + if item.qty > 0: + iqi_record = self.env['sos_iqi'].create({ + 'iqi_no': sequence_util.generate_sequence('sos_iqi', 'IQI', 'iqi_no'), + 'material_option':True, + 'sfg_option':False, + 'supplier_name': self.supplier_name.id, + 'batch_no':item.batch_no, + 'received_qty':item.qty, + 'uom': item.component_id.uom, + 'material_name': item.component_id.id, + 'material_code': item.component_id.material_code, + 'in_tact':self.boxes_sealing, + 'invoice_no':self.dc_no_char, + 'invoice_date':self.dc_date, + 'ir_id_unique_id':self.id, + 'from_origin':"Vendor", + 'unit_price':item.unit_price + - }) + }) # Email part body_html = f"""

Below IQI is waiting for your Approval

@@ -383,7 +384,7 @@ class SOS_IR(models.Model): """ subject = f"Inward Approval Request - {self.ir_no}" send_email = self.env['sos_common_scripts'] - send_email.send_direct_email(self.env,"sos_ir",self.id,"ramachandran.r@sosaley.in",subject,body_html) + send_email.send_direct_email(self.env,"sos_ir",self.id,"ramachandran.r@sosaley.com",subject,body_html) # Email part ends sequence_util = self.env['sos_common_scripts'] return sequence_util.action_assign_signature( diff --git a/sos_inventory/models/sos_mat_outsourcing_vendor_register.py b/sos_inventory/models/sos_mat_outsourcing_vendor_register.py index c046edd..f01db23 100755 --- a/sos_inventory/models/sos_mat_outsourcing_vendor_register.py +++ b/sos_inventory/models/sos_mat_outsourcing_vendor_register.py @@ -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_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display') ppm = fields.Integer(string="PPM",compute="_compute_ppm") - - quality_marks = fields.Float(string="Quality Marks") + quality_marks = fields.Float(string="Quality Marks",compute="_compute_sfgquality") delivery_marks = fields.Float(string="Delivery Marks") responsiveness_marks = fields.Float(string="Responsiveness Marks") report_marks = fields.Float(string="Report Marks") + def _calculate_quality_marks(self, received, rejected): + if received != 0: + q = 100 - ((rejected / received) * 100) + if 91 <= q <= 100: + return 30 + elif 80 <= q <= 90: + return 25 + else: + return 10 + return 0 + @api.depends('received_qty', 'rejected_qty') + def _compute_sfgquality(self): + for record in self: + record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty) + @api.depends('rejection_percentage') def _compute_rejected_percentage_display(self): for record in self: diff --git a/sos_inventory/models/sos_mon.py b/sos_inventory/models/sos_mon.py index 29e8f49..2fce647 100755 --- a/sos_inventory/models/sos_mon.py +++ b/sos_inventory/models/sos_mon.py @@ -16,6 +16,7 @@ class sos__mon(models.Model): filled_by = fields.Many2one('res.users', string='Filled By', readonly=True,required=True,default=lambda self: self.env.user) logged_inuser_group=fields.Boolean(string='Group Name',compute='compute_user_grp',store=True) dept = fields.Many2one('sos_departments',string="Department") + customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name") purpose = fields.Char(string="Purpose") product_name = fields.Many2one('sos_fg', string='Material/Product Name & No') indent_ref_no = fields.Many2one('sos_fg_plan',string="Indent Reference No") @@ -33,7 +34,7 @@ class sos__mon(models.Model): top_management_name = fields.Many2one('res.users', string='Top Management Approver') top_management_approval_image = fields.Image(related="top_management_name.signature_image",string='Top Management Approval',readonly=True) top_management_approved_on = fields.Datetime(string="Approved On") - + deliverables_boq_id = fields.Many2one('sos_deliverables_boq', string="Load From Deliverables/BOQ Id") stores_approved_by = fields.Many2one('res.users', string='Stores Approved By') stores_approved_image = fields.Image(related="stores_approved_by.signature_image",string='Stores Approval Sign',readonly=True) stores_approved_on = fields.Datetime(string="Approved On") @@ -43,9 +44,9 @@ class sos__mon(models.Model): ('fg', 'FG') ], string='Auto Load Items' ,default=False) material_option = fields.Boolean('Materials', default=True) - sfg_option = fields.Boolean('Semi-Finished Goods') - fg_option = fields.Boolean('Finished Goods') - order_type = fields.Char(string='order_type',copy=True) + sfg_option = fields.Boolean('Semi-Finished Goods', default=False) + fg_option = fields.Boolean('Finished Goods', default=False) + order_type = fields.Char(string='order_type',copy=True,compute="_compute_order_type") line_ids_material = fields.One2many('sos_mon_line_material', 'mon_id', string="Materials",copy=True) line_ids_sfg = fields.One2many('sos_mon_line_sfg', 'mon_id', string="Semi-Finished Goods",copy=True) line_ids_fg = fields.One2many('sos_mon_line_fg', 'mon_id', string="Finished Goods",copy=True) @@ -73,6 +74,74 @@ class sos__mon(models.Model): approx_value = fields.Monetary(compute='_compute_approx_value', string="Approximate Value", currency_field='currency_id', readonly=True,store=True) status = fields.Selection([ ('open', 'Open'),('close', 'Closed')], default='open' , string="Status") active = fields.Boolean(default=True) + is_ce_user_created = fields.Boolean( + compute='_compute_is_ce_user_created', + store=True, + string='Created by CE User' + ) + @api.depends('create_uid') + def _compute_is_ce_user_created(self): + ce_groups = [ + self.env.ref('sos_inventory.sos_ce_user').id + ] + for record in self: + record.is_ce_user_created = any( + gid in record.create_uid.groups_id.ids for gid in ce_groups + ) + @api.depends('material_option', 'sfg_option', 'fg_option') + def _compute_order_type(self): + for rec in self: + parts = [] + if rec.material_option: + parts.append('Material') + if rec.sfg_option: + parts.append('SFG') + if rec.fg_option: + parts.append('FG') + rec.order_type = ",".join(parts) if parts else False + @api.onchange('deliverables_boq_id') + def _onchange_deliverables_boq_id(self): + self.material_option = True + self.sfg_option = True + self.fg_option = True + if self.deliverables_boq_id: + self.line_ids_material = [(5, 0, 0)] # Clear existing records + self.line_ids_material = [ + (0, 0, { + 'component_id': line.component_id, # Replace 'field1' with the actual field names + 'uom': line.uom, + 'material_code': line.material_code, + 'quantity': line.quantity + + # Add other fields to copy here + }) for line in self.deliverables_boq_id.line_ids_installation_kit + ] + self.line_ids_fg = [(5, 0, 0)] # Clear existing records + self.line_ids_fg = [ + (0, 0, { + 'component_id': line.component_id, # Replace 'field1' with the actual field names + 'quantity': line.quantity + }) for line in self.deliverables_boq_id.line_ids_fg + ] + self.line_ids_sfg = [(5, 0, 0)] # Clear existing records + self.line_ids_sfg = [ + (0, 0, { + 'component_id': line.component_id, # Replace 'field1' with the actual field names + 'quantity': line.quantity + # Add other fields to copy here + }) for line in self.deliverables_boq_id.line_ids_sfg + ] + self.line_ids_material = [ + (0, 0, { + 'uom': line.uom, + 'material_code': line.material_code, + 'component_id': line.component_id, + 'quantity': line.quantity + # Add other fields to copy here + }) for line in self.deliverables_boq_id.line_ids_material + ] + + @api.constrains('indent_ref_no', 'auto_load_fg_items') def _check_duplicate_fg_items_per_indent(self): for record in self: @@ -461,7 +530,7 @@ class Mon_Line_Material(models.Model): uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram'), ('Packs', 'Packs')], string="Uom") specifications = fields.Char(string="Specifications") quantity = fields.Float(string="Quantity",required=True,default=1) - location = fields.Char(string="Location") + location = fields.Char(string="Location",related='component_id.location') company_id = fields.Many2one('res.company', store=True, copy=False, string="Company", default=lambda self: self.env.user.company_id.id) diff --git a/sos_inventory/models/sos_mrn.py b/sos_inventory/models/sos_mrn.py index 756d2b2..0a584a1 100755 --- a/sos_inventory/models/sos_mrn.py +++ b/sos_inventory/models/sos_mrn.py @@ -46,6 +46,20 @@ class sos__mrn(models.Model): default=lambda self: self.env.user.company_id.currency_id.id) approx_value = fields.Monetary(compute='_compute_approx_value', string="Approximate Value", currency_field='currency_id', readonly=True) + is_ce_user_created = fields.Boolean( + compute='_compute_is_ce_user_created', + store=True, + string='Created by CE User' + ) + @api.depends('create_uid') + def _compute_is_ce_user_created(self): + ce_groups = [ + self.env.ref('sos_inventory.sos_ce_user').id + ] + for record in self: + record.is_ce_user_created = any( + gid in record.create_uid.groups_id.ids for gid in ce_groups + ) @api.onchange('min_no') def _onchange_min_no(self): if self.min_no: diff --git a/sos_inventory/models/sos_ncmr.py b/sos_inventory/models/sos_ncmr.py index 3f71652..ee892cf 100755 --- a/sos_inventory/models/sos_ncmr.py +++ b/sos_inventory/models/sos_ncmr.py @@ -52,7 +52,8 @@ class NCMR_Model(models.Model): finished_fg_assy = fields.Char() finished_fg_assy_responsibility = fields.Text(string="Production Assy FG Responsibility") description_of_nc = fields.Html(string="Description of Non-Conformities") - root_cause_of_nc = fields.Html(string="Root Cause of Non-Conformities") + root_cause_of_nc = fields.Html(string="Root Cause", + default="

Why #1 :

Why #2 :

Why #3 :

Why #4 :

Why #5 :

") containment_action_of_nc = fields.Html(string="Containment Action to close the Non-Conformities") comments_on_capa = fields.Html(string="Comments on Corrective / Preventive Action ") qa_comments=fields.Text(string="QA Comments") @@ -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_sign = fields.Image(related="rework_rd_approval_by.signature_image",string='Rework - R&D In-Charge',readonly=True) rework_rd_approval_on = fields.Datetime(string="Approved On") + + responsible_department = fields.Many2one('sos_departments', string='Department') + responsible_name = fields.Many2one('res.users',string='Responsible Person',domain="[('id', 'in', allowed_user_ids)]") + allowed_user_ids = fields.Many2many('res.users', compute='_compute_allowed_users') + rca_responsible_department = fields.Many2many('sos_departments', string='Department') + rca_responsible_name = fields.Many2many('res.users',string='Responsible Person',relation='sos_ncmr_rca_responsible_name_rel',domain="[('id', 'in', rca_allowed_user_ids)]") + rca_allowed_user_ids = fields.Many2many('res.users', compute='_rca_compute_allowed_users') + combined_incoming_doc_ref = fields.Reference( selection=[ @@ -120,8 +129,155 @@ class NCMR_Model(models.Model): supplier_name = fields.Many2one('sos_suppliers',string="Supplier Name",compute="_get_supplier_name") service_provider_name = fields.Many2one('sos_service_providers',string="Service Provider Name",compute="_get_service_provider_name") customer_name = fields.Many2one('sos_inventory_customers', string="Customer Name",compute="_get_customer_name") - outsourcing_return_ref_no = fields.Many2one('sos_sfg_outsourcing_return_register',string="Outsourcing Return Ref No") + + problem_description_line_ids = fields.One2many('sos_ncmr_problem_description_line', 'ncmr_id', string="Problem Description Line Ids",copy=True) + + @api.depends('responsible_department') + def _compute_allowed_users(self): + for rec in self: + rec.allowed_user_ids = rec.responsible_department.users_line_ids.mapped('users') + + @api.depends('rca_responsible_department') + def _rca_compute_allowed_users(self): + for rec in self: + rec.rca_allowed_user_ids = rec.rca_responsible_department.users_line_ids.mapped('users') + + @api.model + def get_service_suppliers_by_goods_type(self,goods_type=False,startDate=False, endDate=False): + + if goods_type == 'Material': + + query = """ + SELECT id,supplier_name + FROM sos_suppliers + WHERE id IN ( + SELECT b.supplier_name + FROM sos_ncmr a, sos_iqi b + WHERE a.incoming_doc_ref = b.id + AND a.material_option = 't' + AND a.ncmr_date BETWEEN %s AND %s + ) + """ + elif goods_type == 'SFG': + + query = """ + SELECT id,service_provider_name + FROM sos_service_providers + WHERE id IN ( + SELECT b.service_provider_name + FROM sos_ncmr a, sos_iqi b + WHERE a.incoming_doc_ref = b.id + AND a.sfg_option = 't' + AND a.ncmr_date BETWEEN %s AND %s + ) + """ + + self.env.cr.execute(query,(startDate, endDate)) + rows = self.env.cr.fetchall() + result = [{'id': '', 'supplier_name': 'Select Supplier/Service'}] + result += [{'id': row[0], 'supplier_name': row[1]} for row in rows] + + return result + + + @api.model + def get_pareto_data(self, start_date=False, end_date=False, goodstype=False, categoryId=False, servicesupplier=False): + + if not start_date or not end_date: + raise ValueError("Start date and end date must be provided.") + + base_where_clause = "a.ncmr_date BETWEEN %s AND %s" + where_params = [start_date, end_date] + base_groupby_clause = "d.name" + + if goodstype == 'Material': + join_clause = """ + JOIN sos_material_defective_line_sos_ncmr_line_rel c ON b.id = c.sos_ncmr_line_id + JOIN sos_material_defective_line d ON c.sos_material_defective_line_id = d.id + JOIN sos_material_configuration e ON a.material_category = e.id + """ + if categoryId and categoryId != 'Select Category': + join_clause += " JOIN sos_material f ON a.material_name = f.id JOIN sos_material_types g ON f.material_type_id = g.id" + base_where_clause += " AND g.name = %s" + #base_groupby_clause += ",g.name" + where_params.append(categoryId) + + if servicesupplier and servicesupplier !='Select Supplier/Service' and servicesupplier !='undefined': + join_clause += " LEFT JOIN sos_iqi h ON a.incoming_doc_ref = h.id LEFT JOIN sos_suppliers i ON h.supplier_name = i.id" + base_where_clause += " AND h.supplier_name = %s" + base_groupby_clause += ",h.supplier_name" + where_params.append(servicesupplier) + + elif goodstype == 'SFG': + join_clause = """ + JOIN sos_ncmr_line_sos_sfg_defective_line_rel c ON b.id = c.sos_ncmr_line_id + JOIN sos_sfg_defective_line d ON c.sos_sfg_defective_line_id = d.id + JOIN sos_sfg_configuration e ON a.sfg_category = e.id + """ + if categoryId and categoryId != 'Select Category': + join_clause += " JOIN sos_sfg f ON a.sfg_name = f.id" + base_where_clause += " AND f.category = %s" + base_groupby_clause += ",f.category" + where_params.append(categoryId) + + if servicesupplier and servicesupplier !='Select Supplier/Service' and servicesupplier !='undefined': + join_clause += " LEFT JOIN sos_iqi g ON a.incoming_doc_ref = g.id LEFT JOIN sos_service_providers h ON g.service_provider_name = h.id" + base_where_clause += " AND g.service_provider_name = %s" + base_groupby_clause += ",g.service_provider_name" + where_params.append(servicesupplier) + + elif goodstype == 'FG': + join_clause = """ + JOIN sos_defectives_sos_ncmr_line_rel c ON b.id = c.sos_ncmr_line_id + JOIN sos_defectives d ON c.sos_defectives_id = d.id + JOIN sos_testing_parameters f ON a.fg_category = f.id + JOIN sos_fg e ON e.id = f.fg_name + """ + + if categoryId and categoryId != 'Select Category': + base_where_clause += " AND e.fg_type = %s" + base_groupby_clause += ",e.fg_type" + where_params.append(categoryId) + + # Build the full SQL query dynamically + + query = f""" + SELECT + defect_name, + count, + ROUND(count * 100.0 / total, 2) AS individual_percent, + ROUND(SUM(count) OVER (ORDER BY count DESC ROWS UNBOUNDED PRECEDING) * 100.0 / total, 2) AS cumulative_percent + FROM ( + SELECT + d.name AS defect_name, + COUNT(*) AS count, + SUM(COUNT(*)) OVER () AS total + FROM + sos_ncmr a + JOIN sos_ncmr_line b ON a.id = b.ncmr_id + {join_clause} + WHERE + {base_where_clause} + GROUP BY + {base_groupby_clause} + ) AS sub + ORDER BY + count DESC; + """ + + self.env.cr.execute(query, tuple(where_params)) + result = self.env.cr.fetchall() + + data = [] + for row in result: + data.append({ + 'defect_name': row[0], # defect_name + 'count': row[1], # count + 'cumulative_percent': row[3], # cumulative_percent + }) + + return data #Excel Write def action_ncmr_report_orm_btn(self, from_date, to_date,force_download=True): output = io.BytesIO() @@ -559,17 +715,18 @@ class NCMR_Model(models.Model): 'sos_inventory.sos_qa_user' ) else: - if all_closed or record.aodr_no: self.status = 'closed' if self.rework_action == "inhouse": + s_no_list = self.line_ids.mapped('s_no') # Collect all s_no values + s_no_str = ', '.join(map(str, s_no_list)) iqi_record = self.env['sos_iqi'].create({ 'iqi_no': send_email.generate_sequence('sos_iqi', 'R-IQI', 'iqi_no'), 'material_option':self.material_option, 'sfg_option':self.sfg_option, 'received_qty': self.rejected_qty, 'iqi_type':'rework', - 'material_name': self.material_name, + 'material_name': self.material_name.id, 'material_code': self.material_name.material_code, 'sfg_name': self.sfg_name.id, 'sfg_code': self.sfg_name.sfg_code, @@ -577,7 +734,9 @@ class NCMR_Model(models.Model): 'service_provider_name':self.incoming_doc_ref.service_provider_name.id, 'old_iqi_ref': self.incoming_doc_ref.id, 'ir_id_unique_id':self.incoming_doc_ref.ir_id_unique_id.id, - 'in_tact':self.incoming_doc_ref.in_tact + 'in_tact':self.incoming_doc_ref.in_tact, + 'ncmr_ref':self.id, + 'serial_no':s_no_str # 'test_report':self.incoming_doc_ref.test_report, # 'test_report_no':self.incoming_doc_ref.test_report_no, # 'test_report_doc':self.incoming_doc_ref.test_report_doc, @@ -585,7 +744,7 @@ class NCMR_Model(models.Model): }) - + print(iqi_record) # Rework Iteration starts sos_record = self.env['sos_iqi'].search([('id', '=', self.incoming_doc_ref.id)], limit=1) if sos_record: @@ -622,7 +781,27 @@ class NCMR_Model(models.Model): 'sos_inventory.sos_scg_group_user' ) if self.rework_action == "inhouse": - # Email part + if self.material_option: + orr_record = self.env['sos_sfg_outsourcing_return_register'].create({ + 'orr_no': orr_no, + 'iqi_no':self.incoming_doc_ref.id, + 'iqi_date':self.incoming_doc_ref.iqi_date, + 'material_name':self.incoming_doc_ref.material_name, + 'goods_type':'Materials', + 'returned_qty':self.incoming_doc_ref.rejected_qty, + 'rework_type':'inhouse' + }) + else: + orr_record = self.env['sos_sfg_outsourcing_return_register'].create({ + 'orr_no': orr_no, + 'iqi_no':self.incoming_doc_ref.id, + 'iqi_date':self.incoming_doc_ref.iqi_date, + 'sfg_name':self.incoming_doc_ref.sfg_name, + 'goods_type':'SFG', + 'returned_qty':self.incoming_doc_ref.rejected_qty, + 'rework_type':'inhouse' + }) + self.outsourcing_return_ref_no = orr_record.id body_html = f"""

Below NCMR is waiting for your Action

""" @@ -636,7 +815,8 @@ class NCMR_Model(models.Model): 'material_name':self.incoming_doc_ref.material_name, 'supplier_name':self.incoming_doc_ref.supplier_name.id, 'goods_type':'Materials', - 'returned_qty':self.incoming_doc_ref.rejected_qty + 'returned_qty':self.incoming_doc_ref.rejected_qty, + 'rework_type':'outsourcing' }) else: if self.material_option: @@ -647,7 +827,8 @@ class NCMR_Model(models.Model): 'material_name':self.incoming_doc_ref.material_name, 'supplier_name':self.incoming_doc_ref.supplier_name.id, 'goods_type':'Materials', - 'returned_qty':self.incoming_doc_ref.rejected_qty + 'returned_qty':self.incoming_doc_ref.rejected_qty, + 'rework_type':'outsourcing' }) else: orr_record = self.env['sos_sfg_outsourcing_return_register'].create({ @@ -657,7 +838,8 @@ class NCMR_Model(models.Model): 'service_provider_name':self.incoming_doc_ref.service_provider_name, 'sfg_name':self.incoming_doc_ref.sfg_name, 'goods_type':'SFG', - 'returned_qty':self.incoming_doc_ref.rejected_qty + 'returned_qty':self.incoming_doc_ref.rejected_qty, + 'rework_type':'outsourcing' }) self.outsourcing_return_ref_no = orr_record.id @@ -685,6 +867,26 @@ class NCMR_Model(models.Model): ) def action_qa_esign_btn(self): + if self.responsible_department: + if self.material_option: + material = self.material_name.part_no + elif self.sfg_option: + material = self.sfg_name.name + else: + material = self.fg_name.name + result_col = f"NCMR Ref No :{getattr(self, 'ncmr_no', '')}" + + new_action_record = self.env['sos_brm_action'].create({ + 'cross_dept_action': 'cross_dept', + 'department': 3, + 'responsible_person': getattr(self.responsible_name, 'id', False), + 'assigned_by': getattr(self.qa_by, 'id', False), + 'assigned_from_dept': 3, + 'assigned_to_dept': getattr(self.responsible_department, 'id', False), + 'name': f"NCMR Assigned : {material}", + 'result': result_col + }) + if self.action_group: sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( @@ -767,8 +969,9 @@ class NCMR_Model_CAPA_Line(models.Model): ncmr_id = fields.Many2one('sos_ncmr', string="NCMR Reference", ondelete="cascade") issue = fields.Char(string="Issue") - corrective_action = fields.Html(string="Corrective Action") - preventive_action = fields.Html(string="Preventive Action") + corrective_action = fields.Html(string="D5: Permanent Corrective Action") + implement_validate_corrective_action = fields.Html(string="D6:Implement & Validate Corrective Actions") + preventive_action = fields.Html(string="D7:Preventive Recurrence") class NCMR_Model_Line(models.Model): _name = 'sos_ncmr_line' @@ -897,3 +1100,18 @@ class NCMR_Model_Line(models.Model): def _compute_fg_defective_count(self): for record in self: record.fg_defective_count = len(record.fg_defectives) + +class NCMR_Model_PROBLEM_DESCRIPTION_Line(models.Model): + _name = 'sos_ncmr_problem_description_line' + _description = 'Problem Description Lines' + + ncmr_id = fields.Many2one('sos_ncmr', string="NCMR Reference", ondelete="cascade") + clear_statement = fields.Html(string="Clear Statement") + photos = fields.Html(string="Photos") + what = fields.Text(string="What") + where = fields.Text(string="Where") + when = fields.Text(string="When") + why = fields.Text(string="Why") + who = fields.Text(string="Who") + how = fields.Text(string="How") + how_many = fields.Text(string="How many") \ No newline at end of file diff --git a/sos_inventory/models/sos_order_delivery_plan.py b/sos_inventory/models/sos_order_delivery_plan.py index 1bcf6ec..17be091 100755 --- a/sos_inventory/models/sos_order_delivery_plan.py +++ b/sos_inventory/models/sos_order_delivery_plan.py @@ -182,7 +182,7 @@ class SOS_Order_Delivery_Plan(models.Model):

Below Order Delivery Plan is waiting for your Approval

""" send_email = self.env['sos_common_scripts'] - send_email.send_direct_email(self.env,"sos_order_delivery_plan",self.id,"ramachandran.r@sosaley.in","Order Delivery Plan",body_html) + send_email.send_direct_email(self.env,"sos_order_delivery_plan",self.id,"ramachandran.r@sosaley.com","Order Delivery Plan",body_html) # Email part ends sequence_util = self.env['sos_common_scripts'] return sequence_util.action_assign_signature( diff --git a/sos_inventory/models/sos_outsourcing_vendor_monitoring_register.py b/sos_inventory/models/sos_outsourcing_vendor_monitoring_register.py index d06a4b8..e5e1c94 100755 --- a/sos_inventory/models/sos_outsourcing_vendor_monitoring_register.py +++ b/sos_inventory/models/sos_outsourcing_vendor_monitoring_register.py @@ -36,12 +36,25 @@ class SOS_Oursourcing_Monitor_Lines(models.Model): rejection_percentage = fields.Float(string="Rejected Percentage", compute="_compute_rejected_percentage", store=True) rejection_percentage_display = fields.Char('Rejection Percentage', compute='_compute_rejected_percentage_display') ppm = fields.Integer(string="PPM",compute="_compute_ppm") - - quality_marks = fields.Float(string="Quality Marks") + quality_marks = fields.Float(string="Quality Marks",compute="_compute_sfgquality") delivery_marks = fields.Float(string="Delivery Marks") responsiveness_marks = fields.Float(string="Responsiveness Marks") report_marks = fields.Float(string="Report Marks") + def _calculate_quality_marks(self, received, rejected): + if received != 0: + q = 100 - ((rejected / received) * 100) + if 91 <= q <= 100: + return 30 + elif 80 <= q <= 90: + return 25 + else: + return 10 + return 0 + @api.depends('received_qty', 'rejected_qty') + def _compute_sfgquality(self): + for record in self: + record.quality_marks = self._calculate_quality_marks(record.received_qty, record.rejected_qty) @api.depends('rejection_percentage') def _compute_rejected_percentage_display(self): for record in self: diff --git a/sos_inventory/models/sos_po.py b/sos_inventory/models/sos_po.py index d4b3849..3c21e00 100755 --- a/sos_inventory/models/sos_po.py +++ b/sos_inventory/models/sos_po.py @@ -143,7 +143,7 @@ class sos__po(models.Model): """ subject = f"Purchase Order Approval Request - {self.po_no}" send_email = self.env['sos_common_scripts'] - send_email.send_direct_email(self.env,"sos_po",self.id,"ramachandran.r@sosaley.in",subject,body_html) + send_email.send_direct_email(self.env,"sos_po",self.id,"ramachandran.r@sosaley.com",subject,body_html) # Email part ends sequence_util = self.env['sos_common_scripts'] return sequence_util.action_assign_signature( diff --git a/sos_inventory/models/sos_prf.py b/sos_inventory/models/sos_prf.py new file mode 100755 index 0000000..2e05e1d --- /dev/null +++ b/sos_inventory/models/sos_prf.py @@ -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""" +

Below PRF got approved

+ """ + 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""" +

Below PRF is waiting for your Approval

+ """ + 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""" +

Below PRF is waiting for your Approval

+ """ + 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") diff --git a/sos_inventory/models/sos_quote_generation.py b/sos_inventory/models/sos_quote_generation.py index 66825a5..d01b68b 100755 --- a/sos_inventory/models/sos_quote_generation.py +++ b/sos_inventory/models/sos_quote_generation.py @@ -68,35 +68,35 @@ class SOS_Quote_Generation(models.Model): supplier_dict = {} for record in self.line_ids: - if record.supplier1_name: - supplier = record.supplier1_name + if record.final_supplier1_name: + supplier = record.final_supplier1_name if supplier not in supplier_dict: supplier_dict[supplier] = [] supplier_dict[supplier].append({ 'name': record.material_name, - 'product_qty': record.supplier1_qty, - 'price_unit': record.supplier1_quoted_price + 'product_qty': record.final_supplier1_qty, + 'price_unit': record.final_supplier1_quoted_price }) - if record.supplier2_name: - supplier = record.supplier2_name - if supplier not in supplier_dict: - supplier_dict[supplier] = [] - supplier_dict[supplier].append({ - 'name': record.material_name, - 'product_qty': record.supplier2_qty, - 'price_unit': record.supplier2_quoted_price - }) + # if record.supplier2_name: + # supplier = record.supplier2_name + # if supplier not in supplier_dict: + # supplier_dict[supplier] = [] + # supplier_dict[supplier].append({ + # 'name': record.material_name, + # 'product_qty': record.supplier2_qty, + # 'price_unit': record.supplier2_quoted_price + # }) - if record.supplier3_name: - supplier = record.supplier3_name - if supplier not in supplier_dict: - supplier_dict[supplier] = [] - supplier_dict[supplier].append({ - 'name': record.material_name, - 'product_qty': record.supplier3_qty, - 'price_unit': record.supplier3_quoted_price - }) + # if record.supplier3_name: + # supplier = record.supplier3_name + # if supplier not in supplier_dict: + # supplier_dict[supplier] = [] + # supplier_dict[supplier].append({ + # 'name': record.material_name, + # 'product_qty': record.supplier3_qty, + # 'price_unit': record.supplier3_quoted_price + # }) for x, y in supplier_dict.items(): sequence_util = self.env['sos_common_scripts'] po_no = sequence_util.generate_sequence('sos_po','PO', 'po_no') @@ -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'] }) message = 'Purchase Order(s) successfully generated.' + return { 'type': 'ir.actions.client', diff --git a/sos_inventory/models/sos_sales_order.py b/sos_inventory/models/sos_sales_order.py index 3ee5d5b..6215783 100755 --- a/sos_inventory/models/sos_sales_order.py +++ b/sos_inventory/models/sos_sales_order.py @@ -26,6 +26,7 @@ class SOS_SalesOrder(models.Model): string="Product Name",required=True) line_ids = fields.One2many('sos_sales_order_line', 'ref_id',copy=True) customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name") + customer_location = fields.Char(string="Customer Location") lead_time = fields.Datetime(string="Lead Time") customer_po_no = fields.Char(string="PO No") customer_po_date = fields.Datetime(string="PO Date") diff --git a/sos_inventory/models/sos_sequence_generator.py b/sos_inventory/models/sos_sequence_generator.py index 4ec2559..f517455 100755 --- a/sos_inventory/models/sos_sequence_generator.py +++ b/sos_inventory/models/sos_sequence_generator.py @@ -106,7 +106,7 @@ class Sequence_Generator(models.AbstractModel): reporting_user = env['res.users'].search([('id', '=', users.reporting_to.id)]) # email_to = reporting_user.login else: - # email_to = "ramachandran.r@sosaley.in" + # email_to = "ramachandran.r@sosaley.com" print("test") mail_values = { 'subject': subject, diff --git a/sos_inventory/models/sos_service_call_log_report.py b/sos_inventory/models/sos_service_call_log_report.py index 30c4d51..0857698 100755 --- a/sos_inventory/models/sos_service_call_log_report.py +++ b/sos_inventory/models/sos_service_call_log_report.py @@ -23,7 +23,17 @@ class SOS_Service_Call_Log_Report(models.Model): action_taken = fields.Text(string="Action taken or required action to be taken") Date_of_action_Completed = fields.Datetime(string="Date of action completed") status = fields.Selection([('open', 'Open'),('close', 'Closed')], default='open' , string="Status") - + dock_audit_ref_no = fields.Many2one('sos_dock_audit',string="Dock Audit Ref No") + warranty = fields.Integer(string="Warranty(In Months)") + invoice_no = fields.Char(string="Invoice No") + invoice_date = fields.Date(string="Invoice Date") + + @api.onchange('dock_audit_ref_no') + def _onchange_dock_audit_ref_no(self): + if self.dock_audit_ref_no: + self.warranty = self.dock_audit_ref_no.warranty + self.invoice_no = self.dock_audit_ref_no.invoice_no + self.invoice_date = self.dock_audit_ref_no.invoice_date def _generate_id(self): sequence_util = self.env['sos_common_scripts'] return sequence_util.generate_sequence('sos_service_call_log_report','SCLR', 'ref_no') \ No newline at end of file diff --git a/sos_inventory/models/sos_sfg.py b/sos_inventory/models/sos_sfg.py index 88b25b8..cf54fd8 100755 --- a/sos_inventory/models/sos_sfg.py +++ b/sos_inventory/models/sos_sfg.py @@ -111,6 +111,6 @@ class SOS_SFG_Line(models.Model): ir_no = fields.Many2one('sos_ir', string="Material Inward Ref No") min_no = fields.Many2one('sos_min', string="Material Issue Ref No") mrn_no = fields.Many2one('sos_mrn', string="Material Return Ref No") - iqi_no = fields.Many2one('sos_iqi', string="In-house Inspection Ref No") + iqi_no = fields.Many2one('sos_iqi', string="IQI Ref No") diff --git a/sos_inventory/models/sos_sfg_outsourcing_return_register.py b/sos_inventory/models/sos_sfg_outsourcing_return_register.py index 504fd7b..11ce688 100755 --- a/sos_inventory/models/sos_sfg_outsourcing_return_register.py +++ b/sos_inventory/models/sos_sfg_outsourcing_return_register.py @@ -22,6 +22,10 @@ class sos_Outsourcing_Return(models.Model): material_name = fields.Many2one('sos_material',string="Material Name",related="iqi_no.material_name") returned_qty = fields.Integer(string="Returned Quantity",related="iqi_no.rejected_qty") remarks = fields.Text(string="Remarks") + rework_type = fields.Selection([ + ('outsourcing', 'Out-Sourcing'), + ('inhouse', 'In-House') + ], string='Rework Type',default="outsourcing",copy=True) def _generate_id(self): sequence_util = self.env['sos_common_scripts'] return sequence_util.generate_sequence('sos_sfg_outsourcing_return_register','ORR', 'orr_no') diff --git a/sos_inventory/models/sos_wo.py b/sos_inventory/models/sos_wo.py index b3f4610..0fabd85 100755 --- a/sos_inventory/models/sos_wo.py +++ b/sos_inventory/models/sos_wo.py @@ -138,7 +138,7 @@ class sos__wo(models.Model): """ subject = f"Work Order Approval Request - {self.wo_no}" send_email = self.env['sos_common_scripts'] - send_email.send_direct_email(self.env,"sos_wo",self.id,"ramachandran.r@sosaley.in",subject,body_html) + send_email.send_direct_email(self.env,"sos_wo",self.id,"ramachandran.r@sosaley.com",subject,body_html) # Email part ends sequence_util = self.env['sos_common_scripts'] return sequence_util.action_assign_signature( diff --git a/sos_inventory/report/Unconfirmed 666526.crdownload b/sos_inventory/report/Unconfirmed 666526.crdownload new file mode 100755 index 0000000..e69de29 diff --git a/sos_inventory/report/sos_boq_labels.xml b/sos_inventory/report/sos_boq_labels.xml index c7a8ea7..6de0995 100755 --- a/sos_inventory/report/sos_boq_labels.xml +++ b/sos_inventory/report/sos_boq_labels.xml @@ -9,269 +9,296 @@ report -