diff --git a/sos_inside_sales/models/__pycache__/sos_inside_sales_leads.cpython-310.pyc b/sos_inside_sales/models/__pycache__/sos_inside_sales_leads.cpython-310.pyc index b37697b..900f941 100644 Binary files a/sos_inside_sales/models/__pycache__/sos_inside_sales_leads.cpython-310.pyc and b/sos_inside_sales/models/__pycache__/sos_inside_sales_leads.cpython-310.pyc differ diff --git a/sos_inside_sales/views/sos_inside_sales_leads_view.xml b/sos_inside_sales/views/sos_inside_sales_leads_view.xml index 0e8622e..19d706a 100755 --- a/sos_inside_sales/views/sos_inside_sales_leads_view.xml +++ b/sos_inside_sales/views/sos_inside_sales_leads_view.xml @@ -11,7 +11,7 @@ - + diff --git a/sos_inventory/__manifest__.py b/sos_inventory/__manifest__.py index 90f48b2..5408d67 100755 --- a/sos_inventory/__manifest__.py +++ b/sos_inventory/__manifest__.py @@ -114,6 +114,7 @@ 'report/mme_history_card_report.xml', 'report/sos_boq_labels.xml', 'report/shelflife_report.xml', + 'report/sos_boq_report.xml', 'data/send_indent_plan_email_template.xml', 'data/selection_item.xml' diff --git a/sos_inventory/models/__pycache__/sos_deliverables_boq.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_deliverables_boq.cpython-310.pyc index 36ffa66..bb729eb 100644 Binary files a/sos_inventory/models/__pycache__/sos_deliverables_boq.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_deliverables_boq.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 0b3a6b5..5f36228 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_fir.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_fir.cpython-310.pyc index 26b6fed..427b994 100644 Binary files a/sos_inventory/models/__pycache__/sos_fir.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_fir.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_inventory_customers.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_inventory_customers.cpython-310.pyc index 2d548e4..7fc5528 100644 Binary files a/sos_inventory/models/__pycache__/sos_inventory_customers.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_inventory_customers.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 1310398..1cb55d4 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_sales_order.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_sales_order.cpython-310.pyc index b4a5ee0..b5bd074 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/sos_deliverables_boq.py b/sos_inventory/models/sos_deliverables_boq.py index 3bcdea3..b5158ef 100755 --- a/sos_inventory/models/sos_deliverables_boq.py +++ b/sos_inventory/models/sos_deliverables_boq.py @@ -65,6 +65,19 @@ class sos_deliverables_boq(models.Model): verified_by = fields.Many2one('res.users', string='Prepared by') verified_by_image = fields.Image(related="verified_by.signature_image",string='Prepared by Sign',readonly=True) verified_on = fields.Datetime(string="Prepared On") + ce_verified_by = fields.Many2one('res.users', string='CE Verified by') + ce_verified_by_image = fields.Image(related="ce_verified_by.signature_image",string='CE Verified by Sign',readonly=True) + ce_verified_on = fields.Datetime(string="CE Head Verified On") + scg_head_verified_by = fields.Many2one('res.users', string='SCG Verified by') + scg_head_verified_by_image = fields.Image(related="scg_head_verified_by.signature_image",string='SCG Verified by Sign',readonly=True) + scg_head_verified_on = fields.Datetime(string="SCG Head Verified On") + batter_or_cells=fields.Integer(string="Battery/Cells") + def action_report_boq(self): + try: + action = self.env.ref("sos_inventory.action_sos_deliverables_boq").report_action(self) + return action + except ValueError as e: + print(f"Failed to find report action: {e}") def action_print_labels_btn(self): try: action = self.env.ref("sos_inventory.action_report_labels").report_action(self) @@ -127,11 +140,44 @@ class sos_deliverables_boq(models.Model): ) def action_prepared_esign_btn(self): sequence_util = self.env['sos_common_scripts'] + # Email part + body_html = f""" +

Below Deliverables/BOQ is waiting for your Approval

+ """ + sequence_util.send_group_email(self.env,'sos_deliverables_boq',self.id,"deenalaura.m@sosaley.in","Deliverables/BOQ Approval Request",body_html,'sos_inventory.sos_ce_head') + # Email part ends return sequence_util.action_assign_signature( self, 'prepared_by', 'prepared_on' ) + def action_ce_verified_esign_btn(self): + sequence_util = self.env['sos_common_scripts'] + # Email part + body_html = f""" +

Below Deliverables/BOQ is waiting for your Approval

+ """ + sequence_util.send_group_email(self.env,'sos_deliverables_boq',self.id,"deenalaura.m@sosaley.in","Deliverables/BOQ Approval Request",body_html,'sos_inventory.sos_scg_group_manager') + # Email part ends + return sequence_util.action_assign_signature( + self, + 'ce_verified_by', + 'ce_verified_on' + ) + def action_scg_head_verified_esign_btn(self): + sequence_util = self.env['sos_common_scripts'] + # Email part + body_html = f""" +

Below Deliverables/BOQ is waiting for your Approval

