diff --git a/sos_installation_commision/__manifest__.py b/sos_installation_commision/__manifest__.py index b423c71..fb123d5 100755 --- a/sos_installation_commision/__manifest__.py +++ b/sos_installation_commision/__manifest__.py @@ -23,6 +23,7 @@ # always loaded 'data': [ 'security/ir.model.access.csv', + 'security/record_rules.xml', 'views/menu.xml', 'views/sos_travel_plan_view.xml', 'views/sos_travel_reimbursement_bills_view.xml', diff --git a/sos_installation_commision/models/__pycache__/sos_travel_plan.cpython-310.pyc b/sos_installation_commision/models/__pycache__/sos_travel_plan.cpython-310.pyc index 6cde4b9..7db9c07 100644 Binary files a/sos_installation_commision/models/__pycache__/sos_travel_plan.cpython-310.pyc and b/sos_installation_commision/models/__pycache__/sos_travel_plan.cpython-310.pyc differ diff --git a/sos_installation_commision/models/sos_travel_plan.py b/sos_installation_commision/models/sos_travel_plan.py index 59b44e9..312c99e 100755 --- a/sos_installation_commision/models/sos_travel_plan.py +++ b/sos_installation_commision/models/sos_travel_plan.py @@ -44,6 +44,7 @@ class sos_travel_plan(models.Model): ] ) prepared_by_name = fields.Many2one('res.users', string='Prepared by') + reporting_to = fields.Many2one('res.users',related="prepared_by_name.reporting_to", string='Prepared by') prepared_by_image = fields.Image(related="prepared_by_name.signature_image",string='Prepared by Sign',readonly=True) prepared_on = fields.Datetime(string="Approved On") dept_in_charge_name = fields.Many2one('res.users', string='Department In-Charge') diff --git a/sos_installation_commision/security/record_rules.xml b/sos_installation_commision/security/record_rules.xml new file mode 100644 index 0000000..1e44a4d --- /dev/null +++ b/sos_installation_commision/security/record_rules.xml @@ -0,0 +1,34 @@ + + + + + Travel Plan: Own Records - Read Access + + [(1, '=', 1)] + + + + + + + + Travel Plan: All Records - Read Access + +[ +'|', +('create_uid', '=', user.id), +('reporting_to', '=', user.id) +] + + + + + + + + diff --git a/sos_installation_commision/views/menu.xml b/sos_installation_commision/views/menu.xml index 87f4134..98e1a4d 100755 --- a/sos_installation_commision/views/menu.xml +++ b/sos_installation_commision/views/menu.xml @@ -1,14 +1,10 @@ - - - - - - - - + + + + \ No newline at end of file diff --git a/sos_installation_commision/views/sos_travel_plan_view.xml b/sos_installation_commision/views/sos_travel_plan_view.xml index 8ddfd48..512fa6f 100755 --- a/sos_installation_commision/views/sos_travel_plan_view.xml +++ b/sos_installation_commision/views/sos_travel_plan_view.xml @@ -230,6 +230,6 @@ name="action_report_travel_plan"> Print + action="action_travel_plan_list" groups="sos_inventory.sos_ce_user,sos_inventory.sos_ce_head,sos_inventory.sos_finance_user,sos_inventory.sos_management_user,sos_inventory.sos_hr_user,sos_inventory.sos_sales_user,sos_inventory.sos_qc_user,sos_inventory.sos_production_user,sos_inventory.sos_qc_user,sos_inventory.sos_qa_user,sos_inventory.sos_rd_user"/> diff --git a/sos_inventory/__manifest__.py b/sos_inventory/__manifest__.py index f813946..c54744d 100755 --- a/sos_inventory/__manifest__.py +++ b/sos_inventory/__manifest__.py @@ -80,6 +80,7 @@ 'views/sos_serviceprovider_report.xml', 'views/sos_master_customer_property.xml', 'views/sos_service_call_log_report.xml', + 'views/sos_transfer_challan_return_from_customer_view.xml', 'wizard/sfg_bom_bulk_upload_view.xml', 'wizard/mon_bulk_upload_view.xml', 'wizard/missing_component_wizard.xml', diff --git a/sos_inventory/models/__init__.py b/sos_inventory/models/__init__.py index 104356e..fa11e2f 100755 --- a/sos_inventory/models/__init__.py +++ b/sos_inventory/models/__init__.py @@ -56,4 +56,5 @@ from . import sos_mat_outsourcing_vendor_register from . import sos_inventory_customers from . import sos_master_customer_property from . import sos_service_call_log_report -from . import sos_inhouse_validation_reports_files \ No newline at end of file +from . import sos_inhouse_validation_reports_files +from . import sos_transfer_challan_return_from_customer \ 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 e6a7e9c..95c81f2 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_common_scripts.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_common_scripts.cpython-310.pyc index 16e163d..24caafa 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_dock_audit.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_dock_audit.cpython-310.pyc index 8e3b11a..0b3a6b5 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_bom.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_fg_bom.cpython-310.pyc index 35db6fd..18b37ef 100644 Binary files a/sos_inventory/models/__pycache__/sos_fg_bom.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_fg_bom.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_material.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_material.cpython-310.pyc index 7a8bb91..3db2d01 100644 Binary files a/sos_inventory/models/__pycache__/sos_material.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_material.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_min.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_min.cpython-310.pyc index cd3925c..2e4d3dc 100644 Binary files a/sos_inventory/models/__pycache__/sos_min.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_min.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 c85dc3c..e6baf1d 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_return_fir.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_return_fir.cpython-310.pyc index fc06462..8b340a4 100644 Binary files a/sos_inventory/models/__pycache__/sos_return_fir.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_return_fir.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 023cb1c..0bd559c 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_bom.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_sfg_bom.cpython-310.pyc index 9172a53..975df11 100644 Binary files a/sos_inventory/models/__pycache__/sos_sfg_bom.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_sfg_bom.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_sfg_quote_generation.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_sfg_quote_generation.cpython-310.pyc index 8fa8447..ab6598e 100644 Binary files a/sos_inventory/models/__pycache__/sos_sfg_quote_generation.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_sfg_quote_generation.cpython-310.pyc differ diff --git a/sos_inventory/models/__pycache__/sos_transfer_challan.cpython-310.pyc b/sos_inventory/models/__pycache__/sos_transfer_challan.cpython-310.pyc index aac0c0d..fbe8824 100644 Binary files a/sos_inventory/models/__pycache__/sos_transfer_challan.cpython-310.pyc and b/sos_inventory/models/__pycache__/sos_transfer_challan.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 2070291..8276c38 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_common_scripts.py b/sos_inventory/models/sos_common_scripts.py index d287bce..b6b30f6 100755 --- a/sos_inventory/models/sos_common_scripts.py +++ b/sos_inventory/models/sos_common_scripts.py @@ -306,7 +306,8 @@ class Sequence_Generator(models.AbstractModel): record.write({field_name: current_value + value_to_add}) return True return False -def extract_value(self, input_string): + @staticmethod + def extract_value(input_string): total = 0 items = input_string.split(",") # handles both single and comma-separated values diff --git a/sos_inventory/models/sos_dock_audit.py b/sos_inventory/models/sos_dock_audit.py index eadfd5d..1e55618 100755 --- a/sos_inventory/models/sos_dock_audit.py +++ b/sos_inventory/models/sos_dock_audit.py @@ -417,36 +417,36 @@ class sos_dock_audit_Line_Material(models.Model): @api.model def create(self, vals): #print(f" First line in create ") - component_id = vals.get('component_id') + #component_id = vals.get('component_id') ref_id = vals.get('ref_id') - sequence_util = self.env['sos_common_scripts'] + #sequence_util = self.env['sos_common_scripts'] - if component_id and ref_id: - # Search for an existing line with the same component_id and ref_id - existing = self.search([ - ('component_id', '=', component_id), - ('ref_id', '=', ref_id) - ], limit=1) + # if component_id and ref_id: + # # Search for an existing line with the same component_id and ref_id + # existing = self.search([ + # ('component_id', '=', component_id), + # ('ref_id', '=', ref_id) + # ], limit=1) - if existing: - # Merge logic: add quantities - existing.singet_set_qty += vals.get('singet_set_qty', 1) - #existing.total_set += vals.get('total_set', 1) + # if existing: + # # Merge logic: add quantities + # existing.singet_set_qty += vals.get('singet_set_qty', 1) + # #existing.total_set += vals.get('total_set', 1) - previousval = 0 - if vals.get('ref_id'): - parent = self.env['sos_dock_audit'].browse(vals['ref_id']) - existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) - previousval = sequence_util.extract_value(existing.total_phase_count) + # previousval = 0 + # if vals.get('ref_id'): + # parent = self.env['sos_dock_audit'].browse(vals['ref_id']) + # existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) + # previousval = sequence_util.extract_value(existing.total_phase_count) - if existing.total_phase_count: - #existing.total_phase_count = str(existing.total_phase_count)+","+str(existing.singet_set_qty * existing.total_set) - existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) - else: - existing.total_phase_count = str(existing.singet_set_qty * existing.total_set) + # if existing.total_phase_count: + # #existing.total_phase_count = str(existing.total_phase_count)+","+str(existing.singet_set_qty * existing.total_set) + # existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) + # else: + # existing.total_phase_count = str(existing.singet_set_qty * existing.total_set) - # quantity and total_price are computed, so they'll auto update - return existing + # # quantity and total_price are computed, so they'll auto update + # return existing #print(f"Initial : singet {vals.get('singet_set_qty', 1)}") #print(f"Initial : total {vals.get('total_set', 1)}") @@ -508,37 +508,37 @@ class sos_dock_audit_Line_sfg(models.Model): @api.model def create(self, vals): #print(f" First line in create ") - component_id = vals.get('component_id') + #component_id = vals.get('component_id') ref_id = vals.get('ref_id') - sequence_util = self.env['sos_common_scripts'] + #sequence_util = self.env['sos_common_scripts'] - if component_id and ref_id: - # Search for an existing line with the same component_id and ref_id - existing = self.search([ - ('component_id', '=', component_id), - ('ref_id', '=', ref_id) - ], limit=1) + # if component_id and ref_id: + # # Search for an existing line with the same component_id and ref_id + # existing = self.search([ + # ('component_id', '=', component_id), + # ('ref_id', '=', ref_id) + # ], limit=1) - if existing: - # Merge logic: add quantities - existing.singet_set_qty += vals.get('singet_set_qty', 1) - #existing.total_set += vals.get('total_set', 1) + # if existing: + # # Merge logic: add quantities + # existing.singet_set_qty += vals.get('singet_set_qty', 1) + # #existing.total_set += vals.get('total_set', 1) - previousval = 0 - if vals.get('ref_id'): + # previousval = 0 + # if vals.get('ref_id'): - parent = self.env['sos_dock_audit'].browse(vals['ref_id']) - existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) - previousval = sequence_util.extract_value(existing.total_phase_count) + # parent = self.env['sos_dock_audit'].browse(vals['ref_id']) + # existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) + # previousval = sequence_util.extract_value(existing.total_phase_count) - if existing.total_phase_count: - #existing.total_phase_count = str(existing.total_phase_count)+","+str(existing.singet_set_qty * existing.total_set) - existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) - else: - existing.total_phase_count = str(existing.singet_set_qty * existing.total_set) + # if existing.total_phase_count: + # #existing.total_phase_count = str(existing.total_phase_count)+","+str(existing.singet_set_qty * existing.total_set) + # existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) + # else: + # existing.total_phase_count = str(existing.singet_set_qty * existing.total_set) - # quantity and total_price are computed, so they'll auto update - return existing + # # quantity and total_price are computed, so they'll auto update + # return existing singleset = vals.get('singet_set_qty', 1) total_set = vals.get('total_set', 1) @@ -603,34 +603,34 @@ class sos_dock_audit_Line_fg(models.Model): @api.model def create(self, vals): - component_id = vals.get('component_id') + #component_id = vals.get('component_id') ref_id = vals.get('ref_id') - sequence_util = self.env['sos_common_scripts'] + #sequence_util = self.env['sos_common_scripts'] - if component_id and ref_id: - # Search for an existing line with the same component_id and ref_id - existing = self.search([ - ('component_id', '=', component_id), - ('ref_id', '=', ref_id) - ], limit=1) + # if component_id and ref_id: + # # Search for an existing line with the same component_id and ref_id + # existing = self.search([ + # ('component_id', '=', component_id), + # ('ref_id', '=', ref_id) + # ], limit=1) - if existing: - # Merge logic: add quantities - existing.singet_set_qty += vals.get('singet_set_qty', 1) - #existing.total_set += vals.get('total_set', 1) - previousval = 0 - if vals.get('ref_id'): - parent = self.env['sos_dock_audit'].browse(vals['ref_id']) - existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) - previousval = sequence_util.extract_value(existing.total_phase_count) - if existing.total_phase_count: - #print(f" TT :: {existing.total_phase_count} ") - existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) - else: - existing.total_phase_count = "P"+str(existing.phase)+"-"+str(existing.singet_set_qty * existing.total_set) + ##if existing: + # # Merge logic: add quantities + # existing.singet_set_qty += vals.get('singet_set_qty', 1) + # #existing.total_set += vals.get('total_set', 1) + ## previousval = 0 + ## if vals.get('ref_id'): + ## parent = self.env['sos_dock_audit'].browse(vals['ref_id']) + # existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) + ## previousval = sequence_util.extract_value(existing.total_phase_count) + ## if existing.total_phase_count: + # #print(f" TT :: {existing.total_phase_count} ") + ## existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) + # else: + # existing.total_phase_count = "P"+str(existing.phase)+"-"+str(existing.singet_set_qty * existing.total_set) - # quantity and total_price are computed, so they'll auto update - return existing + # # quantity and total_price are computed, so they'll auto update + #return existing singleset = vals.get('singet_set_qty', 1) total_set = vals.get('total_set', 1) @@ -670,7 +670,8 @@ class sos_dock_audit_MiscellaneousCost(models.Model): @api.model def create(self, vals): - if vals.get('ref_id'): + ref_id = vals.get('ref_id') + if ref_id: parent = self.env['sos_dock_audit'].browse(vals['ref_id']) vals['phase'] = (parent.phase_version+1) or 0 return super().create(vals) @@ -718,39 +719,39 @@ class sos_dock_audit_Material_installationkit(models.Model): @api.model def create(self, vals): - component_id = vals.get('component_id') + #component_id = vals.get('component_id') ref_id = vals.get('ref_id') - sequence_util = self.env['sos_common_scripts'] + #sequence_util = self.env['sos_common_scripts'] - if component_id and ref_id: - # Search for an existing line with the same component_id and ref_id - existing = self.search([ - ('component_id', '=', component_id), - ('ref_id', '=', ref_id) - ], limit=1) + # if component_id and ref_id: + # # Search for an existing line with the same component_id and ref_id + # existing = self.search([ + # ('component_id', '=', component_id), + # ('ref_id', '=', ref_id) + # ], limit=1) - if existing: - # Merge logic: add quantities - existing.singet_set_qty += vals.get('singet_set_qty', 1) - #existing.total_set += vals.get('total_set', 1) + # if existing: + # # Merge logic: add quantities + # existing.singet_set_qty += vals.get('singet_set_qty', 1) + # #existing.total_set += vals.get('total_set', 1) - previousval = 0 - if vals.get('ref_id'): - parent = self.env['sos_dock_audit'].browse(vals['ref_id']) - existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) - #print(f" Total_phase_count :{existing.total_phase_count}") - #print(f" Extract Value :{self.extract_value(existing.total_phase_count)}") - previousval = sequence_util.extract_value(existing.total_phase_count) - #print(f" : previousval : {previousval}") - if existing.total_phase_count: - #existing.total_phase_count = str(existing.total_phase_count)+","+str(existing.singet_set_qty * existing.total_set) - #print(f" Total Count : {existing.singet_set_qty * existing.total_set}") - existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) - else: - existing.total_phase_count = str(existing.singet_set_qty * existing.total_set) + # previousval = 0 + # if vals.get('ref_id'): + # parent = self.env['sos_dock_audit'].browse(vals['ref_id']) + # existing.phase = str(existing.phase) + "," + str(parent.phase_version + 1) + # #print(f" Total_phase_count :{existing.total_phase_count}") + # #print(f" Extract Value :{self.extract_value(existing.total_phase_count)}") + # previousval = sequence_util.extract_value(existing.total_phase_count) + # #print(f" : previousval : {previousval}") + # if existing.total_phase_count: + # #existing.total_phase_count = str(existing.total_phase_count)+","+str(existing.singet_set_qty * existing.total_set) + # #print(f" Total Count : {existing.singet_set_qty * existing.total_set}") + # existing.total_phase_count = str(existing.total_phase_count)+","+"P"+str(parent.phase_version + 1 or '') +"-"+str((existing.singet_set_qty * existing.total_set)- int(previousval)) + # else: + # existing.total_phase_count = str(existing.singet_set_qty * existing.total_set) - # quantity and total_price are computed, so they'll auto update - return existing + # # quantity and total_price are computed, so they'll auto update + # return existing singleset = vals.get('singet_set_qty', 1) total_set = vals.get('total_set', 1) diff --git a/sos_inventory/models/sos_fg_bom.py b/sos_inventory/models/sos_fg_bom.py index abb7397..ea4c557 100755 --- a/sos_inventory/models/sos_fg_bom.py +++ b/sos_inventory/models/sos_fg_bom.py @@ -73,6 +73,7 @@ class SOS_Fg_Bom(models.Model): def _compute_overall_total(self): for record in self: record.overall_total = record.sfg_cost + record.component_cost + record.miscellaneous_cost + record._sync_unit_price_with_fg() @api.depends('sfg_bom_line_ids.unit_price') def _compute_component_cost(self): diff --git a/sos_inventory/models/sos_material.py b/sos_inventory/models/sos_material.py index 1d220b4..9ea2156 100755 --- a/sos_inventory/models/sos_material.py +++ b/sos_inventory/models/sos_material.py @@ -66,6 +66,18 @@ class SOS_Material(models.Model): record.name or record.part_no ) ) + def write(self, vals): + unit_price_changed = 'unit_price' in vals + res = super().write(vals) + + if unit_price_changed: + bom_lines = self.env['sos_sfg_bom_line'].search([ + ('primary_component_id', 'in', self.ids) + ]) + bom_ids = bom_lines.mapped('bom_id') + for bom in bom_ids: + bom._sync_unit_price_with_sfg() + return res @api.depends('unit_price','inhand_stock_qty') def _compute_stock_val(self): for val in self: diff --git a/sos_inventory/models/sos_min.py b/sos_inventory/models/sos_min.py index 01347e2..d2efc37 100755 --- a/sos_inventory/models/sos_min.py +++ b/sos_inventory/models/sos_min.py @@ -13,7 +13,7 @@ class sos__min(models.Model): min_no = fields.Char(string="MIN/SIN/FIN No", readonly= True, required= True, default=lambda self: self._generate_id()) min_date = fields.Date(string="MIN/SIN/FIN Date",required=True) - mon_no = fields.Many2one('sos_mon',string="MON/SON/FON No") + mon_no = fields.Many2one('sos_mon',string="MON/SON/FON No",readonly= True) mon_date = fields.Date(string="MON/SON/FON Date") ref_no = fields.Char(string="Purpose") indent_ref_no = fields.Many2one('sos_fg_plan',string="Indent Reference No") @@ -31,7 +31,7 @@ class sos__min(models.Model): line_ids_material = fields.One2many('sos_min_line_material', 'min_id', string="Materials",copy=True) line_ids_sfg = fields.One2many('sos_min_line_sfg', 'min_id', string="Semi-Finished Goods",copy=True) line_ids_fg = fields.One2many('sos_min_line_fg', 'min_id', string="Finished Goods",copy=True) - mon_created_by = fields.Many2one('res.users', string='Requested By') + mon_created_by = fields.Many2one('res.users', string='Requested By',readonly= True) reporting_to = fields.Many2one('res.users', string='Reporting To') issued_to = fields.Many2one('res.users', string='Issued To') status = fields.Selection([ ('open', 'Open'),('close', 'Closed')], default='open' , string="Status") diff --git a/sos_inventory/models/sos_mrn.py b/sos_inventory/models/sos_mrn.py index f8820e7..e4835bf 100755 --- a/sos_inventory/models/sos_mrn.py +++ b/sos_inventory/models/sos_mrn.py @@ -115,10 +115,18 @@ class sos__mrn(models.Model): def action_report_esign_btn1(self): sequence_util = self.env['sos_common_scripts'] splitted=self.order_type.split(",") + if self.return_type == "customer": + sequence_util = self.env['sos_common_scripts'] + tc_no = sequence_util.generate_sequence('sos_transfer_challan_return_from_customer','TC', 'tc_no') + customer_return_record = self.env['sos_transfer_challan_return_from_customer'].create({ + 'tc_no': tc_no, + 'mrn_ref_no':self.id + }) for eachone in splitted: if eachone == "Material": for item in self.line_ids_material: component = self.env['sos_material'].browse(item.component_id.id) + if item.qa_decision == "to_stores": current_qty = getattr(component, 'inhand_stock_qty', 0) new_qty = current_qty + item.quantity @@ -137,6 +145,16 @@ class sos__mrn(models.Model): 'mrn_id':self.id }) + if customer_return_record and self.return_type == "customer" and item.qa_decision != "to_stores": + create_vals = { + 'tc_id': customer_return_record.id, + 'item_name': component.name, + 'item_type': 'Material', + 'return_incoming_doc_ref':iqi_record.id if iqi_record else None + } + new_line = self.env['sos_transfer_challan_return_from_customer_lines'].create(create_vals) + + # Email part body_html = f"""

Below Return-IQI is waiting for your Approval

@@ -147,6 +165,7 @@ class sos__mrn(models.Model): elif eachone == "SFG": for item in self.line_ids_sfg: component = self.env['sos_sfg'].browse(item.component_id.id) + if item.qa_decision == "to_stores": current_qty = getattr(component, 'inhand_stock_qty', 0) new_qty = current_qty + item.quantity @@ -174,9 +193,19 @@ class sos__mrn(models.Model): send_email = self.env['sos_common_scripts'] send_email.send_group_email(self.env,'sos_return_iqi',iqi_record.id,"deenalaura.m@sosaley.in","Return-IQI Inspection Request",body_html,'sos_inventory.sos_qc_user') # Email part ends + if customer_return_record and self.return_type == "customer" and item.qa_decision != "to_stores": + create_vals = { + 'tc_id': customer_return_record.id, + 'item_name': component.name, + 'item_type': 'SFG', + 'return_incoming_doc_ref':iqi_record.id if iqi_record else None + } + new_line = self.env['sos_transfer_challan_return_from_customer_lines'].create(create_vals) + else: for item in self.line_ids_fg: component = self.env['sos_fg'].browse(item.component_id.id) + if item.qa_decision == "to_stores": current_qty = getattr(component, 'inhand_stock_qty', 0) new_qty = current_qty + item.quantity @@ -213,7 +242,15 @@ class sos__mrn(models.Model): send_email = self.env['sos_common_scripts'] send_email.send_group_email(self.env,'sos_return_fir',return_fir_record.id,"deenalaura.m@sosaley.in","Return-BRR Inspection Request",body_html,'sos_inventory.sos_qc_user') # Email part ends - + if customer_return_record and self.return_type == "customer" and item.qa_decision != "to_stores": + create_vals = { + 'tc_id': customer_return_record.id, + 'item_name': component.name, + 'item_type': 'FG', + 'return_fg_incoming_doc_ref':return_fir_record.id if return_fir_record else None + } + new_line = self.env['sos_transfer_challan_return_from_customer_lines'].create(create_vals) + return sequence_util.action_assign_signature( self, 'stores_approved_by', diff --git a/sos_inventory/models/sos_return_fir.py b/sos_inventory/models/sos_return_fir.py index a7ef543..dd9434b 100755 --- a/sos_inventory/models/sos_return_fir.py +++ b/sos_inventory/models/sos_return_fir.py @@ -75,6 +75,8 @@ class Return_FIR_BRR(models.Model): 'return_fg_incoming_doc_ref':self.id, 'department':'Quality Control' }) + if ncmr_record: + self.ncmr_ref = ncmr_record.id ncmr_body_html = f"""

Below NCMR is waiting for your Inspection

""" diff --git a/sos_inventory/models/sos_sfg.py b/sos_inventory/models/sos_sfg.py index 2f293bd..88b25b8 100755 --- a/sos_inventory/models/sos_sfg.py +++ b/sos_inventory/models/sos_sfg.py @@ -53,6 +53,17 @@ class SOS_SFG(models.Model): ) last_batch_no = fields.Char(string="Batch No") last_serial_no = fields.Integer(string="Last Serial No") + def write(self, vals): + assembling_changed = 'assembling_charges' in vals + res = super().write(vals) + + if assembling_changed: + for sfg in self: + bom_records = self.env['sos_sfg_bom'].search([('name', '=', sfg.id)]) + for bom in bom_records: + bom._compute_overall_total() # If you want to force recompute manually + bom._sync_unit_price_with_sfg() + return res @api.onchange('category') def onchange_category(self): if self.category: @@ -62,7 +73,6 @@ class SOS_SFG(models.Model): } prefix = prefix_map.get(self.category, "SO") - # Search all records starting with this prefix matching_records = self.env['sos_sfg'].search([ ('code_no', 'like', prefix + '%') ]) diff --git a/sos_inventory/models/sos_sfg_bom.py b/sos_inventory/models/sos_sfg_bom.py index c6f1aef..b46e5fc 100755 --- a/sos_inventory/models/sos_sfg_bom.py +++ b/sos_inventory/models/sos_sfg_bom.py @@ -38,8 +38,10 @@ class SOS_Sfg_Bom(models.Model): 'material_code' : 'Material Code', 'primary_component_id': 'Material Name', 'quantity': 'Quantity', - 'location':'Location', - 'inhand_stock_qty':'Inhand Qty' + 'inhand_stock_qty':'Inhand Qty', + 'uom':'UOM', + 'unit_price':'Unit Price', + 'total_cost':'Total' } self._write_sheet(workbook, 'Materials', self.sfg_bom_line_ids, headers) @@ -62,12 +64,26 @@ class SOS_Sfg_Bom(models.Model): } def _write_sheet(self, workbook, sheet_name, line_records, headers): - """Write data to each sheet based on specified fields and custom headers.""" worksheet = workbook.add_worksheet(sheet_name) - for col, header in enumerate(headers.values()): - worksheet.write(0, col, header) + + number_format = workbook.add_format({'num_format': '#,##0.00'}) + bold_format = workbook.add_format({'bold': True}) + name_value = getattr(self, 'name', False) + worksheet.write(0, 0, 'Name',bold_format) # Label for the first value + worksheet.write(0, 1, name_value.display_name if name_value else '') # Handle Many2one field - for row, record in enumerate(line_records, start=1): + assembling_charges = getattr(self, 'assembling_charges', 0.0) + worksheet.write(1, 0, 'Assembly Charges',bold_format) # Label for the second value + worksheet.write(1, 1, assembling_charges, number_format) # Assume numeric (float) + + overall_total = getattr(self, 'overall_total', 0.0) + worksheet.write(2, 0, 'Overall Cost (Including assembling charges)',bold_format) # Label for the third value + worksheet.write(2, 1, overall_total, number_format) # Assume numeric (float) + + for col, header in enumerate(headers.values()): + worksheet.write(4, col, header,bold_format) + + for row, record in enumerate(line_records, start=5): for col, field in enumerate(headers.keys()): value = getattr(record, field, '') if isinstance(value, models.Model): @@ -123,7 +139,7 @@ class SOS_Sfg_Bom_Line(models.Model): _description = 'BOM Lines of Semi-Finished Goods' bom_id = fields.Many2one('sos_sfg_bom', string="SFG BOM Reference") - fg_bom_id = fields.Many2one('sos_fg_bom', string="SFG BOM Reference") + fg_bom_id = fields.Many2one('sos_fg_bom', string="FG BOM Reference") primary_component_id = fields.Many2one('sos_material', string='Part No', required=True) location = fields.Char(related="primary_component_id.location") inhand_stock_qty = fields.Float(related="primary_component_id.inhand_stock_qty") diff --git a/sos_inventory/models/sos_sfg_quote_generation.py b/sos_inventory/models/sos_sfg_quote_generation.py index e940e6b..0335a9b 100755 --- a/sos_inventory/models/sos_sfg_quote_generation.py +++ b/sos_inventory/models/sos_sfg_quote_generation.py @@ -162,6 +162,13 @@ class SOS_SFG_Quote_Generation_Line(models.Model): # final_supplier3_qty = fields.Integer(string='Qty') best_supplier = fields.Many2one('sos_service_providers', string="Best Deal", compute='_compute_best_supplier', store=True) available_suppliers = fields.Many2many('sos_service_providers', compute='_compute_available_suppliers') + remarks = fields.Char(string="Remarks") + @api.onchange('required_qty', 'inprogress_qty', 'status') + def _onchange_check_inprogress_qty(self): + for rec in self: + if (rec.inprogress_qty >= rec.required_qty and rec.required_qty != 0) or rec.status == 'close': + rec.inprogress_qty = 0 + @api.depends('required_qty', 'inprogress_qty') def _compute_from_ir(self): for record in self: diff --git a/sos_inventory/models/sos_transfer_challan.py b/sos_inventory/models/sos_transfer_challan.py index 0a1f34e..c4bf71c 100755 --- a/sos_inventory/models/sos_transfer_challan.py +++ b/sos_inventory/models/sos_transfer_challan.py @@ -180,7 +180,7 @@ class TC_Model_Line(models.Model): 'fir_date':self.production_to_qc_transfer_on, 'batch_size':self.production_to_qc_transfer_qty, 'sampling_size':self.production_to_qc_transfer_qty, - 'plan_ref_no':plan_ref_no.id, + 'plan_ref_no':plan_ref_no.id if plan_ref_no else '', 'batch_No':self.batch_no, 'fir_no': sequence_util.generate_sequence('sos_fir_brr','BRR', 'fir_no'), @@ -204,7 +204,7 @@ class TC_Model_Line(models.Model): # Email part ends self.production_to_qc_transfer_by = self.env.user.id sequence_util = self.env['sos_common_scripts'] - if self.tc_id.fg_option: + if self.tc_id.fg_option and self.tc_id.plan_ref_no: week_number = sequence_util.calculate_week_number( self.tc_id.indent_start_date, self.tc_id.indent_target_date, diff --git a/sos_inventory/models/sos_transfer_challan_return_from_customer.py b/sos_inventory/models/sos_transfer_challan_return_from_customer.py new file mode 100755 index 0000000..2b93b59 --- /dev/null +++ b/sos_inventory/models/sos_transfer_challan_return_from_customer.py @@ -0,0 +1,81 @@ +from odoo import models, fields, api +from datetime import date,datetime +from odoo.exceptions import ValidationError,UserError +import re + +class SOS_TC_Return_Customer(models.Model): + _name = 'sos_transfer_challan_return_from_customer' + _description = 'Transfer Challan' + _rec_name="tc_no" + _order = 'tc_no desc' + + + tc_no = fields.Char(string="TC No",default=lambda self: self._generate_id(),readonly= True, required= True) + line_ids = fields.One2many('sos_transfer_challan_return_from_customer_lines', 'tc_id', string="Finished Goods",copy=True, ondelete='cascade') + mrn_ref_no = fields.Many2one('sos_mrn',string="Return Note Reference",readonly= True) + def _generate_id(self): + sequence_util = self.env['sos_common_scripts'] + return sequence_util.generate_sequence('sos_transfer_challan_return_from_customer','TC', 'tc_no') + +class SOS_TC_Return_CustomerLines(models.Model): + _name = 'sos_transfer_challan_return_from_customer_lines' + _description = 'tc Lines' + + tc_id = fields.Many2one('sos_transfer_challan_return_from_customer') + item_name = fields.Char(string="Item Name") + item_type = fields.Selection([ ('FG', 'FG'),('SFG', 'SFG'),('Material', 'Material')], + default='FG' , string="Type") + + return_incoming_doc_ref = fields.Many2one('sos_return_iqi',string="IQI Ref No",readonly=True) + return_fg_incoming_doc_ref = fields.Many2one('sos_return_fir',string="Return FIR ref",readonly=True) + combined_incoming_doc_ref = fields.Reference( + selection=[ + ('sos_return_iqi', 'Return IQI Ref No'), + ('sos_return_fir', 'FIR Ref') + ], + string="Incoming Document Reference", + compute="_compute_combined_incoming_doc_ref", + store=False + ) + stores_handovered_by = fields.Many2one('res.users', string='Stores Handovered By',related="tc_id.mrn_ref_no.stores_approved_by") + stores_handovered_on = fields.Datetime(string="Stores Handovered On",related="tc_id.mrn_ref_no.stores_approved_on") + qc_tested_by = fields.Many2one('res.users', string='QC Tested By',compute="_compute_qc_tested_by") + qc_tested_on = fields.Datetime(string="QC Tested On") + received_qty = fields.Integer(string="Received Quantity") + approved_qty = fields.Integer(string="Approved Quantity") + rejected_qty = fields.Integer(string="Rejected Quantity") + ncmr_ref = fields.Many2one('sos_ncmr',string="NCMR Ref(If any)") + stores_received_by = fields.Many2one('res.users', string='Stores Received By') + stores_received_on = fields.Datetime(string="Stores Received On") + + @api.depends('item_type','return_incoming_doc_ref','return_fg_incoming_doc_ref') + def _compute_qc_tested_by(self): + for record in self: + if record.item_type == "FG": + record.qc_tested_by = record.return_fg_incoming_doc_ref.qc_by_name + record.qc_tested_on = record.return_fg_incoming_doc_ref.qc_tested_on + record.qc_tested_on = record.return_fg_incoming_doc_ref.qc_tested_on + record.received_qty = record.return_fg_incoming_doc_ref.batch_size + record.approved_qty = record.return_fg_incoming_doc_ref.approved_qty + record.rejected_qty = record.return_fg_incoming_doc_ref.rejected_qty + record.ncmr_ref = record.return_fg_incoming_doc_ref.ncmr_ref + record.stores_received_by = record.return_fg_incoming_doc_ref.stores_received_by + record.stores_received_on = record.return_fg_incoming_doc_ref.stores_received_on + else: + record.qc_tested_by = record.return_incoming_doc_ref.qc_by_name + record.qc_tested_on = record.return_incoming_doc_ref.qc_tested_on + record.received_qty = record.return_incoming_doc_ref.received_qty + record.approved_qty = record.return_incoming_doc_ref.approved_qty + record.rejected_qty = record.return_incoming_doc_ref.rejected_qty + record.ncmr_ref = record.return_incoming_doc_ref.ncmr_ref + record.stores_received_by = record.return_incoming_doc_ref.stores_received_by + record.stores_received_on = record.return_incoming_doc_ref.stores_received_on + @api.depends('return_incoming_doc_ref','return_fg_incoming_doc_ref') + def _compute_combined_incoming_doc_ref(self): + for record in self: + if record.return_incoming_doc_ref: + record.combined_incoming_doc_ref = f'sos_return_iqi,{record.return_incoming_doc_ref.id}' + elif record.return_fg_incoming_doc_ref: + record.combined_incoming_doc_ref = f'sos_return_fir,{record.return_fg_incoming_doc_ref.id}' + else: + record.combined_incoming_doc_ref = False \ No newline at end of file diff --git a/sos_inventory/models/sos_wo.py b/sos_inventory/models/sos_wo.py index 4bccfd4..45167a7 100755 --- a/sos_inventory/models/sos_wo.py +++ b/sos_inventory/models/sos_wo.py @@ -61,7 +61,7 @@ class sos__wo(models.Model): total_qty = fields.Integer(string='Total Line Items Count', compute='_compute_total_qty', store=True) received_qty = fields.Integer(string='Received Line Items Count', store=True) payment_method=fields.Selection([('neft', 'NEFT'),('credit', 'Credit'),('credit_card', 'Credit Card')],string="Payment Method") - wo_status = fields.Selection([ ('open', 'Open'),('close', 'Closed')], default='open' , string="Status") + wo_status = fields.Selection([ ('amend', 'Amended'),('open', 'Open'),('close', 'Closed')], default='open' , string="Status") progress = fields.Float(string="Completion Percentage", compute='_compute_progress', store=True) dc_no = fields.Many2one('sos_dc',string="DC Reference No", readonly= True) stores_approved_by = fields.Many2one('res.users', string='Manager Approval By') @@ -82,7 +82,20 @@ class sos__wo(models.Model): # record = super(sos__wo, self).create(vals) # record.action_esign_btn() # return record - + def action_amend(self): + active_ids = self.env.context.get('active_ids', []) + records = self.browse(active_ids) + for record in records: + record.wo_status = 'amend' + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Amended', + 'message': 'The selected record(s) have been amended.', + 'sticky': False, + } + } def action_esign_btn(self): sequence_util = self.env['sos_common_scripts'] sequence_util.action_assign_signature( @@ -143,7 +156,13 @@ class sos__wo(models.Model): record.progress = (progress_sum / line_count) * 100 record.wo_status = 'close' if record.progress == 100 else 'open' - + @api.onchange('progress') + def _onchange_progress_status(self): + for record in self: + if record.progress == 100: + record.wo_status = 'close' + else: + record.wo_status = 'open' # @api.depends('total_qty', 'received_qty') # def _compute_progress(self): # print(self.received_qty,self.total_qty) diff --git a/sos_inventory/security/ir.model.access.csv b/sos_inventory/security/ir.model.access.csv index 769e66a..d6e7c17 100755 --- a/sos_inventory/security/ir.model.access.csv +++ b/sos_inventory/security/ir.model.access.csv @@ -193,4 +193,7 @@ access_sos_transfer_challan_summary_lines,sos_transfer_challan_summary_lines acc access_material_backup_export,material_backup_export access,model_material_backup_export,base.group_user,1,1,1,1 access_ncmr_report_wizard,ncmr_report_wizard access,model_ncmr_report_wizard,base.group_user,1,1,1,1 access_sos_parameter_fir,sos_parameter_fir access,model_sos_parameter_fir,base.group_user,1,1,1,1 +access_sos_transfer_challan_return_from_customer_lines,sos_transfer_challan_return_from_customer_lines access,model_sos_transfer_challan_return_from_customer_lines,base.group_user,1,1,1,1 +access_sos_transfer_challan_return_from_customer,sos_transfer_challan_return_from_customer access,model_sos_transfer_challan_return_from_customer,base.group_user,1,1,1,1 + diff --git a/sos_inventory/views/sos_dock_audit_view.xml b/sos_inventory/views/sos_dock_audit_view.xml index aa17bad..f2d52c0 100755 --- a/sos_inventory/views/sos_dock_audit_view.xml +++ b/sos_inventory/views/sos_dock_audit_view.xml @@ -46,7 +46,7 @@ Print --> + confirm="Are you sure to make revision for next phase?" invisible="not top_management_name">Make Revision

Dock Audit




diff --git a/sos_inventory/views/sos_sfg_quote_generation.xml b/sos_inventory/views/sos_sfg_quote_generation.xml index 733cdf2..10cf786 100755 --- a/sos_inventory/views/sos_sfg_quote_generation.xml +++ b/sos_inventory/views/sos_sfg_quote_generation.xml @@ -27,7 +27,7 @@ - + diff --git a/sos_inventory/views/sos_transfer_challan_return_from_customer_view.xml b/sos_inventory/views/sos_transfer_challan_return_from_customer_view.xml new file mode 100755 index 0000000..641e637 --- /dev/null +++ b/sos_inventory/views/sos_transfer_challan_return_from_customer_view.xml @@ -0,0 +1,115 @@ + + + + Transfer Challan + ir.actions.act_window + sos_transfer_challan_return_from_customer + tree,form + + + sos_transfer_challan_return_from_customer.view.tree + sos_transfer_challan_return_from_customer + + + + + + + + + + + + + + Form + sos_transfer_challan_return_from_customer + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
diff --git a/sos_inventory/views/sos_transfer_challan_view.xml b/sos_inventory/views/sos_transfer_challan_view.xml index 5b933c7..1283a1d 100755 --- a/sos_inventory/views/sos_transfer_challan_view.xml +++ b/sos_inventory/views/sos_transfer_challan_view.xml @@ -5,7 +5,7 @@ ir.actions.act_window sos_transfer_challan tree,form,kanban - {'group_by': 'plan_ref_no'} +

No Data @@ -245,7 +245,7 @@ If no 'to' is used, values will be stored as entered."/> diff --git a/sos_inventory/views/sos_wo_view.xml b/sos_inventory/views/sos_wo_view.xml index 078391c..c26475a 100755 --- a/sos_inventory/views/sos_wo_view.xml +++ b/sos_inventory/views/sos_wo_view.xml @@ -6,6 +6,15 @@ sos_wo tree,form,kanban + + 📝 Amend + + + code + + action = model.action_amend() + + sos_wo.search sos_wo @@ -38,7 +47,8 @@ - + + @@ -58,9 +68,11 @@ - + + - + +

Work Order




diff --git a/sos_sales/models/__pycache__/sos_case_diary.cpython-310.pyc b/sos_sales/models/__pycache__/sos_case_diary.cpython-310.pyc index 0309ae4..67bf31f 100644 Binary files a/sos_sales/models/__pycache__/sos_case_diary.cpython-310.pyc and b/sos_sales/models/__pycache__/sos_case_diary.cpython-310.pyc differ 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 7d1c742..9153736 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_builder.cpython-310.pyc b/sos_sales/models/__pycache__/sos_proposal_builder.cpython-310.pyc index bd8501a..0d45c1a 100755 Binary files a/sos_sales/models/__pycache__/sos_proposal_builder.cpython-310.pyc and b/sos_sales/models/__pycache__/sos_proposal_builder.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 5d5672d..dec46a8 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 36986a6..e84ce6c 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/__pycache__/sos_sales_action_plan.cpython-310.pyc b/sos_sales/models/__pycache__/sos_sales_action_plan.cpython-310.pyc index ff71298..48fe35c 100644 Binary files a/sos_sales/models/__pycache__/sos_sales_action_plan.cpython-310.pyc and b/sos_sales/models/__pycache__/sos_sales_action_plan.cpython-310.pyc differ diff --git a/sos_sales/models/sos_case_diary.py b/sos_sales/models/sos_case_diary.py index 6fb1d13..1ae5fae 100755 --- a/sos_sales/models/sos_case_diary.py +++ b/sos_sales/models/sos_case_diary.py @@ -67,7 +67,6 @@ class sos_case_diary(models.Model): ], string="Service Type") line_ids = fields.One2many('sos_case_diary_line', 'ref_id', string="Action") - accounts_line_ids = fields.One2many('sos_billing_collection', 'ref_id', string="Billed & Collected") status = fields.Selection([('open', 'Open'), ('close', 'Closed without Order'), ('close_order', 'Closed with Order') ],default='open',string='Status') @@ -81,15 +80,7 @@ class sos_case_diary(models.Model): po_copy = fields.Binary(string="PO Copy") po_copy_filename=fields.Char(string="PO DocumentFile Name") order_expected_on = fields.Date(string="Order Expected On") - line_ids_billed= fields.One2many( - 'sos_billing_collection', 'ref_id', - domain=[('action_status', '=', 'Billed')] - ) - line_ids_collected = fields.One2many( - 'sos_billing_collection', 'ref_id', - domain=[('action_status', '=', 'Collected')] - ) @api.depends('end_customer_name', 'quote_no') def _compute_display_name(self): for rec in self: @@ -704,68 +695,3 @@ class SosCaseTransferHistory(models.Model): ) return {'type': 'ir.actions.act_window_close'} -class SosBillingCollection(models.Model): - _name = 'sos_billing_collection' - _description = 'Billing & Collection Details' - - ref_id = fields.Many2one('sos_case_diary', ondelete="cascade") - customer_name = fields.Many2one( - 'sos_customers', - string="Customer Name", - related='ref_id.customer_name', - store=True, - readonly=True -) - sales_person = fields.Many2one( - 'res.users', - string='Sales Executive', related="ref_id.sales_person",store=True) - action_status = fields.Selection([ - ('Billed', 'Billed'), - ('Collected', 'Collected') - ], string='Action') - date_of_action = fields.Date(string="Date") - currency_id = fields.Many2one( - 'res.currency', - string='Currency', - default=lambda self: self.env['res.currency'].search([('name', '=', 'INR')], limit=1).id or False - ) - value = fields.Monetary(currency_field='currency_id',string="Value(In Lakhs)") - def _get_financial_year(self, ref_date=None): - ref_date = ref_date or date.today() - start_year = ref_date.year if ref_date.month >= 4 else ref_date.year - 1 - end_year = start_year + 1 - return f"FY {start_year}-{end_year}" - @api.model - def create(self, vals): - # Get date and convert to datetime if it's a string - date_of_action = vals.get('date_of_action') - if isinstance(date_of_action, str): - date_of_action = datetime.strptime(date_of_action, '%Y-%m-%d').date() - - fy = self._get_financial_year(date_of_action) - month_name = date_of_action.strftime('%B').lower() - action_status = vals.get('action_status', '').lower() - value = vals.get('value', 0.0) - - # Compose the dynamic field - actual_field = f"{action_status}_target_{month_name}" - - # Get sales person from related ref_id - ref_id = vals.get('ref_id') - sales_person = None - if ref_id: - ref = self.env['sos_case_diary'].browse(ref_id) - sales_person = ref.sales_person - - if sales_person: - report = self.env['sos_sales_achievement_report'].search([ - ('financial_year', '=', fy), - ('sales_person', '=', sales_person.id) - ], limit=1) - if report and hasattr(report, actual_field): - current_value = getattr(report, actual_field, 0.0) or 0.0 - report.write({actual_field: current_value + value}) - - return super(SosBillingCollection, self).create(vals) - - diff --git a/sos_sales/models/sos_proposal_boq.py b/sos_sales/models/sos_proposal_boq.py index 6ca61f2..daaa84c 100755 --- a/sos_sales/models/sos_proposal_boq.py +++ b/sos_sales/models/sos_proposal_boq.py @@ -5,6 +5,7 @@ class Battery_Installation_Requirement(models.Model): _name = 'sos_proposal_boq' _description = 'Battery Installation Details' _rec_name="proposal_id" + _order = 'proposal_id asc' _sql_constraints = [ ('unique_proposal_id', 'unique(proposal_id)', 'Proposal ID must be unique!') ] @@ -56,7 +57,7 @@ class Battery_Installation_Requirement(models.Model): warranty_cost = fields.Monetary(string="Warranty Cost", compute='_compute_warranty', store=True, currency_field='currency_id') margin = fields.Monetary(string="Opreational Cost",compute='_compute_margin', currency_field='currency_id') margin_per_battery = fields.Monetary(string="Opreational Cost per Battery", currency_field='currency_id') - warranty_percentage = fields.Integer(string="Warranty(%)") + warranty_percentage = fields.Float(string="Warranty(%)") final_cost = fields.Monetary(string="Final Cost", compute='_compute_final_cost', store=True, currency_field='currency_id') final_cost_per_battery = fields.Monetary(string="Final Cost", compute='_compute_final_cost_per_battery', store=True, currency_field='currency_id') communication_type = fields.Selection([ @@ -87,6 +88,11 @@ class Battery_Installation_Requirement(models.Model): line_ids_fg_ups8 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 8",compute='_compute_line_ids_by_ups',store=False) line_ids_fg_ups9 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 9",compute='_compute_line_ids_by_ups',store=False) line_ids_fg_ups10 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 10",compute='_compute_line_ids_by_ups',store=False) + line_ids_fg_ups11 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 11",compute='_compute_line_ids_by_ups',store=False) + line_ids_fg_ups12 = fields.One2many('sos_proposal_boq_fg', 'ref_id', string="FG UPS 12",compute='_compute_line_ids_by_ups',store=False) + 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) #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) @@ -99,6 +105,12 @@ class Battery_Installation_Requirement(models.Model): line_ids_sfg_ups8 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 8",compute='_compute_line_ids_by_ups',store=False) line_ids_sfg_ups9 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 9",compute='_compute_line_ids_by_ups',store=False) line_ids_sfg_ups10 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 10",compute='_compute_line_ids_by_ups',store=False) + line_ids_sfg_ups11 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 11",compute='_compute_line_ids_by_ups',store=False) + line_ids_sfg_ups12 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 12",compute='_compute_line_ids_by_ups',store=False) + line_ids_sfg_ups13 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 13",compute='_compute_line_ids_by_ups',store=False) + line_ids_sfg_ups14 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 14",compute='_compute_line_ids_by_ups',store=False) + line_ids_sfg_ups15 = fields.One2many('sos_proposal_boq_sfg', 'ref_id', string="SFG UPS 15",compute='_compute_line_ids_by_ups',store=False) + #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) @@ -111,6 +123,12 @@ class Battery_Installation_Requirement(models.Model): 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) + #Installation Kit Fields line_ids_installation_kit_ups1 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 1",compute='_compute_line_ids_by_ups',store=False) @@ -123,6 +141,11 @@ class Battery_Installation_Requirement(models.Model): line_ids_installation_kit_ups8 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 8",compute='_compute_line_ids_by_ups',store=False) line_ids_installation_kit_ups9 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 9",compute='_compute_line_ids_by_ups',store=False) line_ids_installation_kit_ups10 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 10",compute='_compute_line_ids_by_ups',store=False) + line_ids_installation_kit_ups11 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 11",compute='_compute_line_ids_by_ups',store=False) + line_ids_installation_kit_ups12 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 12",compute='_compute_line_ids_by_ups',store=False) + line_ids_installation_kit_ups13 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 13",compute='_compute_line_ids_by_ups',store=False) + line_ids_installation_kit_ups14 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 14",compute='_compute_line_ids_by_ups',store=False) + line_ids_installation_kit_ups15 = fields.One2many('sos_proposal_line_material_installation', 'ref_id', string="Installation Kit UPS 15",compute='_compute_line_ids_by_ups',store=False) #Miscellaneous Kit Fields line_ids_miscellaneous_ups1 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 1",compute='_compute_line_ids_by_ups',store=False) @@ -135,6 +158,11 @@ class Battery_Installation_Requirement(models.Model): line_ids_miscellaneous_ups8 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 8",compute='_compute_line_ids_by_ups',store=False) line_ids_miscellaneous_ups9 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 9",compute='_compute_line_ids_by_ups',store=False) line_ids_miscellaneous_ups10 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 10",compute='_compute_line_ids_by_ups',store=False) + line_ids_miscellaneous_ups11 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 11",compute='_compute_line_ids_by_ups',store=False) + line_ids_miscellaneous_ups12 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 12",compute='_compute_line_ids_by_ups',store=False) + line_ids_miscellaneous_ups13 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 13",compute='_compute_line_ids_by_ups',store=False) + line_ids_miscellaneous_ups14 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 14",compute='_compute_line_ids_by_ups',store=False) + line_ids_miscellaneous_ups15 = fields.One2many('sos_proposal_miscellaneous_items', 'ref_id', string="Miscellaneous UPS 15",compute='_compute_line_ids_by_ups',store=False) #ups totals ups1_total = fields.Float(string="UPS 1 Total Cost",compute='_compute_ups_total',store=True) @@ -147,6 +175,11 @@ class Battery_Installation_Requirement(models.Model): ups8_total = fields.Float(string="UPS 8 Total Cost", compute='_compute_ups_total', store=True) ups9_total = fields.Float(string="UPS 9 Total Cost", compute='_compute_ups_total', store=True) ups10_total = fields.Float(string="UPS 10 Total Cost", compute='_compute_ups_total', store=True) + ups11_total = fields.Float(string="UPS 11 Total Cost", compute='_compute_ups_total', store=True) + ups12_total = fields.Float(string="UPS 12 Total Cost", compute='_compute_ups_total', store=True) + ups13_total = fields.Float(string="UPS 13 Total Cost", compute='_compute_ups_total', store=True) + ups14_total = fields.Float(string="UPS 14 Total Cost", compute='_compute_ups_total', store=True) + ups15_total = fields.Float(string="UPS 15 Total Cost", compute='_compute_ups_total', store=True) extra_lines_cost = fields.Monetary(string="Cost", compute='_compute_extra_lines_cost', store=True, currency_field='currency_id') packing_and_forwarding = fields.Monetary(string="Packing & Forwarding", currency_field='currency_id',store=True) @@ -161,6 +194,11 @@ class Battery_Installation_Requirement(models.Model): line_ids_spare_ups8 = fields.One2many('sos_proposal_line_spare_ups8','ref_id', string="Spare UPS 8") line_ids_spare_ups9 = fields.One2many('sos_proposal_line_spare_ups9','ref_id', string="Spare UPS 9") line_ids_spare_ups10 = fields.One2many('sos_proposal_line_spare_ups10','ref_id', string="Spare UPS 10") + line_ids_spare_ups11 = fields.One2many('sos_proposal_line_spare_ups11','ref_id', string="Spare UPS 11") + line_ids_spare_ups12 = fields.One2many('sos_proposal_line_spare_ups12','ref_id', string="Spare UPS 12") + 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") @api.model def create(self, vals): res = super().create(vals) @@ -254,11 +292,16 @@ class Battery_Installation_Requirement(models.Model): 'line_ids_spare_ups7.total_price', 'line_ids_spare_ups7.production_cost', 'line_ids_spare_ups8.total_price', 'line_ids_spare_ups8.production_cost', 'line_ids_spare_ups9.total_price', 'line_ids_spare_ups9.production_cost', - 'line_ids_spare_ups10.total_price', 'line_ids_spare_ups10.production_cost' + 'line_ids_spare_ups10.total_price', 'line_ids_spare_ups10.production_cost', + 'line_ids_spare_ups11.total_price', 'line_ids_spare_ups11.production_cost', + 'line_ids_spare_ups12.total_price', 'line_ids_spare_ups12.production_cost', + 'line_ids_spare_ups13.total_price', 'line_ids_spare_ups13.production_cost', + 'line_ids_spare_ups14.total_price', 'line_ids_spare_ups14.production_cost', + 'line_ids_spare_ups15.total_price', 'line_ids_spare_ups15.production_cost' ) def _compute_ups_total(self): for rec in self: - for i in range(1, 11): + for i in range(1, 16): total = 0 for field_name in [ 'line_ids_fg', @@ -293,6 +336,11 @@ class Battery_Installation_Requirement(models.Model): rec.line_ids_fg_ups8 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 8) rec.line_ids_fg_ups9 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 9) rec.line_ids_fg_ups10 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 10) + rec.line_ids_fg_ups11 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 11) + rec.line_ids_fg_ups12 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 12) + rec.line_ids_fg_ups13 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 13) + rec.line_ids_fg_ups14 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 14) + rec.line_ids_fg_ups15 = rec.line_ids_fg.filtered(lambda l: l.ups_index == 15) rec.line_ids_sfg_ups1 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 1) rec.line_ids_sfg_ups2 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 2) @@ -304,6 +352,11 @@ class Battery_Installation_Requirement(models.Model): rec.line_ids_sfg_ups8 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 8) rec.line_ids_sfg_ups9 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 9) rec.line_ids_sfg_ups10 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 10) + rec.line_ids_sfg_ups11 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 11) + rec.line_ids_sfg_ups12 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 12) + rec.line_ids_sfg_ups13 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 13) + rec.line_ids_sfg_ups14 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 14) + rec.line_ids_sfg_ups15 = rec.line_ids_sfg.filtered(lambda l: l.ups_index == 15) rec.line_ids_material_ups1 = rec.line_ids_material.filtered(lambda l: l.ups_index == 1) rec.line_ids_material_ups2 = rec.line_ids_material.filtered(lambda l: l.ups_index == 2) @@ -315,6 +368,12 @@ class Battery_Installation_Requirement(models.Model): rec.line_ids_material_ups8 = rec.line_ids_material.filtered(lambda l: l.ups_index == 8) rec.line_ids_material_ups9 = rec.line_ids_material.filtered(lambda l: l.ups_index == 9) rec.line_ids_material_ups10 = rec.line_ids_material.filtered(lambda l: l.ups_index == 10) + rec.line_ids_material_ups11 = rec.line_ids_material.filtered(lambda l: l.ups_index == 11) + rec.line_ids_material_ups12 = rec.line_ids_material.filtered(lambda l: l.ups_index == 12) + rec.line_ids_material_ups13 = rec.line_ids_material.filtered(lambda l: l.ups_index == 13) + rec.line_ids_material_ups14 = rec.line_ids_material.filtered(lambda l: l.ups_index == 14) + rec.line_ids_material_ups15 = rec.line_ids_material.filtered(lambda l: l.ups_index == 15) + rec.line_ids_installation_kit_ups1 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 1) rec.line_ids_installation_kit_ups2 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 2) @@ -326,6 +385,11 @@ class Battery_Installation_Requirement(models.Model): rec.line_ids_installation_kit_ups8 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 8) rec.line_ids_installation_kit_ups9 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 9) rec.line_ids_installation_kit_ups10 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 10) + rec.line_ids_installation_kit_ups11 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 11) + rec.line_ids_installation_kit_ups12 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 12) + rec.line_ids_installation_kit_ups13 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 13) + rec.line_ids_installation_kit_ups14 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 14) + rec.line_ids_installation_kit_ups15 = rec.line_ids_installation_kit.filtered(lambda l: l.ups_index == 15) rec.line_ids_miscellaneous_ups1 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 1) rec.line_ids_miscellaneous_ups2 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 2) @@ -337,6 +401,11 @@ class Battery_Installation_Requirement(models.Model): rec.line_ids_miscellaneous_ups8 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 8) rec.line_ids_miscellaneous_ups9 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 9) rec.line_ids_miscellaneous_ups10 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 10) + rec.line_ids_miscellaneous_ups11 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 11) + rec.line_ids_miscellaneous_ups12 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 12) + rec.line_ids_miscellaneous_ups13 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 13) + rec.line_ids_miscellaneous_ups14 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 14) + rec.line_ids_miscellaneous_ups15 = rec.line_ids_miscellaneous.filtered(lambda l: l.ups_index == 15) @api.model @@ -362,10 +431,14 @@ class Battery_Installation_Requirement(models.Model): self.communication_type = self.proposal_id.communication_type self.specific_requirements = self.proposal_id.specific_requirements multiplier = max(1, math.ceil(self.proposal_id.number_of_batteries / 20)) - self.engineers_nos = 1 - self.no_of_days = multiplier - packing_calculation = 1200 - packing_kg_12V=100 + if self.proposal_id.number_of_batteries < 100: + self.engineers_nos = 1 + self.no_of_days = multiplier + else: + self.engineers_nos = 2 + self.no_of_days = multiplier / 2 + packing_calculation = 1200 + packing_kg_12V = 100 * self.proposal_id.number_of_ups direction = self.proposal_id.direction if direction == "North": forwarding_calculation = 20 * packing_kg_12V @@ -375,8 +448,9 @@ class Battery_Installation_Requirement(models.Model): forwarding_calculation = 23 * packing_kg_12V else: forwarding_calculation = 13 * packing_kg_12V - base_amount = 0.20 * (packing_calculation + forwarding_calculation) - self.packing_and_forwarding = base_amount * 0.18 + base_amount = 1.20 * (packing_calculation + forwarding_calculation) + self.packing_and_forwarding = base_amount * 1.18 + # Clear all lines self.line_ids_fg = [(5, 0, 0)] self.line_ids_sfg = [(5, 0, 0)] @@ -424,6 +498,7 @@ class Battery_Installation_Requirement(models.Model): material_lines.append((0, 0, { 'component_id': material_record.id, 'uom': material_record.uom, + 'singet_set_qty':single_set_qty, 'unit_price': material_record.unit_price, 'description': material_record.description, 'ups_index': ups_index, @@ -778,7 +853,7 @@ class sos_proposal_Material_installationkit(models.Model): def _compute_set_wise(self): for record in self: record.quantity = record.singet_set_qty * record.total_set - @api.depends('unit_price','quantity') + @api.depends('unit_price','quantity','production_cost') def _compute_total_price(self): for record in self: if record.production_cost: @@ -1153,6 +1228,176 @@ class sos_spare_material10(models.Model): _name = 'sos_proposal_line_spare_ups10' _description = 'Spare Material 10' + ref_id = fields.Many2one('sos_proposal_boq', ondelete="cascade") + component_id = fields.Many2one('sos_material', string="Material Name", required=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") + material_name = fields.Char(related="component_id.part_no",string="Material Name") + singet_set_qty = fields.Integer(string="Single Set Quantity",default=1) + total_set = fields.Integer(string="Total Set",default=1) + quantity = fields.Integer(string="Quantity",compute="_compute_set_wise",readonly=False) + unit_price = fields.Monetary(store=True,currency_field='currency_id',string="Unit Price",related="component_id.unit_price") + total_price = fields.Monetary(string="Total",compute="_compute_total_price") + description = fields.Char(string="Description") + production_cost = fields.Boolean(string="Include in Costing",default=True) + ups_index = fields.Integer(string="UPS Index",store=True) + @api.onchange('component_id') + def _onchange_component_id(self): + for record in self: + if record.component_id: + record.unit_price = record.component_id.unit_price + @api.depends('singet_set_qty','total_set') + def _compute_set_wise(self): + for record in self: + record.quantity = record.singet_set_qty * record.total_set + @api.depends('unit_price', 'quantity', 'production_cost') + def _compute_total_price(self): + for record in self: + if record.production_cost: + record.total_price = record.unit_price * record.quantity + else: + record.total_price = 0.00 +class sos_spare_material11(models.Model): + _name = 'sos_proposal_line_spare_ups11' + _description = 'Spare Material 11' + + ref_id = fields.Many2one('sos_proposal_boq', ondelete="cascade") + component_id = fields.Many2one('sos_material', string="Material Name", required=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") + material_name = fields.Char(related="component_id.part_no",string="Material Name") + singet_set_qty = fields.Integer(string="Single Set Quantity",default=1) + total_set = fields.Integer(string="Total Set",default=1) + quantity = fields.Integer(string="Quantity",compute="_compute_set_wise",readonly=False) + unit_price = fields.Monetary(store=True,currency_field='currency_id',string="Unit Price",related="component_id.unit_price") + total_price = fields.Monetary(string="Total",compute="_compute_total_price") + description = fields.Char(string="Description") + production_cost = fields.Boolean(string="Include in Costing",default=True) + ups_index = fields.Integer(string="UPS Index",store=True) + @api.onchange('component_id') + def _onchange_component_id(self): + for record in self: + if record.component_id: + record.unit_price = record.component_id.unit_price + @api.depends('singet_set_qty','total_set') + def _compute_set_wise(self): + for record in self: + record.quantity = record.singet_set_qty * record.total_set + @api.depends('unit_price', 'quantity', 'production_cost') + def _compute_total_price(self): + for record in self: + if record.production_cost: + record.total_price = record.unit_price * record.quantity + else: + record.total_price = 0.00 +class sos_spare_material12(models.Model): + _name = 'sos_proposal_line_spare_ups12' + _description = 'Spare Material 12' + + ref_id = fields.Many2one('sos_proposal_boq', ondelete="cascade") + component_id = fields.Many2one('sos_material', string="Material Name", required=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") + material_name = fields.Char(related="component_id.part_no",string="Material Name") + singet_set_qty = fields.Integer(string="Single Set Quantity",default=1) + total_set = fields.Integer(string="Total Set",default=1) + quantity = fields.Integer(string="Quantity",compute="_compute_set_wise",readonly=False) + unit_price = fields.Monetary(store=True,currency_field='currency_id',string="Unit Price",related="component_id.unit_price") + total_price = fields.Monetary(string="Total",compute="_compute_total_price") + description = fields.Char(string="Description") + production_cost = fields.Boolean(string="Include in Costing",default=True) + ups_index = fields.Integer(string="UPS Index",store=True) + @api.onchange('component_id') + def _onchange_component_id(self): + for record in self: + if record.component_id: + record.unit_price = record.component_id.unit_price + @api.depends('singet_set_qty','total_set') + def _compute_set_wise(self): + for record in self: + record.quantity = record.singet_set_qty * record.total_set + @api.depends('unit_price', 'quantity', 'production_cost') + def _compute_total_price(self): + for record in self: + if record.production_cost: + record.total_price = record.unit_price * record.quantity + else: + record.total_price = 0.00 +class sos_spare_material13(models.Model): + _name = 'sos_proposal_line_spare_ups13' + _description = 'Spare Material 13' + + ref_id = fields.Many2one('sos_proposal_boq', ondelete="cascade") + component_id = fields.Many2one('sos_material', string="Material Name", required=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") + material_name = fields.Char(related="component_id.part_no",string="Material Name") + singet_set_qty = fields.Integer(string="Single Set Quantity",default=1) + total_set = fields.Integer(string="Total Set",default=1) + quantity = fields.Integer(string="Quantity",compute="_compute_set_wise",readonly=False) + unit_price = fields.Monetary(store=True,currency_field='currency_id',string="Unit Price",related="component_id.unit_price") + total_price = fields.Monetary(string="Total",compute="_compute_total_price") + description = fields.Char(string="Description") + production_cost = fields.Boolean(string="Include in Costing",default=True) + ups_index = fields.Integer(string="UPS Index",store=True) + @api.onchange('component_id') + def _onchange_component_id(self): + for record in self: + if record.component_id: + record.unit_price = record.component_id.unit_price + @api.depends('singet_set_qty','total_set') + def _compute_set_wise(self): + for record in self: + record.quantity = record.singet_set_qty * record.total_set + @api.depends('unit_price', 'quantity', 'production_cost') + def _compute_total_price(self): + for record in self: + if record.production_cost: + record.total_price = record.unit_price * record.quantity + else: + record.total_price = 0.00 +class sos_spare_material14(models.Model): + _name = 'sos_proposal_line_spare_ups14' + _description = 'Spare Material 14' + + ref_id = fields.Many2one('sos_proposal_boq', ondelete="cascade") + component_id = fields.Many2one('sos_material', string="Material Name", required=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") + material_name = fields.Char(related="component_id.part_no",string="Material Name") + singet_set_qty = fields.Integer(string="Single Set Quantity",default=1) + total_set = fields.Integer(string="Total Set",default=1) + quantity = fields.Integer(string="Quantity",compute="_compute_set_wise",readonly=False) + unit_price = fields.Monetary(store=True,currency_field='currency_id',string="Unit Price",related="component_id.unit_price") + total_price = fields.Monetary(string="Total",compute="_compute_total_price") + description = fields.Char(string="Description") + production_cost = fields.Boolean(string="Include in Costing",default=True) + ups_index = fields.Integer(string="UPS Index",store=True) + @api.onchange('component_id') + def _onchange_component_id(self): + for record in self: + if record.component_id: + record.unit_price = record.component_id.unit_price + @api.depends('singet_set_qty','total_set') + def _compute_set_wise(self): + for record in self: + record.quantity = record.singet_set_qty * record.total_set + @api.depends('unit_price', 'quantity', 'production_cost') + def _compute_total_price(self): + for record in self: + if record.production_cost: + record.total_price = record.unit_price * record.quantity + else: + record.total_price = 0.00 +class sos_spare_material15(models.Model): + _name = 'sos_proposal_line_spare_ups15' + _description = 'Spare Material 15' + ref_id = fields.Many2one('sos_proposal_boq', ondelete="cascade") component_id = fields.Many2one('sos_material', string="Material Name", required=True) uom = fields.Selection([('meters', 'Meters'),('Nos', 'Nos'),('coils', 'Coils'), ('litre', 'litre'), ('kg', 'Kilogram'), ('Packs', 'Packs')], default="Nos",string="Uom") diff --git a/sos_sales/models/sos_proposal_builder.py b/sos_sales/models/sos_proposal_builder.py index 7f5620b..ab345ed 100755 --- a/sos_sales/models/sos_proposal_builder.py +++ b/sos_sales/models/sos_proposal_builder.py @@ -18,7 +18,7 @@ class SOS_Proposal_Builder(models.Model): string='Currency', default=lambda self: self.env['res.currency'].search([('name', '=', 'INR')], limit=1).id or False ) - total_cost = fields.Monetary(string="Total Cost", currency_field='currency_id') + total_cost = fields.Float(string="Total Cost", compute='_compute_total_cost', store=True) buffer_in_percentage = fields.Integer(string="Buffer(%)",default=0,store=True) total_with_buffer = fields.Monetary(string="Total with Buffer", compute='_compute_total_with_buffer', currency_field='currency_id', store=True) #Proposal builder @@ -197,6 +197,32 @@ class SOS_Proposal_Builder(models.Model): for rec in self: buffer_percent = rec.buffer_in_percentage or 0.0 rec.total_with_buffer = rec.total_cost * (1 + buffer_percent / 100.0) + @api.depends('proposal_id') + def _compute_total_cost(self): + for rec in self: + boq = self.env['sos_proposal_boq'].search( + [('proposal_id', '=', rec.proposal_id.id)], + limit=1, order='id desc' + ) + rec.total_cost = boq.final_cost if boq else 0.0 + @api.model + def create(self, vals): + if vals.get('proposal_id') and not vals.get('total_cost'): + boq = self.env['sos_proposal_boq'].search( + [('proposal_id', '=', vals['proposal_id'])], limit=1, order='id desc' + ) + if boq: + vals['total_cost'] = boq.final_cost + return super().create(vals) + + def write(self, vals): + if vals.get('proposal_id') and not vals.get('total_cost'): + boq = self.env['sos_proposal_boq'].search( + [('proposal_id', '=', vals['proposal_id'])], limit=1, order='id desc' + ) + if boq: + vals['total_cost'] = boq.final_cost + return super().write(vals) class SOS_Technical_Diagrams(models.Model): _name = 'sos_proposal_diagrams' diff --git a/sos_sales/models/sos_proposal_customer_requirement.py b/sos_sales/models/sos_proposal_customer_requirement.py index 3d04d42..b921509 100755 --- a/sos_sales/models/sos_proposal_customer_requirement.py +++ b/sos_sales/models/sos_proposal_customer_requirement.py @@ -147,8 +147,8 @@ 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: - if self.number_of_ups > 10: - raise ValidationError("Number of UPS cannot be more than 10.") + if self.number_of_ups > 15: + raise ValidationError("Number of UPS cannot be more than 15.") lines = [(5, 0, 0)] # clear all for i in range(self.number_of_ups): diff --git a/sos_sales/models/sos_sales_achievement_report.py b/sos_sales/models/sos_sales_achievement_report.py index f1f225c..ff9fdb3 100755 --- a/sos_sales/models/sos_sales_achievement_report.py +++ b/sos_sales/models/sos_sales_achievement_report.py @@ -1,8 +1,9 @@ from odoo import models, fields, api -from datetime import date +from datetime import date, timedelta,datetime import re import calendar +from odoo.exceptions import UserError class SOS_Sales_Achievement_Report(models.Model): @@ -20,13 +21,13 @@ class SOS_Sales_Achievement_Report(models.Model): planned_target_april = fields.Float(string="Planned For April") actual_target_april = fields.Float(string="Actual For April") billed_target_april = fields.Float(string="Billed For April") - yet_to_billed_target_april = fields.Float(string="Yet to Billed For April") + yet_to_billed_target_april = fields.Float(string="Yet to Billed For April",compute="_compute_yet_to_billed_april") collected_target_april = fields.Float(string="Collected For April") achievement_percentage_april = fields.Char(string="Achievement Percentage For April", compute="_compute_achievement_percentage", store=True) planned_target_may = fields.Float(string="Planned For May") actual_target_may = fields.Float(string="Actual For May") billed_target_may = fields.Float(string="Billed For May") - yet_to_billed_target_may = fields.Float(string="Yet to Billed For May") + yet_to_billed_target_may = fields.Float(string="Yet to Billed For May",compute="_compute_yet_to_billed_targets") collected_target_may = fields.Float(string="Collected For May") achievement_percentage_may = fields.Char(string="Achievement Percentage For May", compute="_compute_achievement_percentage", store=True) planned_target_june = fields.Float(string="Planned For June") @@ -108,7 +109,50 @@ class SOS_Sales_Achievement_Report(models.Model): ytd_billed = fields.Float(string="YTD Billed", store=True,compute="_compute_ytd_billed") ytd_collected = fields.Float(string="YTD Collected", store=True,compute="_compute_ytd_collected") line_ids = fields.One2many('sos_sales_achievement_report_brief', 'ref_id',copy=True) - + billing_collection_line_ids = fields.One2many( + 'sos_billing_collection', + 'ref_id', + string="Billing Collection Lines" + ) + @api.depends('opening_balance', 'actual_target_april', 'billed_target_april') + def _compute_yet_to_billed_april(self): + for rec in self: + rec.yet_to_billed_target_april = (rec.opening_balance or 0.0) + \ + (rec.actual_target_april or 0.0) - \ + (rec.billed_target_april or 0.0) + + @api.depends( + 'yet_to_billed_target_april', # link to April + 'actual_target_may', 'billed_target_may', + 'actual_target_june', 'billed_target_june', + 'actual_target_july', 'billed_target_july', + 'actual_target_august', 'billed_target_august', + 'actual_target_september', 'billed_target_september', + 'actual_target_october', 'billed_target_october', + 'actual_target_november', 'billed_target_november', + 'actual_target_december', 'billed_target_december', + 'actual_target_january', 'billed_target_january', + 'actual_target_february', 'billed_target_february', + 'actual_target_march', 'billed_target_march', + ) + def _compute_yet_to_billed_targets(self): + month_order = [ + 'may', 'june', 'july', 'august', 'september', + 'october', 'november', 'december', + 'january', 'february', 'march' + ] + + for rec in self: + prev_yet_to_bill = rec.yet_to_billed_target_april or 0.0 + for month in month_order: + actual = getattr(rec, f'actual_target_{month}', 0.0) or 0.0 + billed = getattr(rec, f'billed_target_{month}', 0.0) or 0.0 + result = prev_yet_to_bill + actual - billed + setattr(rec, f'yet_to_billed_target_{month}', result) + prev_yet_to_bill = result + + + @api.depends( 'yet_to_billed_target_april', 'yet_to_billed_target_may', 'yet_to_billed_target_june', 'yet_to_billed_target_july', 'yet_to_billed_target_august', 'yet_to_billed_target_september', @@ -137,41 +181,52 @@ class SOS_Sales_Achievement_Report(models.Model): def action_view_billed_lines(self): self.ensure_one() - # Extract the month from context - month = int(self.env.context.get('month', 0)) - if not month: - month = 4 # fallback to April - - # You must extract year (hardcoded or dynamic based on financial year logic) - # Assuming your main model has `financial_year = 'FY 2025-2026'` - match = re.search(r'FY\s*(\d{4})', self.financial_year or '') - if match: - year = int(match.group(1)) + month = int(self.env.context.get('month', 4)) # default April + action = self.env.context.get('action', 'Collected') + if action == 'Billed': + view_id = self.env.ref('sos_sales.view_sos_billed_collection_wizard_form_billed').id else: - year = date.today().year # fallback + view_id = self.env.ref('sos_sales.view_sos_billed_collection_wizard_form_collected').id - # Get month boundaries + # Extract year from financial_year string like 'FY 2025-2026' + match = re.search(r'FY\s*(\d{4})', self.financial_year or '') + year = int(match.group(1)) if match else date.today().year + + # Get month date range last_day = calendar.monthrange(year, month)[1] - from_date = f'{year}-{month:02d}-01' - to_date = f'{year}-{month:02d}-{last_day:02d}' - # Fetch Billed lines + from_date = date(year, month, 1) + to_date = date(year, month, last_day) + + # Use the actual related record's value (not the related field) + sales_person_id = self.sales_person.id or self.ref('sales_person').id + + # If sales_person is still None, raise a warning + if not sales_person_id: + raise UserError("Sales person is missing in this record.") + + # Search filtered billing lines billed_lines = self.env['sos_billing_collection'].search([ - ('sales_person', '=', self.sales_person.id), - ('action_status', '=', self.env.context.get('action')), + ('sales_person', '=', sales_person_id), + ('action_status', '=', action), ('date_of_action', '>=', from_date), ('date_of_action', '<=', to_date), ]) + wizard = self.env['sos_billed_collection_wizard'].create({ + 'main_parent_id': self.id, + 'billed_line_ids': [(6, 0, billed_lines.ids)], + 'action_status': action, + }) + return { 'name': f"{self.env.context.get('action')} Items", 'type': 'ir.actions.act_window', 'res_model': 'sos_billed_collection_wizard', 'view_mode': 'form', + 'view_id': view_id, 'target': 'new', - 'context': { - 'default_billed_line_ids': [(6, 0, billed_lines.ids)], - 'default_parent_id': self.id, - } + 'res_id': wizard.id, } + def action_view_brief_lines(self): self.ensure_one() domain = [('ref_id', '=', self.id)] @@ -204,6 +259,7 @@ class SOS_Sales_Achievement_Report(models.Model): } } + @api.depends( 'collected_target_april', 'collected_target_may', 'collected_target_june', 'collected_target_july', 'collected_target_august', 'collected_target_september', @@ -500,13 +556,107 @@ class SOS_Sales_Achievement_Report_Brief(models.Model): _order = 'action_date desc' ref_id = fields.Many2one('sos_sales_achievement_report', string="Financial Year", ondelete="cascade") customer_name = fields.Many2one('sos_customers',string="Customer Name", required=True) - action_date = fields.Date(string="QP No") + action_date = fields.Date(string="Date") currency_id = fields.Many2one( 'res.currency', string='Currency', default=lambda self: self.env['res.currency'].search([('name', '=', 'INR')], limit=1).id or False ) - proposal_value = fields.Monetary(currency_field='currency_id',string="Proposal Value(In Lakhs)") + proposal_value = fields.Monetary(currency_field='currency_id',string="PO Received(In Lakhs)") + billed_date = fields.Date(string="Billed Date") + billed_amount = fields.Monetary(currency_field='currency_id',string="Billed Value(In Lakhs)") + def open_line_form(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Edit Line', + 'res_model': self._name, + 'res_id': self.id, + 'view_mode': 'form', + 'target': 'new', # Opens as a popup + } + def _get_financial_year(self,current_date): + start_year = current_date.year if current_date.month >= 4 else current_date.year - 1 + end_year = start_year + 1 + return f"FY {start_year}-{end_year}" + + def write(self, vals): + for rec in self: + # Previous values + old_billed_date = rec.billed_date + old_billed_amount = rec.billed_amount + report = rec.ref_id + # New values + new_billed_date = vals.get('billed_date', old_billed_date) + new_billed_amount = vals.get('billed_amount', old_billed_amount) + + if isinstance(new_billed_date, str): + new_billed_date = datetime.strptime(new_billed_date, '%Y-%m-%d').date() + + # Adjust billed target + if report and old_billed_date and new_billed_date: + old_month_billed = old_billed_date.strftime('%B').lower() + new_month_billed = new_billed_date.strftime('%B').lower() + if old_month_billed != new_month_billed or old_billed_amount != new_billed_amount: + old_field_billed = f"billed_target_{old_month_billed}" + new_field_billed = f"billed_target_{new_month_billed}" + if hasattr(report, old_field_billed): + report.write({ + old_field_billed: max((getattr(report, old_field_billed, 0.0) or 0.0) - old_billed_amount, 0.0) + }) + if hasattr(report, new_field_billed): + 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: + 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, + 'value': new_billed_amount + }) + + return super(SOS_Sales_Achievement_Report_Brief, self).write(vals) + + @api.model + def create(self, vals): + action_date = vals.get('action_date') + billed_date = vals.get('billed_date') + customer_name = vals.get('customer_name') + + if isinstance(action_date, str): + action_date = datetime.strptime(action_date, '%Y-%m-%d').date() + if isinstance(billed_date, str): + billed_date = datetime.strptime(billed_date, '%Y-%m-%d').date() + + month_name = action_date.strftime('%B').lower() + value = vals.get('proposal_value', 0.0) + actual_field = f"actual_target_{month_name}" + + ref_id = vals.get('ref_id') + if ref_id: + report = self.env['sos_sales_achievement_report'].browse(ref_id) + sales_person = report.sales_person + if sales_person and hasattr(report, actual_field): + current_value = getattr(report, actual_field, 0.0) or 0.0 + report.write({actual_field: current_value + value}) + + # Instead of also summing billed_value here, just create billing line + if billed_date: + self.env['sos_billing_collection'].create({ + 'ref_id': report.id, + 'customer_name': customer_name, + 'sales_person': report.sales_person.id, + 'action_status': 'Billed', + 'date_of_action': billed_date, + 'value': vals.get('billed_amount', 0.0) + }) + + return super(SOS_Sales_Achievement_Report_Brief, self).create(vals) + class SOS_Sales_Achievement_Report_Brief_Wizard(models.TransientModel): _name = 'sos_sales_achievement_report_brief_wizard' @@ -520,13 +670,144 @@ class SOS_Sales_Achievement_Report_Brief_Wizard(models.TransientModel): string="Brief Lines", readonly=True ) + + + def action_add_new_brief_line(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Add Brief Line', + 'res_model': 'sos_sales_achievement_report_brief', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_ref_id': self.parent_id.id, + 'default_currency_id': self.env.ref('base.INR').id, + } + } class SosBilledCollectionWizard(models.TransientModel): _name = 'sos_billed_collection_wizard' _description = 'Billed Collection Lines Wizard' - parent_id = fields.Many2one('sos_billing_collection') # adjust accordingly + main_parent_id = fields.Many2one('sos_sales_achievement_report', string="Sales Achievement Report") billed_line_ids = fields.Many2many( 'sos_billing_collection', string="Billed Lines", readonly=True, - ) \ No newline at end of file + domain="[('ref_id', '=', main_parent_id)]" + ) + action_status = fields.Selection([ + ('Billed', 'Billed'), + ('Collected', 'Collected') + ], string='Action Status', readonly=True) + @api.model + def default_get(self, fields_list): + res = super(SosBilledCollectionWizard, self).default_get(fields_list) + main_parent_id = self.env.context.get('active_id') + if main_parent_id and self.env.context.get('active_model') == 'sos_sales_achievement_report': + res['main_parent_id'] = main_parent_id + # Populate billed_line_ids with related sos_billing_collection records + billing_lines = self.env['sos_billing_collection'].search([('ref_id', '=', main_parent_id)]) + res['billed_line_ids'] = [(6, 0, billing_lines.ids)] + return res + + def action_add_new_collection_line(self): + self.ensure_one() + if not self.main_parent_id: + raise UserError("No Sales Achievement Report found.") + if self.action_status == "Collected": + form_title="Collection" + view_xml_id="view_sos_billing_collection_minimal_form_collection" + else: + form_title="Billed" + view_xml_id="view_sos_billing_collection_minimal_form_billed" + return { + 'type': 'ir.actions.act_window', + 'name': f'Add {form_title}', + 'res_model': 'sos_billing_collection', + 'view_mode': 'form', + 'view_id': self.env.ref(f'sos_sales.{view_xml_id}').id, + + 'target': 'new', + 'context': { + 'default_ref_id': self.main_parent_id.id, + 'default_action_status': self.action_status, + 'default_sales_person': self.main_parent_id.sales_person.id if self.main_parent_id.sales_person else False, + } + } +class SosBillingCollection(models.Model): + _name = 'sos_billing_collection' + _description = 'Billing & Collection Details' + + ref_id = fields.Many2one('sos_sales_achievement_report', ondelete="cascade", required=True) + customer_name = fields.Many2one('sos_customers', string="Customer Name") + sales_person = fields.Many2one( + 'res.users', + string='Sales Executive', + related="ref_id.sales_person", + store=True + ) + action_status = fields.Selection([ + ('Billed', 'Billed'), + ('Collected', 'Collected') + ], string='Action') + date_of_action = fields.Date(string="Date") + currency_id = fields.Many2one( + 'res.currency', + string='Currency', + default=lambda self: self.env['res.currency'].search([('name', '=', 'INR')], limit=1).id or False + ) + value = fields.Monetary(currency_field='currency_id', string="Value (In Lakhs)") + customer_name = fields.Many2one('sos_customers',string="Customer Name") + po_no = fields.Char(string="PO No") + invoice_no = fields.Char(string="Invoice No") + products = fields.Selection( + [ + ('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'), + ('SBMS 55A', 'SBMS 55A'), + ('MC 250W', 'MC 250W'), + ('HeartTarang', 'HeartTarang') + ], + string="Products") + quantity = fields.Integer(string="Quantity") + def _get_financial_year(self, current_date): + start_year = current_date.year if current_date.month >= 4 else current_date.year - 1 + end_year = start_year + 1 + return f"FY {start_year}-{end_year}" + + @api.model + def create(self, vals): + if not vals.get('ref_id'): + raise UserError("Cannot create record without a valid Sales Achievement Report.") + + action_date = vals.get('date_of_action') + action_status = vals.get('action_status').lower() + if isinstance(action_date, str): + action_date = datetime.strptime(action_date, '%Y-%m-%d').date() + + fy = self._get_financial_year(action_date) if action_date else False + month_name = action_date.strftime('%B').lower() if action_date else False + final_value = vals.get('value', 0.0) + actual_field = f"{action_status}_target_{month_name}" if month_name else False + + record = super(SosBillingCollection, self).create(vals) + + # Update the related sos_sales_achievement_report by adding value + if vals.get('ref_id') and month_name: + report = self.env['sos_sales_achievement_report'].search([ + ('id', '=', vals.get('ref_id')), + ('financial_year', '=', fy), + ]) + if report and hasattr(report, actual_field): + current_value = getattr(report, actual_field, 0.0) or 0.0 + new_value = current_value + final_value + report.write({actual_field: new_value}) + + return record + diff --git a/sos_sales/models/sos_sales_action_plan.py b/sos_sales/models/sos_sales_action_plan.py index d883847..4772bb8 100755 --- a/sos_sales/models/sos_sales_action_plan.py +++ b/sos_sales/models/sos_sales_action_plan.py @@ -24,6 +24,14 @@ class sos_sales_action_plan(models.Model): string="End Customer/Quote No", domain="[('customer_name', '=', customer_name), ('quote_no', '!=', False)]" ) + ce_product_type = fields.Selection( + [ + ('Sales', 'Sales'), + ('Service', 'Service'), + ('Spare', 'Spare'), + ('Cloud', 'Cloud') + ], + string="Service Type") product = fields.Selection( [ ('BHMS 1.2V', 'BHMS 1.2V'), @@ -80,7 +88,7 @@ class sos_sales_action_plan(models.Model): ('Engaged to Negotiation', 'Engaged to Negotiation'), ('Negotiation to Order', 'Negotiation to Order') ], - string="Action Category",requierd=True) + string="Action Category",required=True) action_plan = fields.Text(string="Action Plan") result = fields.Text(string="Result") status = fields.Selection( @@ -101,9 +109,15 @@ class sos_sales_action_plan(models.Model): ], string='State', default='draft', readonly=True) def _get_customer_domain(self): - if self.env.user.has_group('sos_inventory.sos_management_user'): + if ( + self.env.user.has_group('sos_inventory.sos_management_user') or + self.env.user.has_group('sos_inventory.sos_ce_head') or + self.env.user.has_group('sos_inventory.sos_ce_user') + ): return [] # no filter, show all return [('responsible', '=', self.env.uid)] + + @api.onchange('quote_no_selector') def _onchange_quote_no_selector(self): if self.quote_no_selector: @@ -436,21 +450,23 @@ class sos_sales_action_plan(models.Model): } } else: - casediary_record = self.env['sos_case_diary'].create({ + vals = { 'customer_name': self.customer_name.id, - 'end_customer_name':self.end_customer_name, + 'end_customer_name': self.end_customer_name, 'customer_city': self.location, 'sales_person': self.sales_executive.id, 'products': self.product, 'proposal_value': self.value, 'status': self.status, - 'quote_no':self.quote_no, - 'quantity':self.quantity, - 'po_no':self.po_no, - 'po_copy':self.po_copy, - 'po_copy_filename':self.po_copy_filename - - }) + 'quote_no': self.quote_no, + 'quantity': self.quantity, + 'po_no': self.po_no, + 'po_copy': self.po_copy, + 'po_copy_filename': self.po_copy_filename + } + if self.ce_product_type: + vals['ce_product_type'] = self.ce_product_type + casediary_record = self.env['sos_case_diary'].create(vals) self.env['sos_case_diary_line'].create({ 'ref_id': casediary_record.id, 'action_plan_date': self.date, diff --git a/sos_sales/report/sos_proposal_report_view.xml b/sos_sales/report/sos_proposal_report_view.xml index 91e9356..c902d37 100755 --- a/sos_sales/report/sos_proposal_report_view.xml +++ b/sos_sales/report/sos_proposal_report_view.xml @@ -35,24 +35,24 @@
-



-

1. INTRODUCTION

-

ABOUT SOSALEY

+



+

1. INTRODUCTION

+
ABOUT SOSALEY

-

2. PROPOSAL OBJECTIVE

+

2. PROPOSAL OBJECTIVE

-

3. PROPOSAL OVERVIEW

+

3. PROPOSAL OVERVIEW

-

4. ARCHITECTURE

+

4. ARCHITECTURE



-

PARAMETERS MONITORED

+
PARAMETERS MONITORED


-

5. QUOTATION

+

5. QUOTATION

@@ -74,9 +74,9 @@
Customer Details
Billing Address
-

6. PAYMENT TERMS

+

6. PAYMENT TERMS

-

7. TERMS AND CONDITIONS

+

7. TERMS AND CONDITIONS

diff --git a/sos_sales/security/ir.model.access.csv b/sos_sales/security/ir.model.access.csv index ff410dd..0f4ffdb 100755 --- a/sos_sales/security/ir.model.access.csv +++ b/sos_sales/security/ir.model.access.csv @@ -51,9 +51,15 @@ access_sos_proposal_line_spare_ups7,sos_proposal_line_spare_ups7 access,model_so access_sos_proposal_line_spare_ups8,sos_proposal_line_spare_ups8 access,model_sos_proposal_line_spare_ups8,base.group_user,1,1,1,1 access_sos_proposal_line_spare_ups9,sos_proposal_line_spare_ups9 access,model_sos_proposal_line_spare_ups9,base.group_user,1,1,1,1 access_sos_proposal_line_spare_ups10,sos_proposal_line_spare_ups10 access,model_sos_proposal_line_spare_ups10,base.group_user,1,1,1,1 +access_sos_proposal_line_spare_ups11,sos_proposal_line_spare_ups11 access,model_sos_proposal_line_spare_ups11,base.group_user,1,1,1,1 +access_sos_proposal_line_spare_ups12,sos_proposal_line_spare_ups12 access,model_sos_proposal_line_spare_ups12,base.group_user,1,1,1,1 +access_sos_proposal_line_spare_ups13,sos_proposal_line_spare_ups13 access,model_sos_proposal_line_spare_ups13,base.group_user,1,1,1,1 +access_sos_proposal_line_spare_ups14,sos_proposal_line_spare_ups14 access,model_sos_proposal_line_spare_ups14,base.group_user,1,1,1,1 +access_sos_proposal_line_spare_ups15,sos_proposal_line_spare_ups15 access,model_sos_proposal_line_spare_ups15,base.group_user,1,1,1,1 access_sos_sales_achievement_report_brief,sos_sales_achievement_report_brief access,model_sos_sales_achievement_report_brief,base.group_user,1,1,1,1 access_sos_sales_achievement_report_brief_wizard,sos_sales_achievement_report_brief_wizard access,model_sos_sales_achievement_report_brief_wizard,base.group_user,1,1,1,1 access_sos_billing_collection,sos_billing_collection access,model_sos_billing_collection,base.group_user,1,1,1,1 access_sos_billed_collection_wizard,sos_billed_collection_wizard access,model_sos_billed_collection_wizard,base.group_user,1,1,1,1 +access_sos_business_performance_wizard,sos_business_performance_wizard access,model_sos_business_performance_wizard,base.group_user,1,1,1,1 diff --git a/sos_sales/security/record_rules.xml b/sos_sales/security/record_rules.xml index 53faec2..77969c5 100755 --- a/sos_sales/security/record_rules.xml +++ b/sos_sales/security/record_rules.xml @@ -68,6 +68,17 @@ + + Sales Action Plan: CE Head Reads CE User Records + + + + + + + + + Sos sales action plan: All Records - Read Access @@ -89,7 +100,8 @@ [(1, '=', 1)] @@ -127,7 +139,16 @@ - + + Case Diary: CE Head Reads CE User Records + + + + + + + + Sos Case Diary: All Records - Read Access diff --git a/sos_sales/views/menu.xml b/sos_sales/views/menu.xml index 402d5cc..335ec9f 100755 --- a/sos_sales/views/menu.xml +++ b/sos_sales/views/menu.xml @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/sos_sales/views/sos_case_diary_view.xml b/sos_sales/views/sos_case_diary_view.xml index c18591f..15c0924 100755 --- a/sos_sales/views/sos_case_diary_view.xml +++ b/sos_sales/views/sos_case_diary_view.xml @@ -164,36 +164,7 @@ - -
- -
-

Billed

-

- - - - - - - -
-
-
-

Collected

-

- - - - - - - -
-
-
+
@@ -310,7 +281,7 @@ - + - + diff --git a/sos_sales/views/sos_proposal_boq_view.xml b/sos_sales/views/sos_proposal_boq_view.xml index cd26653..5d11f3a 100755 --- a/sos_sales/views/sos_proposal_boq_view.xml +++ b/sos_sales/views/sos_proposal_boq_view.xml @@ -1117,6 +1117,541 @@ + + + +

Finished Goods

+ + + + + + + + + + + + + + + +

Semi-Finished Goods

+ + + + + + + + + + + + + + + +

Materials

+ + + + + + + + + + + + + + + +

Installation Kit

+ + + + + + + + + + + + + + + +

Spare/Additional Materials

+ + + + + + + + + + + + + + +

Miscellaneous

+ + + + + + + + + + +
+ +

Finished Goods

+ + + + + + + + + + + + + + + +

Semi-Finished Goods

+ + + + + + + + + + + + + + + +

Materials

+ + + + + + + + + + + + + + + +

Installation Kit

+ + + + + + + + + + + + + + + +

Spare/Additional Materials

+ + + + + + + + + + + + + + +

Miscellaneous

+ + + + + + + + + + +
+ +

Finished Goods

+ + + + + + + + + + + + + + + +

Semi-Finished Goods

+ + + + + + + + + + + + + + + +

Materials

+ + + + + + + + + + + + + + + +

Installation Kit

+ + + + + + + + + + + + + + + +

Spare/Additional Materials

+ + + + + + + + + + + + + + +

Miscellaneous

+ + + + + + + + + + +
+ +

Finished Goods

+ + + + + + + + + + + + + + + +

Semi-Finished Goods

+ + + + + + + + + + + + + + + +

Materials

+ + + + + + + + + + + + + + + +

Installation Kit

+ + + + + + + + + + + + + + + +

Spare/Additional Materials

+ + + + + + + + + + + + + + +

Miscellaneous

+ + + + + + + + + + +
+ +

Finished Goods

+ + + + + + + + + + + + + + + +

Semi-Finished Goods

+ + + + + + + + + + + + + + + +

Materials

+ + + + + + + + + + + + + + + +

Installation Kit

+ + + + + + + + + + + + + + + +

Spare/Additional Materials

+ + + + + + + + + + + + + + +

Miscellaneous

+ + + + + + + + + +
- + diff --git a/sos_sales/views/sos_sales_leads_view.xml b/sos_sales/views/sos_sales_leads_view.xml index b8fdf2a..f607b39 100755 --- a/sos_sales/views/sos_sales_leads_view.xml +++ b/sos_sales/views/sos_sales_leads_view.xml @@ -99,5 +99,5 @@ - + diff --git a/sos_sales/views/sos_sales_plan_view.xml b/sos_sales/views/sos_sales_plan_view.xml index a11bd41..03ea63f 100755 --- a/sos_sales/views/sos_sales_plan_view.xml +++ b/sos_sales/views/sos_sales_plan_view.xml @@ -257,5 +257,5 @@ name="action_report_sales_plan_btn"> Print Report - + diff --git a/sos_sales/views/sos_vertical_domain_view.xml b/sos_sales/views/sos_vertical_domain_view.xml index e3f53da..86d532e 100755 --- a/sos_sales/views/sos_vertical_domain_view.xml +++ b/sos_sales/views/sos_vertical_domain_view.xml @@ -39,5 +39,5 @@ - + diff --git a/sos_sales/wizard/sos_sales_achievement_wizard_view.xml b/sos_sales/wizard/sos_sales_achievement_wizard_view.xml index 1ff9c81..a66042f 100644 --- a/sos_sales/wizard/sos_sales_achievement_wizard_view.xml +++ b/sos_sales/wizard/sos_sales_achievement_wizard_view.xml @@ -1,19 +1,32 @@ + sos_sales_achievement_report_brief_wizard.form sos_sales_achievement_report_brief_wizard
+
+ + + - - + + + + +