# -*- coding: utf-8 -*- from odoo import models, fields, api from odoo.exceptions import ValidationError class sos__grn(models.Model): _name = 'sos_grn' _description = 'Goods Receipt Note' _rec_name = 'grn_no' _order = 'id desc' supplier_invoice_no = fields.Char(string="Supplier Invoice/DC No") iqi_no = fields.Many2one('sos_iqi', string='IQI No') ir_no = fields.Many2one('sos_ir', string='Inward Ref No') grn_no = fields.Char(string="GRN No", required= True, readonly= True, default=lambda self: self._compute_sequence()) grn_date = fields.Datetime(string="GRN Date") supplier_name = fields.Many2one('sos_suppliers',string="Supplier Name") service_provider_name = fields.Many2one('sos_service_providers', string="Service Provider Name") company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company.id, index=True) no_of_parcel=fields.Integer(string="No. of Bags / Boxes/ Barrels/Rolls Received") no_of_parcel_per_invoice=fields.Integer(string="No. of Bags/ Boxes/ Barrels/Rolls as per Invoice") identification_label = fields.Selection([ ('Yes', 'Yes'),('No', 'No'),('NA', 'NA')],'Identification Label affixed post inspection', default='Yes') po_no = fields.Many2one('sos_po', string='Our PO No') wo_no = fields.Many2one('sos_wo', string='Our WO No') approved_selector = fields.Selection([ ('Yes', 'Yes'),('No', 'No')],'Material Received from Approved Supplier', default='Yes') test_report_no = fields.Char(string="Material Final Inspection Test Report No. & Date") reworked_process = fields.Char(string="Reworked Process (If any)") approved_storage_location = fields.Char(string="Approved Material Stored in",default="Stores") rejected_storage_location = fields.Char(string="Rejected Material Stored in",default="Stores") inspection_remarks = fields.Selection([ ('Approved', 'Approved'),('Re work', 'Re work'),('Rejected', 'Rejected')],'Inspection Remarks: Approved / Re work / Rejected', default='Approved') stores_approval_name = fields.Many2one('res.users', string='Approved By') stores_approval_image = fields.Image(related="stores_approval_name.signature_image",string='Stores-RM/PM In Charge',readonly=True) stores_approval_on = fields.Date(string="Stores Received On") line_ids = fields.One2many('sos_grn_line', 'grn_id', string="Components") line_ids_sfg = fields.One2many('sos_grn_line_sfg', 'grn_id', string="SFG") line_ids_fg = fields.One2many('sos_grn_line_fg', 'grn_id', string="FG") received_goods_type = fields.Selection([ ('Materials', 'Materials'), ('SFG', 'SFG'), ('FG', 'FG') ], string='Received Goods Type',default="Materials",copy=True) responsiveness = fields.Selection([ ('10', '10'), ('5', '5') ], string='Responsiveness',copy=True) reports = fields.Selection([ ('30', '30'), ('25', '25'), ('10', '10') ], string='Report',copy=True) delivery = fields.Selection([ ('30', '30'), ('25', '25'), ('10', '10') ], string='Delivery',copy=True) _sql_constraints = [ ('grn_no_unique', 'UNIQUE(grn_no)', 'The GRN No must be unique!') ] @api.model def create(self, vals): record = super(sos__grn, self).create(vals) if not self.env.context.get('from_script', False): record.action_report_esign_btn() return record def action_report_grn_btn(self): try: action = self.env.ref("sos_inventory.action_report_grn").report_action(self) return action except ValueError as e: 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, 'stores_approval_name', '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) if component: current_qty = getattr(component, 'inhand_stock_qty', 0) current_value = getattr(component, 'unit_price', 0) current_intransit_qty = getattr(component, 'in_transit_stock_qty', 0) if item.approved_qty < current_intransit_qty: new_intransit_qty = current_intransit_qty - item.approved_qty else: new_intransit_qty = 0 new_qty = current_qty + item.approved_qty new_unit_price = ((current_qty * current_value) + (item.approved_qty * item.unit_price))/new_qty component.write({'inhand_stock_qty': new_qty, 'in_transit_stock_qty': new_intransit_qty, 'unit_price': new_unit_price}) if item.approved_qty > 0: component.line_ids_in.create({'ref_id': component.id,'component_id': item.component_id.id, 'quantity' : item.approved_qty,'unit_price':item.unit_price,'ir_no':self.ir_no.id, 'action' : 'in'}) if item.rejected_qty > 0: if self.po_no: self.po_no.po_status = 'open' elif self.received_goods_type == "SFG": for item in self.line_ids_sfg: if self.ir_no.indent_no: procurement_record = self.env['sos_sfg_quote_generation'].search([('plan_ref_no', '=', self.ir_no.indent_no.plan_ref_no)], limit=1) for every in procurement_record.line_ids: if str(every.material_name.name).strip().lower() == str(item.component_id.name).strip().lower(): if (every.inprogress_qty - item.approved_qty) < 0: inprogress = 0 else: inprogress = every.inprogress_qty - item.approved_qty every.write({ 'inprogress_qty':inprogress }) component = self.env['sos_sfg'].browse(item.component_id.id) current_intransit_qty = getattr(component, 'in_transit_stock_qty', 0) if item.approved_qty < current_intransit_qty: new_intransit_qty = current_intransit_qty - item.approved_qty else: new_intransit_qty = 0 if component: current_value = getattr(component, 'inhand_stock_qty', 0) component.write({'inhand_stock_qty': current_value + item.approved_qty ,'in_transit_stock_qty': new_intransit_qty,}) if item.approved_qty > 0: component.line_ids_in.create({'ref_id': component.id,'component_id': item.component_id.id, 'quantity' : item.approved_qty,'ir_no':self.ir_no.id, 'action' : 'in'}) if item.rejected_qty > 0: if self.wo_no: self.wo_no.wo_status = 'open' else: for item in self.line_ids_fg: component = self.env['sos_fg'].browse(item.component_id.id) if component: current_value = getattr(component, 'inhand_stock_qty', 0) component.write({'inhand_stock_qty': current_value + item.approved_qty}) 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' grn_id = fields.Many2one('sos_grn', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) ordered_qty = fields.Integer(string="Ordered Quantity") received_qty = fields.Integer(string="Actual Received Quantity") invoice_qty = fields.Integer(string="Quantity as Per Invoice") approved_qty = fields.Integer(string="Approved Quantity") currency_id = fields.Many2one('res.currency', string='Currency') unit_price = fields.Monetary(string="Unit Price", currency_field='currency_id') rejected_qty = fields.Integer(string="Rejected Quantity") reworked_qty = fields.Integer(string="Reworked Quantity (If any)") quality_marks = fields.Integer(string="Quality",compute="_compute_quality") @api.depends('received_qty', 'rejected_qty') def _compute_quality(self): for record in self: 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: return 10 return 0 @api.model def write(self, vals): for record in self: if 'approved_qty' in vals: component_id = vals.get('component_id', record.component_id.id) approved_qty = vals.get('approved_qty', record.approved_qty) unit_price = vals.get('unit_price', record.unit_price) component = self.env['sos_material'].browse(component_id) if component: current_qty = component.inhand_stock_qty or 0 current_value = component.unit_price or 0 current_intransit_qty = getattr(component, 'in_transit_stock_qty', 0) if approved_qty < current_intransit_qty: new_intransit_qty = current_intransit_qty - approved_qty else: new_intransit_qty = 0 new_qty = current_qty + approved_qty if new_qty > 0: new_unit_price = ((current_qty * current_value) + (approved_qty * unit_price)) / new_qty else: new_unit_price = unit_price component.write({ 'inhand_stock_qty': new_qty, 'unit_price': new_unit_price, 'in_transit_stock_qty': new_intransit_qty }) if approved_qty > 0: self.env['sos_material_transaction_history'].create({ 'ref_id': component.id, 'component_id': component_id, 'quantity': approved_qty, 'unit_price': unit_price, 'action': 'in', }) # Call the super method to apply the actual write return super(sos_grn_line, self).write(vals) @api.model def create(self, vals): if not self.env.context.get('from_script', False): component_id = vals.get('component_id') approved_qty = vals.get('approved_qty', 0) unit_price = vals.get('unit_price', 0) if component_id: component = self.env['sos_material'].browse(component_id) if component: current_qty = getattr(component, 'inhand_stock_qty', 0) current_value = getattr(component, 'unit_price', 0) current_intransit_qty = getattr(component, 'in_transit_stock_qty', 0) if approved_qty < current_intransit_qty: new_intransit_qty = current_intransit_qty - approved_qty else: new_intransit_qty = 0 new_qty = current_qty + approved_qty if new_qty > 0: new_unit_price = ((current_qty * current_value) + (approved_qty * unit_price)) / new_qty else: new_unit_price = unit_price component.write({ 'inhand_stock_qty': new_qty, 'unit_price': new_unit_price, 'in_transit_stock_qty': new_intransit_qty }) if approved_qty > 0: self.env['sos_material_transaction_history'].create({ 'ref_id': component.id, 'component_id': component_id, 'quantity': approved_qty, 'unit_price': unit_price, 'action': 'in', }) record = super(sos_grn_line, self).create(vals) return record class sos_grn_line_sfg(models.Model): _name = 'sos_grn_line_sfg' _description = 'GRN SFG Lines' grn_id = fields.Many2one('sos_grn', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_sfg', string="Material Name", required=True) ordered_qty = fields.Integer(string="Ordered Quantity") received_qty = fields.Integer(string="Actual Received Quantity") invoice_qty = fields.Integer(string="Quantity as Per Invoice") approved_qty = fields.Integer(string="Approved Quantity") rejected_qty = fields.Integer(string="Rejected Quantity") reworked_qty = fields.Integer(string="Reworked Quantity (If any)") quality_marks = fields.Integer(string="Quality",compute="_compute_sfgquality") responsiveness = fields.Selection([ ('10', '10'), ('5', '5') ], string='Responsiveness',copy=True,default="10") reports = fields.Selection([ ('30', '30'), ('25', '25'), ('10', '10') ], string='Report',copy=True,default="30") delivery = fields.Selection([ ('30', '30'), ('25', '25'), ('10', '10') ], string='Delivery',copy=True,default="30") @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) 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 class sos_grn_line_fg(models.Model): _name = 'sos_grn_line_fg' _description = 'GRN FG Lines' grn_id = fields.Many2one('sos_grn', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_fg', string="Material Name", required=True) ordered_qty = fields.Integer(string="Ordered Quantity") received_qty = fields.Integer(string="Actual Received Quantity") invoice_qty = fields.Integer(string="Quantity as Per Invoice") approved_qty = fields.Integer(string="Approved Quantity") rejected_qty = fields.Integer(string="Rejected Quantity") reworked_qty = fields.Integer(string="Reworked Quantity (If any)")