Slink/sos_sales/models/sos_sales_achievement_repor...

1067 lines
48 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))
# AprDec => start year; JanMar => 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))
# AprDec belong to start year; JanMar 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