Advertisement
SherinKR

pradan_budget.py

Mar 2nd, 2024
826
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.33 KB | Source Code | 0 0
  1. # Copyright (c) 2023, New Indictranstech and contributors
  2. # For license information, please see license.txt
  3.  
  4. import frappe
  5. import json
  6. from pradan.pradan.utils import *
  7. from frappe.utils import getdate, add_years, add_days
  8. from frappe.model.document import Document
  9.  
  10. class PradanBudget(Document):
  11.     def validate(self):
  12.         if self.project:
  13.             is_able_to_create_budget(self.project, 1)
  14.         self.check_duplicate_projects()
  15.         self.calculate_totals()
  16.         self.validate_totals()
  17.  
  18.     def after_insert(self):
  19.         self.set_initial_values()
  20.         self.generate_allocation_rows()
  21.         self.generate_staff_and_project_wise_salary()
  22.  
  23.     def before_submit(self):
  24.         self.set_allocations()
  25.  
  26.     def on_update_after_submit(self):
  27.         self.set_allocations()
  28.         self.calculate_totals()
  29.         self.validate_totals()
  30.  
  31.     def set_allocations(self):
  32.         for row in self.allocations:
  33.             budget_item_entry = create_or_update_budget_item_entry(row)
  34.             if not row.budget_item_entry:
  35.                 row.budget_item_entry = budget_item_entry
  36.                 row.db_set('budget_item_entry', budget_item_entry)
  37.  
  38.     def on_trash(self):
  39.         for row in self.allocations:
  40.             if row.budget_item_entry:
  41.                 frappe.db.delete('Pradan Budget Item Entry', row.budget_item_entry)
  42.         frappe.db.commit()
  43.  
  44.     def on_submit(self):
  45.         if not self.generated:
  46.             frappe.throw('Set availability before submit')
  47.  
  48.     def check_duplicate_projects(self):
  49.         budget_id = get_budget_from_project(self.project)
  50.         if budget_id and budget_id!= self.name:
  51.             frappe.throw('Budget already created for this Project')
  52.  
  53.     @frappe.whitelist()
  54.     def generate_allocation_rows(self):
  55.         self.allocations = []
  56.         for fy_year in self.fy_wise_breakup:
  57.             for ledger_row in self.ledger_mapping:
  58.                 row = self.append('allocations')
  59.                 row.project = self.project
  60.                 row.budget_item = ledger_row.budget_line_item
  61.                 row.sub_budget_item = ledger_row.sub_budget_line_item
  62.                 row.fiscal_year = fy_year.fiscal_year
  63.                 row.available_amount = 0
  64.         self.year_wise_allocation = []
  65.         for year_wise_alloc in self.year_wise_breakup:
  66.             for ledger_row in self.ledger_mapping:
  67.                 row = self.append('year_wise_allocation')
  68.                 row.project = self.project
  69.                 row.budget_item = ledger_row.budget_line_item
  70.                 row.sub_budget_item = ledger_row.sub_budget_line_item
  71.                 row.from_date = year_wise_alloc.from_date
  72.                 row.to_date = year_wise_alloc.to_date
  73.                 row.available_amount = 0
  74.         self.generated = 1
  75.         self.save()
  76.  
  77.     def set_initial_values(self):
  78.         if self.project:
  79.             teams = get_project_teams(self.project)
  80.             self.teams = []
  81.             for team in teams:
  82.                 self.append('teams', {'teams':team})
  83.             budget_items = get_budget_items(self.project)
  84.             self.ledger_mapping = []
  85.             for budget_item in budget_items:
  86.                 row = self.append('ledger_mapping')
  87.                 row.budget_line_item = budget_item.budget_line_item
  88.                 row.sub_budget_line_item = budget_item.sub_budget_line_item
  89.                 row.year_maximum = 0
  90.                 row.fy_maximum = 0
  91.                 row.year_total = 0
  92.                 row.fy_total = 0
  93.  
  94.         if self.year_wise_breakdown_count:
  95.             year_wise_breakups = get_year_wise_breakup_from_project(self.project)
  96.             self.year_wise_breakup = []
  97.             for year_wise_breakup in year_wise_breakups:
  98.                 self.append('year_wise_breakup',{
  99.                     'from_date': year_wise_breakup.from_date,
  100.                     'to_date': year_wise_breakup.to_date,
  101.                     'amount': 0
  102.                 })
  103.         if self.project_start_date and self.project_end_date:
  104.             fiscal_years = get_fiscal_years(self.project_start_date, self.project_end_date)
  105.             self.fy_wise_breakup = []
  106.             for fy_year in fiscal_years:
  107.                 row = self.append('fy_wise_breakup')
  108.                 row.fiscal_year = fy_year
  109.                 row.amount = 0
  110.  
  111.     def calculate_fy_totals(self):
  112.         fy_total = 0
  113.         for row in self.fy_wise_breakup:
  114.             amount = get_fy_total(self.name, row.fiscal_year)
  115.             row.amount = amount
  116.             row.db_set('amount', amount)
  117.             fy_total += amount
  118.         self.fy_wise_total = fy_total
  119.         self.fy_wise_balance = self.budget_size - fy_total
  120.         self.db_set('fy_wise_total', fy_total)
  121.         self.db_set('fy_wise_balance', self.budget_size - fy_total)
  122.  
  123.     def calculate_year_totals(self):
  124.         year_total = 0
  125.         for row in self.year_wise_breakup:
  126.             amount = get_year_total(self.name, row.from_date, row.to_date)
  127.             row.amount = amount
  128.             row.db_set('amount', amount)
  129.             year_total += amount
  130.         self.year_wise_total = year_total
  131.         self.year_wise_balance = self.budget_size - year_total
  132.         self.db_set('year_wise_total', year_total)
  133.         self.db_set('year_wise_balance', self.budget_size - year_total)
  134.  
  135.     def validate_totals(self):
  136.         if not self.budget_size:
  137.             frappe.throw('Please define the Budget Size at Project level.')
  138.         if self.budget_size < self.fy_wise_total:
  139.             frappe.throw('Total FY wise budget exceeded than Budget Size.')
  140.         if self.budget_size < self.year_wise_total:
  141.             frappe.throw('Total Year wise budget exceeded than Budget Size.')
  142.         if self.budget_size < self.total_fy_maximum:
  143.             frappe.throw('Total FY maximum budget exceeded than Budget Size.')
  144.         if self.budget_size < self.total_year_maximum:
  145.             frappe.throw('Total year maximum budget exceeded than Budget Size.')
  146.  
  147.     def calculate_totals(self):
  148.         self.calculate_fy_totals()
  149.         self.calculate_year_totals()
  150.         values = get_year_and_fy_maximum(self.name)
  151.         self.total_year_maximum = values.total_year_max
  152.         self.total_fy_maximum = values.total_fy_max
  153.         self.db_set('total_year_maximum', values.total_year_max)
  154.         self.db_set('total_fy_maximum', values.total_fy_max)
  155.         for row in self.ledger_mapping:
  156.             year_total = float(get_item_wise_year_total(self.name, row.sub_budget_line_item))
  157.             fy_total = float(get_item_wise_fy_total(self.name, row.sub_budget_line_item))
  158.             if row.year_maximum and year_total:
  159.                 if float(row.year_maximum) < year_total:
  160.                     frappe.throw('Total year wise budget exceeded than maximum assigned budget for `Row: {0}`'.format(row.idx))
  161.             if row.fy_maximum and fy_total:
  162.                 if float(row.fy_maximum) < fy_total:
  163.                     frappe.throw('Total fy wise budget exceeded than maximum assigned budget for `Row: {0}`'.format(row.idx))
  164.             row.year_total = get_item_wise_year_total(self.name, row.sub_budget_line_item)
  165.             row.fy_total = get_item_wise_fy_total(self.name, row.sub_budget_line_item)
  166.             row.db_set('year_total', get_item_wise_year_total(self.name, row.sub_budget_line_item))
  167.             row.db_set('fy_total', get_item_wise_fy_total(self.name, row.sub_budget_line_item))
  168.  
  169.     def generate_staff_and_project_wise_salary(self):
  170.         if self.teams and self.fy_wise_breakup:
  171.             for team in self.teams:
  172.                 for fy in self.fy_wise_breakup:
  173.                     if not frappe.db.exists('Staff and Project wise Salary', { 'teams':team.teams, 'fiscal_year':fy.fiscal_year }):
  174.                         staff_and_project_wise_salary = frappe.new_doc('Staff and Project wise Salary')
  175.                         staff_and_project_wise_salary.teams = team.teams
  176.                         staff_and_project_wise_salary.fiscal_year = fy.fiscal_year
  177.                         staff_and_project_wise_salary.insert()
  178.  
  179.     def on_cancel(self):
  180.         if self.workflow_state == 'Rejected' and not self.rejection_feedback:
  181.             frappe.throw("Please add Rejection Feedback prior to Reject the Budget")
  182.  
  183. def create_or_update_budget_item_entry(args):
  184.     '''
  185.         Method to create or update budget item entry
  186.     '''
  187.     if args.budget_item_entry:
  188.         budget_item_entry_doc = frappe.get_doc('Pradan Budget Item Entry', args.budget_item_entry)
  189.     else:
  190.         budget_item_entry_doc = frappe.new_doc('Pradan Budget Item Entry')
  191.     budget_item_entry_doc.pradan_budget = args.parent
  192.     budget_item_entry_doc.project = args.project
  193.     budget_item_entry_doc.budget_item = args.budget_item
  194.     budget_item_entry_doc.sub_budget_item = args.sub_budget_item
  195.     budget_item_entry_doc.project_duration = args.project_duration
  196.     budget_item_entry_doc.fiscal_year = args.fiscal_year
  197.     budget_item_entry_doc.available_amount = args.available_amount
  198.     budget_item_entry_doc.allocated_amount = 0
  199.     budget_item_entry_doc.balance_amount = args.available_amount
  200.     budget_item_entry_doc.flags.ignore_mandatory = True
  201.     budget_item_entry_doc.save(ignore_permissions=True)
  202.     return budget_item_entry_doc.name
  203.  
  204. @frappe.whitelist()
  205. def get_team_wise_budget(project, available_amount, sub_budget_item, fiscal_year, natural_head=None):
  206.     '''
  207.         Method to get default team and will equaly distribute amount
  208.     '''
  209.     # salary_natural_heads = ['Stipend to Apprentices', 'Salary & Benefits - Contractual Staff', 'Salary & Benefits - Associates', 'Salary & Benefits - Professionals']
  210.     budget_id = get_budget_from_project(project)
  211.     budget_item_entry = get_budget_item_entry(budget_id, sub_budget_item, fiscal_year)
  212.     available_amount = float(available_amount)
  213.     data = { 'is_salary_head':0, 'team_wise_budget':[] }
  214.     total_teams, team_amount = 0, 0
  215.     if budget_id and budget_item_entry:
  216.         if not frappe.db.exists('Project', project):
  217.             frappe.throw('Project `{0}` not found'.format(project))
  218.         project_teams = frappe.db.get_all('Project Teams', filters={ 'parent':project }, fields=['name', 'teams'])
  219.         budget_teams = frappe.db.get_all('Team wise Budget', filters={ 'parent':budget_item_entry }, fields=['name', 'teams', 'allocated_amount', 'remarks'])
  220.  
  221.         if budget_teams:
  222.             for team in budget_teams:
  223.                 data['team_wise_budget'].append({'project':project, 'teams': team.teams, 'allocated_amount':team.allocated_amount, 'remarks':team.remarks })
  224.         elif project_teams:
  225.             total_teams = len(project_teams)
  226.             team_amount = available_amount/total_teams
  227.             for team in project_teams:
  228.                 data['team_wise_budget'].append({'project':project, 'teams': team.teams, 'allocated_amount':team_amount, 'remarks':team.remarks })
  229.     return data
  230.  
  231. @frappe.whitelist()
  232. def allocate_budget_to_teams(project, sub_budget_item, fiscal_year, values):
  233.     '''
  234.         Method to make the string in to json and allocate to each teams
  235.     '''
  236.     budget_id = get_budget_from_project(project)
  237.     budget_item_entry = get_budget_item_entry(budget_id, sub_budget_item, fiscal_year)
  238.     data = json.loads(values)
  239.     if not frappe.db.exists('Pradan Budget Item Entry', budget_item_entry):
  240.         frappe.throw('Budget Item Entry does not exists')
  241.     if data.get('team_wise_budget'):
  242.         team_wise_budget = data.get('team_wise_budget')
  243.         budget_item_entry_doc = frappe.get_doc('Pradan Budget Item Entry', budget_item_entry)
  244.         old_data = budget_item_entry_doc.teams
  245.         budget_item_entry_doc.teams = []
  246.         idx = 0
  247.         if old_data:
  248.             message = '<div><p>{0} has made changes as follows</p>'.format(frappe.session.user)
  249.         else:
  250.             message = False
  251.         for row in team_wise_budget:
  252.             team_wise_budget_row = budget_item_entry_doc.append('teams')
  253.             team_wise_budget_row.budget_code = budget_item_entry_doc.name
  254.             team_wise_budget_row.teams = row.get('teams')
  255.             team_wise_budget_row.allocated_amount = row.get('allocated_amount')
  256.             team_wise_budget_row.used_amount = 0
  257.             team_wise_budget_row.remarks = row.get('remarks')
  258.             if old_data:
  259.                 old_amount = 0
  260.                 if len(old_data)>=idx+1:
  261.                     old_amount = old_data[idx].get('allocated_amount')
  262.                 new_amount = row.get('allocated_amount') or 0
  263.                 teams = row.get('teams')
  264.                 message += '<p>{0}. Allocated Amount has been changed from {1} to {2} for Teams : {3}</p>'.format(idx+1, int(old_amount), int(new_amount), teams)
  265.             idx += 1
  266.         budget_item_entry_doc.save(ignore_permissions=True)
  267.         if message:
  268.             message += '</div>'
  269.             budget_item_entry_doc.add_comment("Comment", text=message)
  270.         mark_as_allocated(budget_item_entry)
  271.     return 1
  272.  
  273. @frappe.whitelist()
  274. def mark_as_allocated(budget_item_entry):
  275.     '''
  276.         Method to make the item row as allocated
  277.     '''
  278.     if frappe.db.exists('Pradan Budget Items', { 'budget_item_entry':budget_item_entry }):
  279.         name = frappe.db.get_value('Pradan Budget Items', { 'budget_item_entry':budget_item_entry })
  280.         frappe.db.set_value('Pradan Budget Items', name, 'allocated', 1)
  281.         frappe.db.commit()
  282.         frappe.msgprint(msg='Budget allocated successfully', indicator='green', alert=True)
  283.  
  284. def get_fy_total(budget_id, fiscal_year):
  285.     '''
  286.         Method to get total amount made availble for a fiscal year for all items
  287.     '''
  288.     total = 0
  289.     query = '''
  290.         SELECT
  291.             IFNULL(SUM(available_amount), 0) as total
  292.         FROM
  293.             `tabPradan Budget Items`
  294.         WHERE
  295.             parent = "{0}" AND
  296.             fiscal_year = "{1}"
  297.     '''
  298.     output=frappe.db.sql(query.format(budget_id, fiscal_year), as_list=1)
  299.     if output:
  300.         total = output[0][0]
  301.     return total
  302.  
  303. def get_year_total(budget_id, from_date, to_date):
  304.     '''
  305.         Method to get total amount made availble for a fiscal year for all items
  306.     '''
  307.     total = 0
  308.     query = '''
  309.         SELECT
  310.             IFNULL(SUM(available_amount), 0) as total
  311.         FROM
  312.             `tabYear wise Allocation`
  313.         WHERE
  314.             parent = "{0}" AND
  315.             from_date = DATE("{1}") AND
  316.             to_date = DATE("{2}")
  317.     '''
  318.     output=frappe.db.sql(query.format(budget_id, getdate(from_date), getdate(to_date)), as_list=1)
  319.     if output:
  320.         total = output[0][0]
  321.     return total
  322.  
  323.  
  324. def get_year_and_fy_maximum(budget_id):
  325.     '''
  326.         Method to get total year_max and fy_max value
  327.     '''
  328.     values = { 'total_year_max':0, 'fy_maximum':0 }
  329.     query = '''
  330.         SELECT
  331.             IFNULL(SUM(year_maximum), 0) as total_year_max,
  332.             IFNULL(SUM(fy_maximum), 0) as total_fy_max
  333.         FROM
  334.             `tabLedger Mapping`
  335.         WHERE
  336.             parent = "{0}"
  337.     '''
  338.     output=frappe.db.sql(query.format(budget_id), as_dict=1)
  339.     if output:
  340.         values = output[0]
  341.     return values
  342.  
  343.  
  344. def get_item_wise_fy_total(budget_id, sub_budget_item):
  345.     '''
  346.         Method to get FY wise total amount made availble for a sub budget item
  347.     '''
  348.     total = 0
  349.     query = '''
  350.         SELECT
  351.             IFNULL(SUM(available_amount), 0) as total
  352.         FROM
  353.             `tabPradan Budget Items`
  354.         WHERE
  355.             parent = "{0}" AND
  356.             sub_budget_item = "{1}"
  357.     '''
  358.     output=frappe.db.sql(query.format(budget_id, sub_budget_item), as_list=1)
  359.     if output:
  360.         total = output[0][0]
  361.     return total
  362.  
  363. def get_item_wise_year_total(budget_id, sub_budget_item):
  364.     '''
  365.         Method to get year wise total amount made availble for a sub budget item
  366.     '''
  367.     total = 0
  368.     query = '''
  369.         SELECT
  370.             IFNULL(SUM(available_amount), 0) as total
  371.         FROM
  372.             `tabYear wise Allocation`
  373.         WHERE
  374.             parent = "{0}" AND
  375.             sub_budget_item = "{1}"
  376.     '''
  377.     output=frappe.db.sql(query.format(budget_id, sub_budget_item), as_list=1)
  378.     if output:
  379.         total = output[0][0]
  380.     return total
  381.  
  382.  
  383. @frappe.whitelist()
  384. def get_budget_item_entry(budget_id, sub_budget_item, fiscal_year):
  385.     '''
  386.         Method to get Budget Item Entry for a sub budget item wrt fiscal_year
  387.     '''
  388.     budget_item_entry = False
  389.     if frappe.db.exists('Pradan Budget Items', { 'parent':budget_id, 'sub_budget_item':sub_budget_item, 'fiscal_year':fiscal_year }):
  390.         budget_item_entry = frappe.db.get_value('Pradan Budget Items', { 'parent':budget_id, 'sub_budget_item':sub_budget_item, 'fiscal_year':fiscal_year }, 'budget_item_entry')
  391.     return budget_item_entry
  392.  
  393. @frappe.whitelist()
  394. def is_able_to_create_budget(project, throw_error=0):
  395.     '''
  396.         Mehtod to check the availability to create budget from project
  397.     '''
  398.     able_to_create = 1
  399.     if frappe.db.exists('Project', project):
  400.         #Directly return 0 if budget already created
  401.         budget_id = get_budget_from_project(project)
  402.         if frappe.db.exists('Pradan Budget', budget_id):
  403.             return 0
  404.         project_size, project_start_date = frappe.db.get_value('Project', project, ['budget_size', 'project_start_date'])
  405.         project_end_date, project_periodduration = frappe.db.get_value('Project', project, ['project_end_date', 'project_periodduration'])
  406.         #Check project size
  407.         if not project_size:
  408.             if throw_error:
  409.                 frappe.throw('Please set Budget size')
  410.             else:
  411.                 able_to_create = 0
  412.         #Check project start date
  413.         if not project_start_date:
  414.             if throw_error:
  415.                 frappe.throw('Please set project start date')
  416.             else:
  417.                 able_to_create = 0
  418.         #Check project end date
  419.         if not project_end_date:
  420.             if throw_error:
  421.                 frappe.throw('Please set project end date!')
  422.             else:
  423.                 able_to_create = 0
  424.         #Check project duration
  425.         if not project_periodduration:
  426.             if throw_error:
  427.                 frappe.throw('Please set project duration!')
  428.             else:
  429.                 able_to_create = 0
  430.         #Check budget line items
  431.         if not frappe.db.exists('Budget Line Items', { 'parent':project }):
  432.             if throw_error:
  433.                 frappe.throw('Please create budget line items')
  434.             else:
  435.                 able_to_create = 0
  436.         #Check teams
  437.         if not frappe.db.exists('Project Teams', { 'parent':project }):
  438.             if throw_error:
  439.                 frappe.throw('Please set teams against this project')
  440.             else:
  441.                 able_to_create = 0
  442.         #Check Fiscal Years
  443.         missing_fy = get_missing_fiscal_years(project_start_date, project_end_date)
  444.         if missing_fy:
  445.             if throw_error:
  446.                 frappe.throw('Please create missing fiscal years : {0}'.format(missing_fy))
  447.             else:
  448.                 able_to_create = 0
  449.     else:
  450.         if throw_error:
  451.             frappe.throw('Project `{0}` not found', project)
  452.         else:
  453.             able_to_create = 0
  454.     return able_to_create
  455.  
  456. def get_year_wise_breakup_from_project(project):
  457.     '''
  458.         Method to get Year wise breakup from Project
  459.     '''
  460.     year_wise_breakup = []
  461.     if frappe.db.exists('Year Wise Breakdown', { 'parent':project, 'parentfield':'year_wise_breakdown', 'parenttype':'Project' }):
  462.         year_wise_breakup = frappe.db.get_all('Year Wise Breakdown', filters={ 'parent':project, 'parentfield':'year_wise_breakdown', 'parenttype':'Project' }, fields=['from_date', 'to_date'], order_by='idx asc')
  463.     return year_wise_breakup
  464.  
  465. @frappe.whitelist()
  466. def reject_with_feedback(budget_id, feedback):
  467.     '''
  468.         Method to Reject the budget with feedback
  469.     '''
  470.     if frappe.db.exists('Pradan Budget', budget_id) and feedback:
  471.         frappe.db.set_value('Pradan Budget', budget_id, 'rejection_feedback', feedback)
  472.         frappe.db.set_value('Pradan Budget', budget_id, 'workflow_state', 'Rejected')
  473.         frappe.get_doc('Pradan Budget', budget_id).cancel()
  474.     return 1
  475.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement