from odoo import models, fields, api from datetime import date, timedelta,datetime import re import calendar from odoo.exceptions import UserError class SOS_Sales_Achievement_Report(models.Model): _name = 'sos_sales_achievement_report' _description = 'Sosaley Sales Achievement Report' _rec_name='financial_year' _sql_constraints = [ ('unique_financial_year_sales_person', 'UNIQUE(financial_year, sales_person)', 'The combination of Financial Year and Sales Person must be unique.') ] display_name = fields.Char(string="Name", compute='_compute_display_name', store=True) category = fields.Selection([ ('sales', 'Sales'), ('export', 'Export') ], string="Category", required=True, default='sales') financial_year = fields.Char(string="Financial Year", default=lambda self: self._get_financial_year()) sales_person = fields.Many2one('res.users', string='Sales person') overall_target = fields.Float(string="Overall Target") opening_balance = fields.Float(string="Opening Balance") 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",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",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") actual_target_june = fields.Float(string="Actual For June") billed_target_june = fields.Float(string="Billed For June") yet_to_billed_target_june = fields.Float(string="Yet to Billed For June") collected_target_june = fields.Float(string="Collected For June") achievement_percentage_june = fields.Char(string="Achievement Percentage For June", compute="_compute_achievement_percentage", store=True) planned_target_july = fields.Float(string="Planned For July") actual_target_july = fields.Float(string="Actual For July") billed_target_july = fields.Float(string="Billed For July") yet_to_billed_target_july = fields.Float(string="Yet to Billed For July") collected_target_july = fields.Float(string="Collected For July") achievement_percentage_july = fields.Char(string="Achievement Percentage For July", compute="_compute_achievement_percentage", store=True) planned_target_august = fields.Float(string="Planned For August") actual_target_august = fields.Float(string="Actual For August") billed_target_august = fields.Float(string="Billed For August") yet_to_billed_target_august = fields.Float(string="Yet to Billed For August") collected_target_august = fields.Float(string="Collected For August") achievement_percentage_august = fields.Char(string="Achievement Percentage For August", compute="_compute_achievement_percentage", store=True) planned_target_september = fields.Float(string="Planned For September") actual_target_september = fields.Float(string="Actual For September") billed_target_september = fields.Float(string="Billed For September") yet_to_billed_target_september = fields.Float(string="Yet to Billed For September") collected_target_september = fields.Float(string="Collected For September") achievement_percentage_september = fields.Char(string="Achievement Percentage For September", compute="_compute_achievement_percentage", store=True) planned_target_october = fields.Float(string="Planned For October") actual_target_october = fields.Float(string="Actual For October") billed_target_october = fields.Float(string="Billed For October") yet_to_billed_target_october = fields.Float(string="Yet to Billed For October") collected_target_october = fields.Float(string="Collected For October") achievement_percentage_october = fields.Char(string="Achievement Percentage For October", compute="_compute_achievement_percentage", store=True) planned_target_november = fields.Float(string="Planned For November") actual_target_november = fields.Float(string="Actual For November") billed_target_november = fields.Float(string="Billed For November") yet_to_billed_target_november = fields.Float(string="Yet to Billed For November") collected_target_november = fields.Float(string="Collected For November") achievement_percentage_november = fields.Char(string="Achievement Percentage For November", compute="_compute_achievement_percentage", store=True) planned_target_december = fields.Float(string="Planned For December") actual_target_december = fields.Float(string="Actual For December") billed_target_december = fields.Float(string="Billed For December") yet_to_billed_target_december = fields.Float(string="Yet to Billed For December") collected_target_december = fields.Float(string="Collected For December") achievement_percentage_december = fields.Char(string="Achievement Percentage For December", compute="_compute_achievement_percentage", store=True) planned_target_january = fields.Float(string="Planned For January") actual_target_january = fields.Float(string="Actual For January") billed_target_january = fields.Float(string="Billed For January") yet_to_billed_target_january = fields.Float(string="Yet to Billed For January") collected_target_january = fields.Float(string="Collected For January") achievement_percentage_january = fields.Char(string="Achievement Percentage For January", compute="_compute_achievement_percentage", store=True) planned_target_february = fields.Float(string="Planned For February") actual_target_february = fields.Float(string="Actual For February") billed_target_february = fields.Float(string="Billed For February") yet_to_billed_target_february = fields.Float(string="Yet to Billed For February") collected_target_february = fields.Float(string="Collected For February") achievement_percentage_february = fields.Char(string="Achievement Percentage For February", compute="_compute_achievement_percentage", store=True) planned_target_march = fields.Float(string="Planned For March") actual_target_march = fields.Float(string="Actual For March") billed_target_march = fields.Float(string="Billed For March") yet_to_billed_target_march = fields.Float(string="Yet to Billed For March") collected_target_march = fields.Float(string="Collected For March") achievement_percentage_march = fields.Char(string="Achievement Percentage For March", compute="_compute_achievement_percentage", store=True) planned_target_total = fields.Float(string="Planned Total", compute="_compute_planned_total_target", store=True) actual_target_total = fields.Float(string="Actual Total", compute="_compute_actual_total_target", store=True) achievement_percentage_total = fields.Char(string="Achievement Percentage For Total", compute="_compute_achievement_percentage_total", store=True) billed_target_total = fields.Char(string="Billed For Total", compute="_compute_billed_total_target", store=True) yet_to_billed_target_total = fields.Char(string="Billed For Total", compute="_compute_yet_to_billed_total_target", store=True) collected_target_total = fields.Float(string="collected For Total", compute="_compute_collected_total_target", store=True) can_edit_billed_target = fields.Boolean( string="Can Edit Billed Target", compute="_compute_can_edit_billed_target", store=False, ) ytd_target = fields.Float(string="YTD Total", store=True, compute="_compute_ytd_target") ytd_sales = fields.Float(string="YTD Sales", store=True, compute="_compute_ytd_sales_target") ytd_sales_percentage = fields.Float(string="YTD Sales Percentage", store=True, compute="_compute_ytd_sales_percentage") ytd_yet_to_billed = fields.Float(string="YTD Billed") 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" ) def action_yet_to_bill(self): self.ensure_one() return { 'type': 'ir.actions.act_window', 'name': 'Yet To Bill', 'res_model': 'yet_to_bill_wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'brief_ref_id': self.id, # scopes wizard to this report's brief lines } } @api.depends('category','financial_year', 'sales_person') def _compute_display_name(self): for record in self: if record.category == "sales": record.display_name = f"{record.financial_year or ''} / {record.sales_person.name or ''}" else: record.display_name = f"{record.financial_year or ''} / Export" @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 def action_view_billed_lines(self): self.ensure_one() 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: view_id = self.env.ref('sos_sales.view_sos_billed_collection_wizard_form_collected').id match = re.search(r'FY\s*(\d{4})', self.financial_year or '') year = int(match.group(1)) if match else date.today().year last_day = calendar.monthrange(year, month)[1] from_date = date(year, month, 1) to_date = date(year, month, last_day) if self.category == "sales": sales_person_id = self.sales_person.id or self.ref('sales_person').id if not sales_person_id: raise UserError("Sales person is missing in this record.") billed_lines = self.env['sos_billing_collection'].search([ ('sales_person', '=', sales_person_id), ('action_status', '=', action), ('date_of_action', '>=', from_date), ('date_of_action', '<=', to_date), ]) else: billed_lines = self.env['sos_billing_collection'].search([ ('category', '=', "export"), ('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', 'res_id': wizard.id, } # def action_view_brief_lines_acc(self): # return { # 'name': "Action", # 'type': 'ir.actions.act_window', # 'res_model': 'sos_sales_achievement_report_brief', # 'view_mode': 'form', # 'view_id': self.env.ref('sos_sales.view_form_sos_sales_achievement_acc').id, # 'target': 'new', # 'context': { # 'ref_record_id': self.id # } # } def action_view_brief_lines_acc(self): self.ensure_one() current_date = date.today() start_year = current_date.year if current_date.month >= 4 else current_date.year - 1 end_year = start_year + 1 current_fy = f"FY {start_year}-{end_year}" domain = [ ('ref_record_id', '=', self.id), ('financial_year', '!=', current_fy), ] brief_lines = self.env['sos_sales_achievement_report_brief'].search(domain) return { 'name': 'Achievement Brief Lines', 'type': 'ir.actions.act_window', 'res_model': 'sos_sales_achievement_report_brief_wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'default_brief_line_ids': [(6, 0, brief_lines.ids)], 'default_parent_id': self.id, } } def action_view_brief_lines(self): self.ensure_one() domain = [('ref_id', '=', self.id)] # Extract starting year from 'FY 2025-2026' match = re.search(r'FY\s*(\d{4})', self.financial_year or '') if match: year = int(match.group(1)) else: raise ValueError(f"Unable to extract year from financial year: {self.financial_year}") month = int(self.env.context.get('month', 0)) if month: # Filter specific month domain.append(('action_date', '>=', f'{year}-{month:02d}-01')) last_day = calendar.monthrange(year, month)[1] domain.append(('action_date', '<=', f'{year}-{month:02d}-{last_day:02d}')) else: # Filter for entire financial year (April to March) start_date = f'{year}-04-01' end_date = f'{year + 1}-03-31' domain.append(('action_date', '>=', start_date)) domain.append(('action_date', '<=', end_date)) brief_lines = self.env['sos_sales_achievement_report_brief'].search(domain) total_proposal_value=0 for eachrecord in brief_lines: total_proposal_value += eachrecord.proposal_value if month: month_name = calendar.month_name[month].lower() field_name = f"actual_target_{month_name}" self[field_name] = total_proposal_value return { 'name': 'Achievement Brief Lines', 'type': 'ir.actions.act_window', 'res_model': 'sos_sales_achievement_report_brief_wizard', 'view_mode': 'form', 'target': 'new', 'context': { 'default_brief_line_ids': [(6, 0, brief_lines.ids)], 'default_parent_id': self.id, } } @api.depends( 'collected_target_april', 'collected_target_may', 'collected_target_june', 'collected_target_july', 'collected_target_august', 'collected_target_september', 'collected_target_october', 'collected_target_november', 'collected_target_december', 'collected_target_january', 'collected_target_february', 'collected_target_march' ) def _compute_ytd_collected(self): month_map = [ 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', 'january', 'february', 'march' ] current_month = date.today().month # In FY April to March, January=10th index if current_month >= 4: months_to_include = month_map[:current_month - 4 + 1] else: months_to_include = month_map[:current_month + 9 + 1] for rec in self: total = 0.0 for m in months_to_include: total += getattr(rec, f'collected_target_{m}', 0.0) rec.ytd_collected = total @api.depends( 'billed_target_april', 'billed_target_may', 'billed_target_june', 'billed_target_july', 'billed_target_august', 'billed_target_september', 'billed_target_october', 'billed_target_november', 'billed_target_december', 'billed_target_january', 'billed_target_february', 'billed_target_march' ) def _compute_ytd_billed(self): month_map = [ 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', 'january', 'february', 'march' ] current_month = date.today().month # In FY April to March, January=10th index if current_month >= 4: months_to_include = month_map[:current_month - 4 + 1] else: months_to_include = month_map[:current_month + 9 + 1] for rec in self: total = 0.0 for m in months_to_include: total += getattr(rec, f'billed_target_{m}', 0.0) rec.ytd_billed = total @api.depends( 'achievement_percentage_april', 'achievement_percentage_may', 'achievement_percentage_june', 'achievement_percentage_july', 'achievement_percentage_august', 'achievement_percentage_september', 'achievement_percentage_october', 'achievement_percentage_november', 'achievement_percentage_december', 'achievement_percentage_january', 'achievement_percentage_february', 'achievement_percentage_march', ) def _compute_ytd_sales_percentage(self): month_map = [ 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', 'january', 'february', 'march' ] current_month = date.today().month if current_month >= 4: months_to_include = month_map[:current_month - 4 + 1] else: months_to_include = month_map[:current_month + 9 + 1] for rec in self: total = 0.0 count = 0 for m in months_to_include: raw = getattr(rec, f'achievement_percentage_{m}', '0').replace('%', '').strip() try: val = float(raw) total += val count += 1 except (ValueError, TypeError): continue rec.ytd_sales_percentage = round(total / count, 2) if count else 0.0 @api.depends( 'actual_target_april', 'actual_target_may', 'actual_target_june', 'actual_target_july', 'actual_target_august', 'actual_target_september', 'actual_target_october', 'actual_target_november', 'actual_target_december', 'actual_target_january', 'actual_target_february', 'actual_target_march' ) def _compute_ytd_sales_target(self): month_map = [ 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', 'january', 'february', 'march' ] current_month = date.today().month # In FY April to March, January=10th index if current_month >= 4: months_to_include = month_map[:current_month - 4 + 1] else: months_to_include = month_map[:current_month + 9 + 1] for rec in self: total = 0.0 for m in months_to_include: total += getattr(rec, f'actual_target_{m}', 0.0) rec.ytd_sales = total @api.depends( 'planned_target_april', 'planned_target_may', 'planned_target_june', 'planned_target_july', 'planned_target_august', 'planned_target_september', 'planned_target_october', 'planned_target_november', 'planned_target_december', 'planned_target_january', 'planned_target_february', 'planned_target_march' ) def _compute_ytd_target(self): month_map = [ 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', 'january', 'february', 'march' ] current_month = date.today().month # In FY April to March, January=10th index if current_month >= 4: months_to_include = month_map[:current_month - 4 + 1] else: months_to_include = month_map[:current_month + 9 + 1] for rec in self: total = 0.0 for m in months_to_include: total += getattr(rec, f'planned_target_{m}', 0.0) rec.ytd_target = total @api.depends_context('uid') def _compute_can_edit_billed_target(self): for record in self: record.can_edit_billed_target = ( self.env.user.has_group('sos_inventory.sos_finance_user') or self.env.user.has_group('sos_inventory.sos_management_user') ) @api.depends( 'collected_target_april','collected_target_may','collected_target_june', 'collected_target_july','collected_target_august','collected_target_september', 'collected_target_october','collected_target_november','collected_target_december', 'collected_target_january','collected_target_february','collected_target_march' ) def _compute_collected_total_target(self): for record in self: record.collected_target_total = round(sum([ record.collected_target_april or 0, record.collected_target_may or 0, record.collected_target_june or 0, record.collected_target_july or 0, record.collected_target_august or 0, record.collected_target_september or 0, record.collected_target_october or 0, record.collected_target_november or 0, record.collected_target_december or 0, record.collected_target_january or 0, record.collected_target_february or 0, record.collected_target_march or 0 ]), 2) @api.depends( 'billed_target_april','billed_target_may','billed_target_june', 'billed_target_july','billed_target_august','billed_target_september', 'billed_target_october','billed_target_november','billed_target_december', 'billed_target_january','billed_target_february','billed_target_march' ) def _compute_billed_total_target(self): for record in self: record.billed_target_total = round(sum([ record.billed_target_april or 0, record.billed_target_may or 0, record.billed_target_june or 0, record.billed_target_july or 0, record.billed_target_august or 0, record.billed_target_september or 0, record.billed_target_october or 0, record.billed_target_november or 0, record.billed_target_december or 0, record.billed_target_january or 0, record.billed_target_february or 0, record.billed_target_march or 0 ]), 2) @api.depends('actual_target_total', 'planned_target_total') def _compute_achievement_percentage_total(self): for record in self: if record.planned_target_total != 0: record.achievement_percentage_total = f"{round((record.actual_target_total / record.planned_target_total) * 100, 2)}%" else: record.achievement_percentage_total = "0.00%" @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', 'yet_to_billed_target_october','yet_to_billed_target_november','yet_to_billed_target_december', 'yet_to_billed_target_january','yet_to_billed_target_february','yet_to_billed_target_march' ) def _compute_yet_to_billed_total_target(self): for record in self: # Get current month name in lowercase month_num = date.today().month month_name = calendar.month_name[month_num].lower() # e.g., "august" # Build the dynamic field name field_name = f"yet_to_billed_target_{month_name}" record.ytd_yet_to_billed = round(getattr(record, field_name) or 0, 2) record.yet_to_billed_target_total = round(getattr(record, field_name) or 0, 2) @api.depends( 'planned_target_april','planned_target_may','planned_target_june', 'planned_target_july','planned_target_august','planned_target_september', 'planned_target_october','planned_target_november','planned_target_december', 'planned_target_january','planned_target_february','planned_target_march' ) def _compute_planned_total_target(self): for record in self: record.planned_target_total = sum([ record.planned_target_april or 0, record.planned_target_may or 0, record.planned_target_june or 0, record.planned_target_july or 0, record.planned_target_august or 0, record.planned_target_september or 0, record.planned_target_october or 0, record.planned_target_november or 0, record.planned_target_december or 0, record.planned_target_january or 0, record.planned_target_february or 0, record.planned_target_march or 0 ]) @api.depends( 'actual_target_april','actual_target_may','actual_target_june', 'actual_target_july','actual_target_august','actual_target_september', 'actual_target_october','actual_target_november','actual_target_december', 'actual_target_january','actual_target_february','actual_target_march' ) def _compute_actual_total_target(self): for record in self: record.actual_target_total = sum([ record.actual_target_april or 0, record.actual_target_may or 0, record.actual_target_june or 0, record.actual_target_july or 0, record.actual_target_august or 0, record.actual_target_september or 0, record.actual_target_october or 0, record.actual_target_november or 0, record.actual_target_december or 0, record.actual_target_january or 0, record.actual_target_february or 0, record.actual_target_march or 0 ]) @api.depends( 'actual_target_april','actual_target_may','actual_target_june', 'actual_target_july','actual_target_august','actual_target_september', 'actual_target_october','actual_target_november','actual_target_december', 'actual_target_january','actual_target_february','actual_target_march' ) def _compute_achievement_percentage(self): # Loop over all months and calculate achievement percentage dynamically for record in self: for month in ['april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december', 'january', 'february', 'march']: planned_field = f"planned_target_{month}" actual_field = f"actual_target_{month}" achievement_field = f"achievement_percentage_{month}" planned_value = getattr(record, planned_field) actual_value = getattr(record, actual_field) if planned_value != 0: percentage = (actual_value / planned_value) * 100 setattr(record, achievement_field, f"{percentage:.2f}%") else: setattr(record, achievement_field, "0.00%") def _get_financial_year(self): current_date = date.today() 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 action_report_achievement_btn(self): try: action = self.env.ref("sos_sales.action_report_achievement").with_context(landscape=True).report_action(self) return action except ValueError as e: print(f"Failed to find report action: {e}") class SOS_Sales_Achievement_Report_Brief(models.Model): _name = 'sos_sales_achievement_report_brief' _description = 'Achievement Brief' _order = 'action_date desc' _rec_name = 'po_no' def get_financial_year_selection(self): current_date = date.today() start_year = 2024 # starting from FY 2024-2025 current_year = current_date.year if current_date.month >= 4 else current_date.year - 1 end_year = current_year + 1 selection = [] for y in range(start_year, current_year + 1): fy = f"FY {y}-{y+1}" selection.append((fy, fy)) return selection ref_id = fields.Many2one('sos_sales_achievement_report', string="Financial Year", ondelete="cascade") ref_record_id = fields.Integer(string="Reference") customer_name = fields.Many2one('sos_customers',string="Customer Name", required=True) action_date = fields.Date(string="PO Date") currency_id = fields.Many2one( 'res.currency', string='Currency', default=lambda self: self.env['res.currency'].search([('name', '=', 'INR')], limit=1).id or False ) financial_year = fields.Selection( selection=get_financial_year_selection, string="Financial Year", required=True ) category = fields.Selection([ ('sales', 'Sales'), ('export', 'Export') ], string="Category", required=True,related="ref_id.category") sales_person = fields.Many2one('res.users', string='Sales person',related="ref_id.sales_person",store=True) po_no = fields.Char(string="PO No") invoice_no = fields.Char(string="Invoice No") proposal_value = fields.Monetary(currency_field='currency_id',string="PO Value(In Lakhs)") billed_date = fields.Date(string="Billed Date") billed_amount = fields.Monetary(currency_field='currency_id',string="Billed Value(In Lakhs)") def delete_achievement_line(self): Env = self.env Line = Env['sos_sales_achievement_report_brief'] Parent = Env['sos_sales_achievement_report'] # 1) Collect affected month windows per parent (no sums yet) # { parent_id: set((cal_year, month_num)) } affected = {} for rec in self: # resolve parent id (prefer Many2one 'ref_id'; fallback to 'ref_record_id') parent_id = False if hasattr(rec, 'ref_id') and rec.ref_id: parent_id = rec.ref_id.id elif hasattr(rec, 'ref_record_id') and rec.ref_record_id: parent_id = getattr(rec.ref_record_id, 'id', rec.ref_record_id) if not parent_id: continue d = fields.Date.to_date(rec.action_date) or fields.Date.context_today(self) month_num = d.month # 1..12 fy_text = (rec.financial_year or '').strip() m = re.search(r'FY\s*(\d{4})', fy_text) if not m: # skip this line if FY is malformed continue fy_start = int(m.group(1)) # Apr–Dec => start year; Jan–Mar => start year+1 cal_year = fy_start if month_num >= 4 else fy_start + 1 affected.setdefault(parent_id, set()).add((cal_year, month_num)) # 2) Delete the lines first super(type(self), self).unlink() # 3) Recompute totals from remaining lines and write to parent for parent_id, months in affected.items(): write_vals = {} for cal_year, month_num in months: last_day = calendar.monthrange(cal_year, month_num)[1] start_str = f"{cal_year}-{month_num:02d}-01" end_str = f"{cal_year}-{month_num:02d}-{last_day:02d}" total = sum(Line.search([ ('ref_id', '=', parent_id), ('action_date', '>=', start_str), ('action_date', '<=', end_str), ]).mapped('proposal_value')) or 0.0 field_name = f"actual_target_{calendar.month_name[month_num].lower()}" if field_name in Parent._fields: write_vals[field_name] = total if write_vals: Parent.browse(parent_id).write(write_vals) return True 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): res = super().write(vals) # avoid infinite loop when we write our computed monthly field if self.env.context.get('_skip_month_compute'): return res for rec in self: # take incoming values if present, else current record values action_date_any = vals.get('action_date', rec.action_date) fy_text = (vals.get('financial_year', rec.financial_year) or '').strip() # normalize to date d = fields.Date.to_date(action_date_any) or fields.Date.context_today(self) month_num = d.month # 1..12 # FY like "FY 2025-2026" -> start year 2025 m = re.search(r'FY\s*(\d{4})', fy_text) if not m: raise ValueError(f"Unable to extract year from financial year: {fy_text}") fy_start = int(m.group(1)) # Apr–Dec belong to start year; Jan–Mar to start year+1 cal_year = fy_start if month_num >= 4 else fy_start + 1 # monthly date window last_day = calendar.monthrange(cal_year, month_num)[1] start_str = f"{cal_year}-{month_num:02d}-01" end_str = f"{cal_year}-{month_num:02d}-{last_day:02d}" # sum for this record in that month domain = [ ('ref_id', '=', self.ref_record_id), ('action_date', '>=', start_str), ('action_date', '<=', end_str), ] lines = rec.env['sos_sales_achievement_report_brief'].search(domain) total_proposal_value=0 for eachrecord in lines: total_proposal_value += eachrecord.proposal_value record = self.env['sos_sales_achievement_report'].search( [('id', '=', self.ref_record_id)], limit=1 ) month_name = calendar.month_name[month_num].lower() field_name = f"actual_target_{month_name}" field_name = f"actual_target_{month_name}" record.write({ field_name : total_proposal_value }) return res @api.model def create(self, vals): ref_id = vals.get('ref_id') vals['ref_record_id'] = ref_id action_date = vals.get('action_date') billed_date = vals.get('billed_date') customer_name = vals.get('customer_name') value = vals.get('proposal_value', 0.0) billed_value = vals.get('billed_amount', 0.0) ref_id = vals.get('ref_id') 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() if ref_id and action_date: report = self.env['sos_sales_achievement_report'].browse(ref_id) month_name = action_date.strftime('%B').lower() actual_field = f"actual_target_{month_name}" current_date = date.today() start_year = current_date.year if current_date.month >= 4 else current_date.year - 1 end_year = start_year + 1 current_fy = f"FY {start_year}-{end_year}" if vals.get('financial_year') != current_fy: report.write({'opening_balance': report.opening_balance + value}) if vals.get('financial_year') == current_fy: if hasattr(report, actual_field): current_val = getattr(report, actual_field) or 0.0 report.write({actual_field: current_val + value}) 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': billed_value, 'po_no':vals.get('po_no') }) new_record = super(SOS_Sales_Achievement_Report_Brief, self).create(vals) return new_record class SOS_Sales_Achievement_Report_Brief_Wizard(models.TransientModel): _name = 'sos_sales_achievement_report_brief_wizard' _description = 'Achievement Brief Wizard' parent_id = fields.Many2one('sos_sales_achievement_report', string="Sales Achievement Report") brief_line_ids = fields.Many2many( 'sos_sales_achievement_report_brief', relation='sos_brief_wizard_rel', # Custom table name 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' 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, 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) category = fields.Selection([ ('sales', 'Sales'), ('export', 'Export') ], string="Category", required=True,related="ref_id.category") 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_id = fields.Many2one( 'sos_sales_achievement_report_brief', # or 'sos_sales_achievement_report' if PO lives there string="PO No", domain="[('po_no','!=', False), ('customer_name', '=', customer_name)]", ) po_no = fields.Char(string="PO No", related='po_id.po_no', store=True, readonly=True) 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}" def delete_form_line(self): self.unlink() def write_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 unlink(self): for record in self: if record.date_of_action: fy = self._get_financial_year(record.date_of_action) month_name = record.date_of_action.strftime('%B').lower() else: fy = False month_name = False if fy and month_name: actual_field_name = f"{record.action_status.lower()}_target_{month_name}" # Fetch related record where field update should happen (example logic) report = self.env['sos_sales_achievement_report'].search([ ('financial_year', '=', fy), ('sales_person', '=', record.sales_person.id) ], limit=1) if report and hasattr(report, actual_field_name): current_val = getattr(report, actual_field_name, 0.0) setattr(report, actual_field_name, current_val - record.value) return super(SosBillingCollection, self).unlink() def write(self, vals): for record in self: # Capture old values old_date = record.date_of_action old_status = record.action_status old_value = record.value # Determine if any key field is changing new_date = vals.get('date_of_action', old_date) new_status = vals.get('action_status', old_status) new_value = vals.get('value', old_value) # Convert date if needed if isinstance(new_date, str): new_date = datetime.strptime(new_date, '%Y-%m-%d').date() # Compute old and new financial year/month old_fy = self._get_financial_year(old_date) if old_date else None old_month = old_date.strftime('%B').lower() if old_date else None new_fy = self._get_financial_year(new_date) if new_date else None new_month = new_date.strftime('%B').lower() if new_date else None # Build field names old_field = f"{old_status.lower()}_target_{old_month}" if old_month else None new_field = f"{new_status.lower()}_target_{new_month}" if new_month else None # Fetch related report years = [fy for fy in [old_fy, new_fy] if fy] report = self.env['sos_sales_achievement_report'].search([ ('sales_person', '=', record.sales_person.id), ('financial_year', 'in', years) ], limit=1) if report: # Subtract old value if old_field and hasattr(report, old_field): current_old = getattr(report, old_field, 0.0) or 0.0 setattr(report, old_field, current_old - old_value) # Add new value if new_field and hasattr(report, new_field): current_new = getattr(report, new_field, 0.0) or 0.0 setattr(report, new_field, current_new + new_value) # Proceed with actual write return super(SosBillingCollection, self).write(vals) @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