+ """ + sequence_util.send_group_email(self.env,'sos_deliverables_boq',self.id,"deenalaura.m@sosaley.in","Deliverables/BOQ Approval Request",body_html,'sos_inventory.sos_qa_user') + # Email part ends + return sequence_util.action_assign_signature( + self, + 'scg_head_verified_by', + 'scg_head_verified_on' + ) + def action_scg_esign_btn(self): sequence_util = self.env['sos_common_scripts'] return sequence_util.action_assign_signature( @@ -185,6 +231,7 @@ class sos_deliverables_boq_Line_Material(models.Model): ref_id = fields.Many2one('sos_deliverables_boq', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) + display_name = fields.Char(string="Display Name", related="component_id.name", store=True) uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram')], default="Nos",string="Uom") currency_id = fields.Many2one('res.currency', string='Currency') material_code = fields.Char(related="component_id.material_code",string="Material Code") @@ -306,6 +353,7 @@ class sos_deliverables_Material_installationkit(models.Model): ref_id = fields.Many2one('sos_deliverables_boq', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) + display_name = fields.Char(string="Display Name", related="component_id.name", store=True) uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram'), ('Packs', 'Packs')], default="Nos",string="Uom") currency_id = fields.Many2one('res.currency', string='Currency') material_code = fields.Char(related="component_id.material_code",string="Material Code") diff --git a/sos_inventory/models/sos_dock_audit.py b/sos_inventory/models/sos_dock_audit.py index 1e55618..83d6754 100755 --- a/sos_inventory/models/sos_dock_audit.py +++ b/sos_inventory/models/sos_dock_audit.py @@ -17,12 +17,15 @@ class SOS_Dock_Audit(models.Model): deliverables_boq_id = fields.Many2one('sos_deliverables_boq', string="Deliverables/BOQ Id") fg_name = fields.Selection( [ - ('BHMS 1.2V', 'BHMS 1.2V'), - ('BHMS 2V', 'BHMS 2V'), - ('BHMS 12V', 'BHMS 12V'), + ('BHMS 1.2V', 'BHMS 1.2V'), + ('BHMS 2V', 'BHMS 2V'), + ('BHMS 12V', 'BHMS 12V'), + ('BHMS 48V', 'BHMS 48V'), + ('BMS-HV', 'BMS-HV'), ('BMS-LV 100A', 'BMS-LV 100A'), - ('BMS-LV 40A', 'BMS-LV 40A'), - ('MC 250W', 'MC 250W'), + ('BMS-LV 40A', 'BMS-LV 40A'), + ('SBMS 55A', 'SBMS 55A'), + ('MC 250W', 'MC 250W'), ('HeartTarang', 'HeartTarang') ], string="Product Type",required=True) @@ -384,6 +387,7 @@ class sos_dock_audit_Line_Material(models.Model): ref_id = fields.Many2one('sos_dock_audit', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) + display_name = fields.Char(string="Display Name", related="component_id.name", store=True) uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram')], default="Nos",string="Uom") currency_id = fields.Many2one('res.currency', string='Currency') material_code = fields.Char(related="component_id.material_code",string="Material Code") @@ -689,6 +693,7 @@ class sos_dock_audit_Material_installationkit(models.Model): ref_id = fields.Many2one('sos_dock_audit', string="Materials", ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) + display_name = fields.Char(string="Display Name", related="component_id.name", store=True) uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram')], default="Nos",string="Uom") currency_id = fields.Many2one('res.currency', string='Currency') material_code = fields.Char(related="component_id.material_code",string="Material Code") diff --git a/sos_inventory/models/sos_fir.py b/sos_inventory/models/sos_fir.py index e2302b6..9ab45da 100755 --- a/sos_inventory/models/sos_fir.py +++ b/sos_inventory/models/sos_fir.py @@ -51,21 +51,45 @@ class FIR_Only(models.Model): stores_received_on = fields.Date(string="Stores Received On") ncmr_ref = fields.Many2one('sos_ncmr',string="NCMR Reference (If any Rejected)") customer_name = fields.Many2one('sos_inventory_customers', string="Customer Name", required=True) - remarks = fields.Text(string="Remarks") test_log = fields.Binary("Test Log", required=False, attachment=True) test_log_filename = fields.Char("Test Log Filename") rejected_line_ids = fields.One2many('sos_fir_rejected_lines', 'ref_id', string="Finished Goods",copy=True, ondelete='cascade') serial_no_line_ids = fields.One2many('sos_fir_serial_no_lines', 'ref_id',copy=True) - sd_card_data = fields.Binary("SD Card Data", required=False, attachment=True) sd_card_data_filename = fields.Char("SD Card Data Filename") - cloud_data = fields.Binary("Cloud Data", required=False, attachment=True) cloud_data_filename = fields.Char("Cloud Data Filename") - firmware_data = fields.Binary("Firmware Data", required=False, attachment=True) firmware_data_filename = fields.Char("Firmware Data Filename") + serial_no_parse = fields.Text(string="Serial no's to parse") + def parse_serial_nos(self): + self.ensure_one() + if not self.serial_no_parse: + return + + # Split into clean serial numbers + serial_numbers = [ + line.strip() for line in self.serial_no_parse.splitlines() if line.strip() + ] + + SerialLine = self.env['sos_fir_serial_no_lines'] + + for serial in serial_numbers: + # Check if already exists for this ref_id + exists = SerialLine.search([ + ('ref_id', '=', self.id), + ('serial_no', '=', serial) + ], limit=1) + + if not exists: + SerialLine.create({ + 'ref_id': self.id, + 'serial_no': serial, + 'inspection_decision': 'PASS', # default + }) + + @api.onchange('batch_size') def _onchange_batch_size(self): if self._origin and self.batch_size is not False: diff --git a/sos_inventory/models/sos_inventory_customers.py b/sos_inventory/models/sos_inventory_customers.py index b9c0615..b54768a 100755 --- a/sos_inventory/models/sos_inventory_customers.py +++ b/sos_inventory/models/sos_inventory_customers.py @@ -18,18 +18,30 @@ class sos_inventory_customers(models.Model): new_name = (vals.get('customer_name') or '').lower() if new_name and len(new_name) >= 5: - existing_customers = self.search([]) + # Words/phrases to exclude + blacklist = [ + 'private limited', 'pvt ltd', 'pvt. ltd.', 'ltd', 'llp', 'inc', 'co', 'company', 'corporation' + ] + def clean_name(name): + for word in blacklist: + name = name.replace(word, '') + return name.strip() + + new_name_clean = clean_name(new_name) + + existing_customers = self.search([]) for customer in existing_customers: existing_name = (customer.customer_name or '').lower() + existing_name_clean = clean_name(existing_name) - # Check all substrings of length 5 or more - for i in range(len(new_name) - 4): - substring = new_name[i:i+5] - if substring in existing_name: + # Check all substrings of length 7 or more + for i in range(len(new_name_clean) - 6): + substring = new_name_clean[i:i+7] + if substring in existing_name_clean: raise UserError( f"A customer with a similar name already exists: '{customer.customer_name}' " f"(matched substring: '{substring}')" ) - return super(sos_inventory_customers, self).create(vals) \ No newline at end of file + return super().create(vals) \ No newline at end of file diff --git a/sos_inventory/models/sos_ir.py b/sos_inventory/models/sos_ir.py index ed8d164..6a19daa 100755 --- a/sos_inventory/models/sos_ir.py +++ b/sos_inventory/models/sos_ir.py @@ -217,6 +217,11 @@ class SOS_IR(models.Model): for item in self.line_ids_material: # Fetch the component related to the current item component = self.env['sos_material'].browse(item.component_id.id) + if self.supplier_name and self.supplier_name.id not in component.suppliers.ids: + component.write({ + 'suppliers': [(4, self.supplier_name.id)] + }) + if component.shelf_life == "yes": shelflife_line_values = { 'ir_ref_no':self.id, @@ -301,6 +306,10 @@ class SOS_IR(models.Model): for item in self.line_ids_sfg: # PO update part sfg_component = self.env['sos_sfg'].browse(item.component_id.id) + if self.service_provider_name and self.service_provider_name.id not in sfg_component.service_providers.ids: + sfg_component.write({ + 'service_providers': [(4, self.service_provider_name.id)] + }) if self.wo_planned_at == "outsource": if self.wo_no: if not self.orr_no: diff --git a/sos_inventory/models/sos_sales_order.py b/sos_inventory/models/sos_sales_order.py index d7a1ab4..3ee5d5b 100755 --- a/sos_inventory/models/sos_sales_order.py +++ b/sos_inventory/models/sos_sales_order.py @@ -25,7 +25,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.Char(string="Customer Name") + customer_name = fields.Many2one('sos_inventory_customers',string="Customer Name") lead_time = fields.Datetime(string="Lead Time") customer_po_no = fields.Char(string="PO No") customer_po_date = fields.Datetime(string="PO Date") @@ -58,7 +58,7 @@ class SOS_SalesOrder(models.Model): 'sales_id':self.id, 'fg_name':self.fg_name, 'quantity':self.qty, - 'customer_name':self.customer_name, + 'customer_name':self.customer_name.customer_name, 'lead_time':self.lead_time, 'customer_po_no':self.customer_po_no, 'customer_po_date':self.customer_po_date @@ -109,15 +109,15 @@ class SOS_SalesOrder(models.Model): sequence_util = self.env['sos_common_scripts'] return sequence_util.generate_sequence('sos_sales_order','SALES', 'order_id') - @api.model - def create(self, vals): - customer_name = vals.get('customer_name') - if customer_name: - existing = self.env['sos_inventory_customers'].search([('customer_name', '=', customer_name)], limit=1) - if not existing: - self.env['sos_inventory_customers'].create({'customer_name': customer_name}) + # @api.model + # def create(self, vals): + # customer_name = vals.get('customer_name') + # if customer_name: + # existing = self.env['sos_inventory_customers'].search([('customer_name', '=', customer_name)], limit=1) + # if not existing: + # self.env['sos_inventory_customers'].create({'customer_name': customer_name}) - return super(SOS_SalesOrder, self).create(vals) + # return super(SOS_SalesOrder, self).create(vals) class SOS_SalesOrder_Line(models.Model): _name = 'sos_sales_order_line' diff --git a/sos_inventory/report/sos_boq_labels.xml b/sos_inventory/report/sos_boq_labels.xml index 86ae22f..c7a8ea7 100755 --- a/sos_inventory/report/sos_boq_labels.xml +++ b/sos_inventory/report/sos_boq_labels.xml @@ -86,131 +86,192 @@ } -
+
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Set No#/
Battery
No.
Name + + + + + + +
UOM + + + + + + +
Qty + + + + + + +
S.No
+
+
- - - -
- - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Set No#/
Battery
No.
Name
UOM
Qty
S.No
-
-
- - -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Set No#/
Battery
No.
Name
UOM
Qty
S.No
-
-
- -
-
-
-
-
+ +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Set No#/
Battery
No.
Name + + + + + + +
UOM + + + + + + +
Qty + + + + + + +
S.No
+ +
+
-
+
-
- +
+
+
+
+
+
+ \ No newline at end of file diff --git a/sos_inventory/report/sos_boq_report.xml b/sos_inventory/report/sos_boq_report.xml new file mode 100755 index 0000000..4ab3776 --- /dev/null +++ b/sos_inventory/report/sos_boq_report.xml @@ -0,0 +1,100 @@ + + + + + + BOQ Report + sos_deliverables_boq + qweb-pdf + sos_inventory.report_boq + + \ No newline at end of file diff --git a/sos_inventory/report/sos_fir_report.xml b/sos_inventory/report/sos_fir_report.xml index 88073d7..f99c570 100755 --- a/sos_inventory/report/sos_fir_report.xml +++ b/sos_inventory/report/sos_fir_report.xml @@ -45,10 +45,7 @@ Sampling Size - - Serial No - - + @@ -56,10 +53,7 @@ Sampling Size - - Serial No - - + @@ -74,7 +68,21 @@ -

+ + + + + + + + + + + + + +
Serial No's
+ - @@ -307,6 +353,8 @@
Verified By + QA Verified By



+
+
diff --git a/sos_inventory/views/sos_dock_audit_view.xml b/sos_inventory/views/sos_dock_audit_view.xml index f2d52c0..db5c7a5 100755 --- a/sos_inventory/views/sos_dock_audit_view.xml +++ b/sos_inventory/views/sos_dock_audit_view.xml @@ -108,6 +108,7 @@ + @@ -122,6 +123,7 @@ + diff --git a/sos_inventory/views/sos_fir_view.xml b/sos_inventory/views/sos_fir_view.xml index f31eb38..de4fe21 100755 --- a/sos_inventory/views/sos_fir_view.xml +++ b/sos_inventory/views/sos_fir_view.xml @@ -20,11 +20,12 @@ - + + - + @@ -61,6 +62,13 @@ + + + +
Parse Serial No's
+

+ + diff --git a/sos_inventory/views/sos_order_delivery_plan_view.xml b/sos_inventory/views/sos_order_delivery_plan_view.xml index 77bbb78..bc79c39 100755 --- a/sos_inventory/views/sos_order_delivery_plan_view.xml +++ b/sos_inventory/views/sos_order_delivery_plan_view.xml @@ -62,7 +62,7 @@
- +
Finished Goods
diff --git a/sos_marketing/models/sos_marketing_leads.py b/sos_marketing/models/sos_marketing_leads.py index f09f16d..4de1a7a 100755 --- a/sos_marketing/models/sos_marketing_leads.py +++ b/sos_marketing/models/sos_marketing_leads.py @@ -41,9 +41,21 @@ class sos_marketing(models.Model): cc_mail = fields.Many2one('res.users',string="CC to", domain=lambda self: [('groups_id', 'in', self.env.ref('sos_inventory.sos_sales_user').ids + self.env.ref('sos_inventory.sos_inside_sales_user').ids)]) move_div_display = fields.Boolean(default=False,string="Moved to Sales") moved_on = fields.Date(string="Moved On") + source = fields.Selection( + [ + ('Expo', 'Expo'), + ('Conference', 'Conference'), + ('LinkedIn Groups', 'LinkedIn Groups'), + ('LinkedIn Poll', 'LinkedIn Poll'), + ('Direct Company', 'Direct Company'), + ('Expo [Paricipated]', 'Expo [Paricipated]') + + ], + string="Source",default="Expo") @api.onchange('expo_option') def _onchange_expo(self): if self.expo_option: + self.source = "Expo" self.conference_option = False self.linkedin_groups_option = False self.linkedin_poll_option = False @@ -53,6 +65,7 @@ class sos_marketing(models.Model): @api.onchange('conference_option') def _onchange_conference(self): if self.conference_option: + self.source = "Conference" self.expo_option = False self.linkedin_groups_option = False self.linkedin_poll_option = False @@ -62,6 +75,7 @@ class sos_marketing(models.Model): @api.onchange('linkedin_groups_option') def _onchange_linkedin_groups(self): if self.linkedin_groups_option: + self.source = "LinkedIn Groups" self.expo_option = False self.conference_option = False self.linkedin_poll_option = False @@ -71,6 +85,7 @@ class sos_marketing(models.Model): @api.onchange('linkedin_poll_option') def _onchange_linkedin_poll(self): if self.linkedin_poll_option: + self.source = "LinkedIn Poll" self.expo_option = False self.conference_option = False self.linkedin_groups_option = False @@ -80,6 +95,7 @@ class sos_marketing(models.Model): @api.onchange('direct_company') def _onchange_direct_company(self): if self.direct_company: + self.source = "Direct Company" self.expo_option = False self.conference_option = False self.linkedin_groups_option = False @@ -89,6 +105,7 @@ class sos_marketing(models.Model): @api.onchange('expo_participated_option') def _onchange_expo_participated(self): if self.expo_participated_option: + self.source = "Expo [Paricipated]" self.expo_option = False self.conference_option = False self.linkedin_groups_option = False diff --git a/sos_marketing/views/sos_marketing_leads_view.xml b/sos_marketing/views/sos_marketing_leads_view.xml index f0b55b7..0da03bc 100755 --- a/sos_marketing/views/sos_marketing_leads_view.xml +++ b/sos_marketing/views/sos_marketing_leads_view.xml @@ -7,7 +7,25 @@ tree,form,kanban + + sos_marketing_leads.search + sos_marketing_leads + + + + + + + + + + + + + + + sos_marketing_leads.view.tree @@ -16,6 +34,7 @@ + @@ -55,6 +74,7 @@ + diff --git a/sos_sales/models/__pycache__/sos_proposal_boq.cpython-310.pyc b/sos_sales/models/__pycache__/sos_proposal_boq.cpython-310.pyc index 9153736..bc5e6a9 100644 Binary files a/sos_sales/models/__pycache__/sos_proposal_boq.cpython-310.pyc and b/sos_sales/models/__pycache__/sos_proposal_boq.cpython-310.pyc differ diff --git a/sos_sales/models/__pycache__/sos_proposal_customer_requirement.cpython-310.pyc b/sos_sales/models/__pycache__/sos_proposal_customer_requirement.cpython-310.pyc index dec46a8..5a0b441 100644 Binary files a/sos_sales/models/__pycache__/sos_proposal_customer_requirement.cpython-310.pyc and b/sos_sales/models/__pycache__/sos_proposal_customer_requirement.cpython-310.pyc differ diff --git a/sos_sales/models/__pycache__/sos_sales_achievement_report.cpython-310.pyc b/sos_sales/models/__pycache__/sos_sales_achievement_report.cpython-310.pyc index 7b98b7b..8e69670 100644 Binary files a/sos_sales/models/__pycache__/sos_sales_achievement_report.cpython-310.pyc and b/sos_sales/models/__pycache__/sos_sales_achievement_report.cpython-310.pyc differ diff --git a/sos_sales/models/sos_proposal_boq.py b/sos_sales/models/sos_proposal_boq.py index daaa84c..bb898ef 100755 --- a/sos_sales/models/sos_proposal_boq.py +++ b/sos_sales/models/sos_proposal_boq.py @@ -1,5 +1,7 @@ from odoo import models, fields, api import math +from collections import defaultdict +from odoo.tools.misc import format_amount class Battery_Installation_Requirement(models.Model): _name = 'sos_proposal_boq' @@ -17,6 +19,7 @@ class Battery_Installation_Requirement(models.Model): number_of_strings = fields.Integer(string="Number of Strings") number_of_ups = fields.Integer(string="Number of UPS",store=True) ups_rating_kva = fields.Integer(string="UPS Rating (KVA)") + no_of_electrical_panel = fields.Integer(string="No of Electrical Panel") battery_capacity_ah = fields.Integer(string="Battery Capacity (AH)") products = fields.Selection([ ('BHMS 1.2V', 'BHMS 1.2V'), @@ -69,13 +72,19 @@ class Battery_Installation_Requirement(models.Model): #CE Team Fields engineers_nos=fields.Integer(string="No of engineers required") no_of_days=fields.Integer(string="No of days") - mode_of_transport = fields.Selection([('car','🚗 Car'),('bus','🚌 Bus'),('train','🚂 Train'),('flight','đŸ›Šī¸ Flight')],string='Mode of Transport',default="train") - extra_cost_line_ids = fields.One2many('sos_extra_cost_lines', 'ref_id',copy=True) - transport_expense = fields.Monetary(readonly=False,string="Transport", compute='_compute_transport_expense', store=True, currency_field='currency_id') - food_expense = fields.Monetary(readonly=False,string="Food", compute='_compute_food_expense', store=True, currency_field='currency_id') - stay_expense = fields.Monetary(readonly=False,string="Room Rent", compute='_compute_stay_expense', store=True, currency_field='currency_id') - local_expense = fields.Monetary(readonly=False,string="Local Conveyance", compute='_compute_local_expense', store=True, currency_field='currency_id') + + # mode_of_transport = fields.Selection([('car','🚗 Car'),('bus','🚌 Bus'),('train','🚂 Train'),('flight','đŸ›Šī¸ Flight')],string='Mode of Transport',default="train") + # transport_expense = fields.Monetary(readonly=False,string="Transport", compute='_compute_transport_expense', store=True, currency_field='currency_id') + # food_expense = fields.Monetary(readonly=False,string="Food", compute='_compute_food_expense', store=True, currency_field='currency_id') + # stay_expense = fields.Monetary(readonly=False,string="Room Rent", compute='_compute_stay_expense', store=True, currency_field='currency_id') + # local_expense = fields.Monetary(readonly=False,string="Local Conveyance", compute='_compute_local_expense', store=True, currency_field='currency_id') + man_month_1to2_yrs_persons = fields.Integer(string="Man Month (Persons) 1 to 2 Yrs") + man_month_2to3_yrs_persons = fields.Integer(string="Man Month (Persons) 2 to 3 Yrs") + man_month_manager_persons = fields.Integer(string="Man Month (Persons) Manager Yrs") + man_month_1to2_yrs_cost = fields.Monetary(string="Cost", currency_field='currency_id') + man_month_2to3_yrs_cost = fields.Monetary(string="Cost", currency_field='currency_id') + man_month_manager_cost = fields.Monetary(string="Cost", currency_field='currency_id') iandc_costing=fields.Monetary(readonly=False,string="I & C Costing",currency_field='currency_id',compute='_compute_iandc_expense') #FG Fields line_ids_fg_ups1 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 1",compute='_compute_line_ids_by_ups',store=False) @@ -93,6 +102,14 @@ class Battery_Installation_Requirement(models.Model): line_ids_fg_ups13 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 13",compute='_compute_line_ids_by_ups',store=False) line_ids_fg_ups14 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 14",compute='_compute_line_ids_by_ups',store=False) line_ids_fg_ups15 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 15",compute='_compute_line_ids_by_ups',store=False) + merged_fg_html = fields.Html(string="FG (Merged)", compute="_compute_merged_fg_html", sanitize=False) + merged_sfg_html = fields.Html(string="SFG (Merged)", compute="_compute_merged_sfg_html", sanitize=False) + merged_material_html = fields.Html(string="Materials (Merged)", compute="_compute_merged_material_html", sanitize=False) + merged_installation_kit_html = fields.Html(string="InstallationKit (Merged)", compute="_compute_merged_installation_kit_html", sanitize=False) + merged_miscellaneous_html = fields.Html(string="Miscellaneous (Merged)", compute="_compute_merged_miscellaneous_html", sanitize=False) + merged_spare_html = fields.Html(string="Spare (Merged)", compute="_compute_merged_spare_html", sanitize=False) + + #SFG Fields line_ids_sfg_ups1 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 1",compute='_compute_line_ids_by_ups',store=False) @@ -113,21 +130,21 @@ class Battery_Installation_Requirement(models.Model): #Material Fields - line_ids_material_ups1 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 1",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups2 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 2",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups3 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 3",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups4 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 4",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups5 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 5",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups6 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 6",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups7 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 7",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups8 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 8",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups9 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 9",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups10 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 10",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups11 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 11",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups12 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 12",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups13 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 13",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups14 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 14",compute='_compute_line_ids_by_ups',store=False) - line_ids_material_ups15 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 15",compute='_compute_line_ids_by_ups',store=False) + line_ids_material_ups1 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 1",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups2 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 2",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups3 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 3",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups4 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 4",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups5 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 5",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups6 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 6",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups7 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 7",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups8 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 8",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups9 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 9",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups10 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 10",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups11 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 11",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups12 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 12",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups13 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 13",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups14 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 14",compute='_compute_line_ids_by_ups',store=False,readonly=False) + line_ids_material_ups15 = fields.One2many('sos_proposal_boq_material', 'ref_id', string="Material UPS 15",compute='_compute_line_ids_by_ups',store=False,readonly=False) #Installation Kit Fields @@ -199,6 +216,454 @@ class Battery_Installation_Requirement(models.Model): line_ids_spare_ups13 = fields.One2many('sos_proposal_line_spare_ups13','ref_id', string="Spare UPS 13") line_ids_spare_ups14 = fields.One2many('sos_proposal_line_spare_ups14','ref_id', string="Spare UPS 14") line_ids_spare_ups15 = fields.One2many('sos_proposal_line_spare_ups15','ref_id', string="Spare UPS 15") + def _compute_merged_spare_html(self): + for rec in self: + # Collect lines from ups1..ups15 (skip models that don't exist) + models = [f'sos_proposal_line_spare_ups{i}' for i in range(1, 16)] + + agg = defaultdict(float) # (component_id, uom, currency_id, unit_price) -> qty + name_map, curr_map, price_map = {}, {}, {} + + for model in models: + if model not in self.env: + continue + for l in self.env[model].search([('ref_id', '=', rec.id)]): + unit_price = l.unit_price or 0.0 + curr_id = l.currency_id.id if l.currency_id else False + key = (l.component_id.id, l.uom, curr_id, unit_price) + + agg[key] += float(l.quantity or 0) + if key not in name_map: + # Use part number (fallback to component name if needed) + name_map[key] = (getattr(l.component_id, 'part_no', False) or l.component_id.name or '') + curr_map[key] = l.currency_id if l.currency_id else False + price_map[key] = unit_price + + rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower()) + if not rows_sorted: + rec.merged_spare_html = "No Material items." + continue + + def fmt_price(amount, currency): + return format_amount(self.env, amount, currency) if currency else f"{amount:.2f}" + + total_sum = 0.0 + rows_html = "" + for key, qty in rows_sorted: + if not qty: + continue + total_price = qty * price_map[key] + total_sum += total_price + rows_html += ( + f"" + f"{name_map[key]}" + f"{key[1] or ''}" + f"{fmt_price(price_map[key], curr_map[key])}" + f"{qty:.2f}" + f"{fmt_price(total_price, curr_map[key])}" + f"" + ) + + if not rows_html: + rec.merged_spare_html = "No Material items." + continue + + currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False + rows_html += ( + f"" + f"Grand Total:" + f"{fmt_price(total_sum, currency_for_total)}" + f"" + ) + + rec.merged_spare_html = f""" + + + + + + + + + + + {rows_html} +
Material NameUoMUnit PriceTotal QtyTotal Price
+ """ + def _compute_merged_miscellaneous_html(self): + for rec in self: + lines = self.env['sos_proposal_miscellaneous_items'].search([('ref_id', '=', rec.id)]) + + # Group by (component, currency, cost) + agg = defaultdict(float) + name_map = {} + curr_map = {} + price_map = {} + + for l in lines: + cost = l.cost or 0.0 + curr_id = l.currency_id.id if l.currency_id else False + key = (l.name, curr_id, cost) + + agg[key] += float(l.quantity or 0) + if key not in name_map: + name_map[key] = l.name or '' + curr_map[key] = l.currency_id if l.currency_id else False + price_map[key] = cost + + rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower()) + if not rows_sorted: + rec.merged_miscellaneous_html = "No Miscellaneous items." + continue + + def fmt_price(amount, currency): + if currency: + return format_amount(self.env, amount, currency) + return f"{amount:.2f}" + + total_sum = 0.0 + rows_html = "" + for key, qty in rows_sorted: + if not qty: # Skip rows with qty = 0 + continue + total_price = qty * price_map[key] + total_sum += total_price + rows_html += ( + f"" + f"{name_map[key]}" + f"{fmt_price(price_map[key], curr_map[key])}" + f"{qty:.2f}" + f"{fmt_price(total_price, curr_map[key])}" + f"" + ) + + if not rows_html: # If all rows were skipped + rec.merged_miscellaneous_html = "No Miscellaneous items." + continue + + # Add grand total row + currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False + rows_html += ( + f"" + f"Grand Total:" + f"{fmt_price(total_sum, currency_for_total)}" + f"" + ) + + rec.merged_miscellaneous_html = f""" + + + + + + + + + + {rows_html} +
NameUnit PriceTotal QtyTotal Price
+ """ + def _compute_merged_installation_kit_html(self): + for rec in self: + lines = self.env['sos_proposal_line_material_installation'].search([('ref_id', '=', rec.id)]) + + # Group by (component, uom, currency, unit_price) + agg = defaultdict(float) + name_map = {} + curr_map = {} + price_map = {} + + for l in lines: + unit_price = l.unit_price or 0.0 + curr_id = l.currency_id.id if l.currency_id else False + key = (l.component_id.id, l.uom, curr_id, unit_price) + + agg[key] += float(l.quantity or 0) + if key not in name_map: + name_map[key] = l.component_id.part_no or '' + curr_map[key] = l.currency_id if l.currency_id else False + price_map[key] = unit_price + + rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower()) + if not rows_sorted: + rec.merged_installation_kit_html = "No Material items." + continue + + def fmt_price(amount, currency): + if currency: + return format_amount(self.env, amount, currency) + return f"{amount:.2f}" + + total_sum = 0.0 + rows_html = "" + for key, qty in rows_sorted: + if not qty: # Skip rows with qty = 0 + continue + total_price = qty * price_map[key] + total_sum += total_price + rows_html += ( + f"" + f"{name_map[key]}" + f"{key[1] or ''}" + f"{fmt_price(price_map[key], curr_map[key])}" + f"{qty:.2f}" + f"{fmt_price(total_price, curr_map[key])}" + f"" + ) + + if not rows_html: # If all rows were skipped + rec.merged_installation_kit_html = "No Material items." + continue + + # Add grand total row + currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False + rows_html += ( + f"" + f"Grand Total:" + f"{fmt_price(total_sum, currency_for_total)}" + f"" + ) + + rec.merged_installation_kit_html = f""" + + + + + + + + + + + {rows_html} +
Material NameUoMUnit PriceTotal QtyTotal Price
+ """ + def _compute_merged_material_html(self): + for rec in self: + lines = self.env['sos_proposal_boq_material'].search([('ref_id', '=', rec.id)]) + + # Group by (component, uom, currency, unit_price) + agg = defaultdict(float) + name_map = {} + curr_map = {} + price_map = {} + + for l in lines: + unit_price = l.unit_price or 0.0 + curr_id = l.currency_id.id if l.currency_id else False + key = (l.component_id.id, l.uom, curr_id, unit_price) + + agg[key] += float(l.quantity or 0) + if key not in name_map: + name_map[key] = l.component_id.part_no or '' + curr_map[key] = l.currency_id if l.currency_id else False + price_map[key] = unit_price + + rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower()) + if not rows_sorted: + rec.merged_material_html = "No Material items." + continue + + def fmt_price(amount, currency): + if currency: + return format_amount(self.env, amount, currency) + return f"{amount:.2f}" + + total_sum = 0.0 + rows_html = "" + for key, qty in rows_sorted: + if not qty: # Skip rows with qty = 0 + continue + total_price = qty * price_map[key] + total_sum += total_price + rows_html += ( + f"" + f"{name_map[key]}" + f"{key[1] or ''}" + f"{fmt_price(price_map[key], curr_map[key])}" + f"{qty:.2f}" + f"{fmt_price(total_price, curr_map[key])}" + f"" + ) + + if not rows_html: # If all rows were skipped + rec.merged_material_html = "No Material items." + continue + + # Add grand total row + currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False + rows_html += ( + f"" + f"Grand Total:" + f"{fmt_price(total_sum, currency_for_total)}" + f"" + ) + + rec.merged_material_html = f""" + + + + + + + + + + + {rows_html} +
Material NameUoMUnit PriceTotal QtyTotal Price
+ """ + def _compute_merged_sfg_html(self): + for rec in self: + lines = self.env['sos_proposal_boq_sfg'].search([('ref_id', '=', rec.id)]) + + # Group by (component, uom, currency, unit_price) + agg = defaultdict(float) + name_map = {} + curr_map = {} + price_map = {} + + for l in lines: + unit_price = l.unit_price or 0.0 + curr_id = l.currency_id.id if l.currency_id else False + key = (l.component_id.id, l.uom, curr_id, unit_price) + + agg[key] += float(l.quantity or 0) + if key not in name_map: + name_map[key] = l.component_id.name or '' + curr_map[key] = l.currency_id if l.currency_id else False + price_map[key] = unit_price + + rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower()) + if not rows_sorted: + rec.merged_sfg_html = "No SFG items." + continue + + def fmt_price(amount, currency): + if currency: + return format_amount(self.env, amount, currency) + return f"{amount:.2f}" + + total_sum = 0.0 + rows_html = "" + for key, qty in rows_sorted: + if not qty: # Skip rows with qty = 0 + continue + total_price = qty * price_map[key] + total_sum += total_price + rows_html += ( + f"" + f"{name_map[key]}" + f"{key[1] or ''}" + f"{fmt_price(price_map[key], curr_map[key])}" + f"{qty:.2f}" + f"{fmt_price(total_price, curr_map[key])}" + f"" + ) + + if not rows_html: # If all rows were skipped + rec.merged_sfg_html = "No SFG items." + continue + + # Add grand total row + currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False + rows_html += ( + f"" + f"Grand Total:" + f"{fmt_price(total_sum, currency_for_total)}" + f"" + ) + + rec.merged_sfg_html = f""" + + + + + + + + + + + {rows_html} +
SFG NameUoMUnit PriceTotal QtyTotal Price
+ """ + def _compute_merged_fg_html(self): + for rec in self: + lines = self.env['sos_proposal_boq_fg'].search([('ref_id', '=', rec.id)]) + + # Group by (component, uom, currency, unit_price) + agg = defaultdict(float) + name_map = {} + curr_map = {} + price_map = {} + + for l in lines: + unit_price = l.unit_price or 0.0 + curr_id = l.currency_id.id if l.currency_id else False + key = (l.component_id.id, l.uom, curr_id, unit_price) + + agg[key] += float(l.quantity or 0) + if key not in name_map: + name_map[key] = l.component_id.name or '' + curr_map[key] = l.currency_id if l.currency_id else False + price_map[key] = unit_price + + rows_sorted = sorted(agg.items(), key=lambda it: name_map[it[0]].lower()) + if not rows_sorted: + rec.merged_fg_html = "No FG items." + continue + + def fmt_price(amount, currency): + if currency: + return format_amount(self.env, amount, currency) + return f"{amount:.2f}" + + total_sum = 0.0 + rows_html = "" + for key, qty in rows_sorted: + if not qty: # Skip rows with qty = 0 + continue + total_price = qty * price_map[key] + total_sum += total_price + rows_html += ( + f"" + f"{name_map[key]}" + f"{key[1] or ''}" + f"{fmt_price(price_map[key], curr_map[key])}" + f"{qty:.2f}" + f"{fmt_price(total_price, curr_map[key])}" + f"" + ) + + if not rows_html: # If all rows were skipped + rec.merged_fg_html = "No FG items." + continue + + # Add grand total row + currency_for_total = curr_map[rows_sorted[0][0]] if rows_sorted else False + rows_html += ( + f"" + f"Grand Total:" + f"{fmt_price(total_sum, currency_for_total)}" + f"" + ) + + rec.merged_fg_html = f""" + + + + + + + + + + + {rows_html} +
FG NameUoMUnit PriceTotal QtyTotal Price
+ """ + @api.model def create(self, vals): res = super().create(vals) @@ -225,26 +690,29 @@ class Battery_Installation_Requirement(models.Model): for rec in related_records: rec._compute_acc_costing_submitted_by() - @api.depends('no_of_days') - def _compute_local_expense(self): - for rec in self: - rec.local_expense = rec.no_of_days * 500 - @api.depends('no_of_days') - def _compute_stay_expense(self): - for rec in self: - rec.stay_expense = rec.no_of_days * 2500 - @api.depends('no_of_days') - def _compute_food_expense(self): - for rec in self: - rec.food_expense = rec.no_of_days * 600 - @api.depends('no_of_days') - def _compute_transport_expense(self): - for rec in self: - rec.transport_expense = 5000 - @api.depends('no_of_days','transport_expense','food_expense','stay_expense','local_expense') + # @api.depends('no_of_days') + # def _compute_local_expense(self): + # for rec in self: + # rec.local_expense = rec.no_of_days * 500 + # @api.depends('no_of_days') + # def _compute_stay_expense(self): + # for rec in self: + # rec.stay_expense = rec.no_of_days * 2500 + # @api.depends('no_of_days') + # def _compute_food_expense(self): + # for rec in self: + # rec.food_expense = rec.no_of_days * 600 + # @api.depends('no_of_days') + # def _compute_transport_expense(self): + # for rec in self: + # rec.transport_expense = 5000 + + @api.depends('no_of_days','man_month_1to2_yrs_persons','man_month_2to3_yrs_persons','man_month_manager_persons','man_month_1to2_yrs_cost','man_month_2to3_yrs_cost','man_month_manager_cost') def _compute_iandc_expense(self): for rec in self: - rec.iandc_costing = rec.transport_expense + rec.food_expense + rec.stay_expense + rec.local_expense + rec.iandc_costing = ((rec.man_month_1to2_yrs_persons * rec.man_month_1to2_yrs_cost) + + (rec.man_month_2to3_yrs_persons * rec.man_month_2to3_yrs_cost) + + (rec.man_month_manager_persons * rec.man_month_manager_cost)) * rec.no_of_days @api.depends('extra_cost_line_ids.cost') def _compute_extra_lines_cost(self): @@ -425,6 +893,7 @@ class Battery_Installation_Requirement(models.Model): self.customer_name = self.proposal_id.customer_name self.location = self.proposal_id.location + self.no_of_electrical_panel = self.proposal_id.no_of_electrical_panel self.number_of_batteries = self.proposal_id.number_of_batteries self.number_of_ups = self.proposal_id.number_of_ups self.products = self.proposal_id.products @@ -508,17 +977,22 @@ class Battery_Installation_Requirement(models.Model): matching_ups_line = self.proposal_id.ups_line_ids.filtered(lambda l: l.ups_index == ups_index) if matching_ups_line: single_set_qty = math.ceil((matching_ups_line.number_of_batteries)/4) * matching_ups_line.number_of_strings_per_battery + name_lower = (line.component_id.name or "").lower() + is_electrical_panel = "electrical panel" in name_lower + is_panel_exceeding = self.proposal_id.no_of_electrical_panel < ups_index fg_lines.append((0, 0, { 'component_id': line.component_id.id, 'uom': line.uom, - 'unit_price': line.component_id.unit_price, + 'unit_price': 0.00 if is_panel_exceeding and is_electrical_panel else line.component_id.unit_price, 'description': line.description, 'item_type': line.item_type, - 'singet_set_qty': single_set_qty, + 'total_set': 0 if is_panel_exceeding and is_electrical_panel else 1, + 'singet_set_qty': 0 if is_panel_exceeding and is_electrical_panel else single_set_qty, 'production_cost': line.add_production_cost, 'ups_index': ups_index })) + for line in record.sfg_ids: if line.item_type == 'Master Panel': single_set_qty = 1 @@ -646,10 +1120,22 @@ class Battery_Installation_Requirement(models.Model): record.total_fg_cost = sum(line.total_price for line in record.line_ids_fg) def action_ce_esign_btn(self): body_html = f""" -

Below Proposal is waiting for your Updation

+

Below BOQ is waiting for your updation.

+

Customer Name: {self.customer_name or ''}

+

Location: {self.location or ''}

+

Number of Batteries: {self.number_of_batteries or ''}

""" + sequence_util = self.env['sos_common_scripts'] - sequence_util.send_group_email(self.env,'sos_proposal_boq',self.id,"deenalaura.m@sosaley.in","Proposal System - BOQ Submitted",body_html,'sos_inventory.sos_finance_user') + sequence_util.send_group_email( + self.env, + 'sos_proposal_boq', + self.id, + "deenalaura.m@sosaley.in", + f"Proposal System - BOQ Submitted for {self.customer_name}", + body_html, + 'sos_inventory.sos_finance_user' + ) return sequence_util.action_assign_signature( self, 'boq_submitted_by_name', diff --git a/sos_sales/models/sos_proposal_customer_requirement.py b/sos_sales/models/sos_proposal_customer_requirement.py index b921509..4980c47 100755 --- a/sos_sales/models/sos_proposal_customer_requirement.py +++ b/sos_sales/models/sos_proposal_customer_requirement.py @@ -30,6 +30,7 @@ class Battery_Installation_Requirement(models.Model): number_of_strings = fields.Integer(string="Number of Strings") number_of_ups = fields.Integer(string="Number of UPS") ups_rating_kva = fields.Integer(string="UPS Rating (KVA)") + no_of_electrical_panel = fields.Integer(string="No of Electrical Panel") battery_capacity_ah = fields.Integer(string="Battery Capacity (AH)") specific_requirements = fields.Html(string="Specific Requirements") products = fields.Selection( @@ -147,6 +148,7 @@ class Battery_Installation_Requirement(models.Model): @api.onchange('number_of_ups') def _onchange_number_of_ups(self): if self.number_of_ups is not None: + self.no_of_electrical_panel = self.number_of_ups if self.number_of_ups > 15: raise ValidationError("Number of UPS cannot be more than 15.") diff --git a/sos_sales/models/sos_sales_achievement_report.py b/sos_sales/models/sos_sales_achievement_report.py index 25b26ca..7bb5410 100755 --- a/sos_sales/models/sos_sales_achievement_report.py +++ b/sos_sales/models/sos_sales_achievement_report.py @@ -694,16 +694,31 @@ class SOS_Sales_Achievement_Report_Brief(models.Model): report.write({ new_field_billed: (getattr(report, new_field_billed, 0.0) or 0.0) + new_billed_amount }) - # Optionally create billing collection entry (if needed) - if new_billed_amount > 0: + # Optionally create billing collection entry (if needed + domain = [ + ('ref_id', '=', report.id), + ('sales_person', '=', report.sales_person.id), + ('customer_name', '=', vals.get('customer_name', rec.customer_name.id)) + ] + + existing = self.env['sos_billing_collection'].search(domain, limit=1) + + if not existing: self.env['sos_billing_collection'].create({ 'ref_id': report.id, 'customer_name': vals.get('customer_name', rec.customer_name.id), 'sales_person': report.sales_person.id, 'action_status': 'Billed', 'date_of_action': new_billed_date, + 'po_no':vals.get('po_no'), 'value': new_billed_amount }) + else: + existing.write({ + 'value': new_billed_amount, + 'po_no':vals.get('po_no'), + 'date_of_action': new_billed_date + }) return super(SOS_Sales_Achievement_Report_Brief, self).write(vals) @@ -748,7 +763,8 @@ class SOS_Sales_Achievement_Report_Brief(models.Model): 'sales_person': report.sales_person.id, 'action_status': 'Billed', 'date_of_action': billed_date, - 'value': billed_value + 'value': billed_value, + 'po_no':vals.get('po_no') }) new_record = super(SOS_Sales_Achievement_Report_Brief, self).create(vals) return new_record diff --git a/sos_sales/views/sos_proposal_boq_view.xml b/sos_sales/views/sos_proposal_boq_view.xml index 5d11f3a..f70bcc1 100755 --- a/sos_sales/views/sos_proposal_boq_view.xml +++ b/sos_sales/views/sos_proposal_boq_view.xml @@ -13,12 +13,12 @@ - - + + - - + +

@@ -26,9 +26,9 @@

Detailed Specifications




- - - + + + @@ -40,6 +40,7 @@ +

Finished Goods

@@ -111,7 +112,7 @@

Spare/Additional Materials

- + @@ -219,7 +220,7 @@

Spare/Additional Materials

- + @@ -328,7 +329,7 @@

Spare/Additional Materials

- + @@ -436,7 +437,7 @@

Spare/Additional Materials

- + @@ -544,7 +545,7 @@

Spare/Additional Materials

- + @@ -651,7 +652,7 @@

Spare/Additional Materials

- + @@ -759,7 +760,7 @@

Spare/Additional Materials

- + @@ -867,7 +868,7 @@

Spare/Additional Materials

- + @@ -975,7 +976,7 @@

Spare/Additional Materials

- + @@ -1083,7 +1084,7 @@

Spare/Additional Materials

- + @@ -1190,7 +1191,7 @@

Spare/Additional Materials

- + @@ -1297,7 +1298,7 @@

Spare/Additional Materials

- + @@ -1404,7 +1405,7 @@

Spare/Additional Materials

- + @@ -1511,7 +1512,7 @@

Spare/Additional Materials

- + @@ -1618,7 +1619,7 @@

Spare/Additional Materials

- + @@ -1654,116 +1655,42 @@
- - - - - - - - - - - - - - + + + +

+ + + + + + + + +
No of PersonsCost per Day
Man Month( 2 to 3 Yrs)
Man Month( 3 to 5 Yrs)
Man Month(Manager)
Total
@@ -1798,7 +1725,7 @@

Miscellaneous Cost

- + @@ -1812,20 +1739,20 @@

Final Cost

- - + + - - - - + + + + - + - - + + - +
Product Cost
Opreational Cost per Battery
Product Cost
Opreational Cost per Battery
Opreational Cost
Packing & Forwarding
Installation & Commissioning
Warranty (%)
Opreational Cost
Packing & Forwarding
Installation & Commissioning
Warranty (%)
Warranty Cost
Warranty Cost
Additional Warranty
MSP Per Battery
Additional Warranty
MSP Per Battery
Final MSP
Final MSP
diff --git a/sos_sales/views/sos_proposal_customer_requirement_view.xml b/sos_sales/views/sos_proposal_customer_requirement_view.xml index bd54e83..6160b9a 100755 --- a/sos_sales/views/sos_proposal_customer_requirement_view.xml +++ b/sos_sales/views/sos_proposal_customer_requirement_view.xml @@ -37,6 +37,7 @@ + diff --git a/sos_sales/views/sos_sales_leads_view.xml b/sos_sales/views/sos_sales_leads_view.xml index 33dea9c..4a822f4 100755 --- a/sos_sales/views/sos_sales_leads_view.xml +++ b/sos_sales/views/sos_sales_leads_view.xml @@ -8,7 +8,7 @@ - +