367 lines
No EOL
14 KiB
Python
367 lines
No EOL
14 KiB
Python
import pandas as pd
|
|
import xlsxwriter
|
|
|
|
# --- 1. SETUP THE DATA (Effective June 2025) ---
|
|
ANNUAL_FACTOR = 26
|
|
|
|
# Stipend Rates (Base Annual Amounts)
|
|
stipend_data = {
|
|
'Key': [
|
|
'Urban_Ordained Minister', 'Provincial_Ordained Minister', 'Rural_Ordained Minister', 'Remote_Ordained Minister',
|
|
'Urban_Home Missionary', 'Provincial_Home Missionary', 'Rural_Home Missionary', 'Remote_Home Missionary',
|
|
'Urban_Specialised Ministry Worker', 'Provincial_Specialised Ministry Worker', 'Rural_Specialised Ministry Worker', 'Remote_Specialised Ministry Worker'
|
|
],
|
|
# Annual Figures (Fortnightly x 26)
|
|
'Cash': [41782, 42627, 43459, 45136, 37193, 37934, 38675, 40170, 31343, 31967, 32591, 33852],
|
|
'EPFB': [41782, 42627, 43459, 45136, 37193, 37934, 38675, 40170, 31343, 31967, 32591, 33852],
|
|
'MEA': [12974, 14040, 18460, 19864, 12974, 14040, 18460, 19864, 12974, 14040, 18460, 19864]
|
|
}
|
|
|
|
# Manse Deduction (Annual)
|
|
manse_data = {
|
|
'Zone': ['Urban', 'Provincial', 'Rural', 'Remote'],
|
|
'Deduction': [26104, 26104, 24388, 24388]
|
|
}
|
|
|
|
df_rates = pd.DataFrame(stipend_data)
|
|
df_manse = pd.DataFrame(manse_data)
|
|
|
|
# --- 2. CREATE THE EXCEL FILE ---
|
|
filename = 'PCQ_Form_B_Final_Fixed.xlsx'
|
|
writer = pd.ExcelWriter(filename, engine='xlsxwriter')
|
|
workbook = writer.book
|
|
|
|
# --- 3. FORMATS ---
|
|
fmt_header = workbook.add_format({'bold': True, 'bg_color': '#4F81BD', 'font_color': 'white', 'border': 1})
|
|
fmt_subhead = workbook.add_format({'bold': True, 'bg_color': '#DCE6F1', 'border': 1})
|
|
fmt_subhead_c = workbook.add_format({'bold': True, 'bg_color': '#DCE6F1', 'border': 1, 'align': 'center'})
|
|
fmt_input = workbook.add_format({'bg_color': '#FFF2CC', 'border': 1})
|
|
fmt_locked = workbook.add_format({'bg_color': '#E7E6E6', 'border': 1})
|
|
fmt_currency_locked = workbook.add_format({'num_format': '$#,##0', 'bg_color': '#E7E6E6', 'border': 1})
|
|
fmt_currency_input = workbook.add_format({'num_format': '$#,##0', 'bg_color': '#FFF2CC', 'border': 1})
|
|
fmt_percent = workbook.add_format({'num_format': '0%', 'bg_color': '#FFF2CC', 'border': 1})
|
|
fmt_int_input = workbook.add_format({'num_format': '0', 'bg_color': '#FFF2CC', 'border': 1})
|
|
fmt_total = workbook.add_format({'num_format': '$#,##0', 'bold': True, 'bg_color': '#D9D9D9', 'border': 1})
|
|
fmt_result = workbook.add_format({'num_format': '$#,##0;[Red]($#,##0)', 'bold': True, 'font_size': 12, 'border': 1})
|
|
fmt_title = workbook.add_format({'bold': True, 'font_size': 14})
|
|
fmt_deduction = workbook.add_format({'num_format': '-$#,##0', 'bg_color': '#E7E6E6', 'font_color': '#9C0006', 'border': 1})
|
|
|
|
# --- 4. SHEET: "Hidden_Data" ---
|
|
df_rates.to_excel(writer, sheet_name='Hidden_Data', index=False)
|
|
df_manse.to_excel(writer, sheet_name='Hidden_Data', startcol=6, index=False)
|
|
writer.sheets['Hidden_Data'].hide()
|
|
|
|
# --- 5. SHEET: "Form B Calculator" ---
|
|
ws = workbook.add_worksheet('Form B Calculator')
|
|
ws.set_column('A:A', 5)
|
|
ws.set_column('B:B', 45)
|
|
ws.set_column('C:C', 25)
|
|
ws.set_column('D:D', 40)
|
|
|
|
# Title
|
|
ws.write('B2', 'MINISTRY SUPPORT FUND SCHEDULE (FORM B) - 2025 CALCULATOR', fmt_title)
|
|
|
|
# === SECTION 1: APPOINTMENT DETAILS ===
|
|
ws.write('B4', '1. APPOINTMENT DETAILS', fmt_header)
|
|
ws.write('C4', 'SELECTION', fmt_header)
|
|
ws.write('D4', 'NOTES', fmt_header)
|
|
|
|
r1 = 4
|
|
|
|
# Lists
|
|
list_zones = {'validate': 'list', 'source': ['Urban', 'Provincial', 'Rural', 'Remote']}
|
|
list_roles = {'validate': 'list', 'source': ['Ordained Minister', 'Home Missionary', 'Specialised Ministry Worker']}
|
|
list_manse_type = {'validate': 'list', 'source': ['No Manse', 'Church Owned Manse', 'Church Rented Manse']}
|
|
list_yesno = {'validate': 'list', 'source': ['Yes', 'No']}
|
|
list_energy = {'validate': 'list', 'source': ['Appointee Pays', 'Church Pays 100%', 'Fixed Amount (Paid by Church)']}
|
|
|
|
# -- Basic Details --
|
|
ws.write(r1+1, 1, 'Charge Name', fmt_locked)
|
|
ws.write(r1+1, 2, '', fmt_input)
|
|
|
|
ws.write(r1+2, 1, 'Zone', fmt_locked)
|
|
ws.data_validation(r1+2, 2, r1+2, 2, list_zones)
|
|
ws.write(r1+2, 2, 'Provincial', fmt_input)
|
|
|
|
ws.write(r1+3, 1, 'Position', fmt_locked)
|
|
ws.data_validation(r1+3, 2, r1+3, 2, list_roles)
|
|
ws.write(r1+3, 2, 'Ordained Minister', fmt_input)
|
|
|
|
ws.write(r1+4, 1, 'Work Load %', fmt_locked)
|
|
ws.write(r1+4, 2, 1.0, fmt_percent)
|
|
ws.write(r1+4, 3, '100% = Full Time, 50% = Half Time')
|
|
|
|
# -- Loading --
|
|
ws.write(r1+5, 1, 'Stipend Loading % (Optional)', fmt_locked)
|
|
ws.write(r1+5, 2, 0.0, fmt_percent)
|
|
ws.write(r1+5, 3, '% Paid above minimum')
|
|
|
|
# -- Manse Details --
|
|
ws.write(r1+6, 1, 'Manse Arrangement', fmt_locked)
|
|
ws.data_validation(r1+6, 2, r1+6, 2, list_manse_type)
|
|
ws.write(r1+6, 2, 'No Manse', fmt_input)
|
|
|
|
# Guideline 210
|
|
ws.write(r1+7, 1, 'Does Manse meet Guideline 210?', fmt_locked)
|
|
ws.data_validation(r1+7, 2, r1+7, 2, list_yesno)
|
|
ws.write(r1+7, 2, 'Yes', fmt_input)
|
|
|
|
ws.write(r1+8, 1, 'If No, specify details:', fmt_locked)
|
|
ws.write(r1+8, 2, '', fmt_input)
|
|
|
|
# Energy
|
|
ws.write(r1+9, 1, 'Manse Energy Arrangement', fmt_locked)
|
|
ws.data_validation(r1+9, 2, r1+9, 2, list_energy)
|
|
ws.write(r1+9, 2, 'Appointee Pays', fmt_input)
|
|
|
|
ws.write(r1+10, 1, 'Est. Energy Cost (If Church Pays)', fmt_locked)
|
|
ws.write(r1+10, 2, 0, fmt_currency_input)
|
|
|
|
# Rental Cost
|
|
ws.write(r1+11, 1, 'Actual Rent Paid to Landlord', fmt_locked)
|
|
ws.write(r1+11, 2, 0, fmt_currency_input)
|
|
ws.write(r1+11, 3, 'Only if "Church Rented Manse" is selected')
|
|
|
|
# === SECTION 2: CALCULATED TERMS ===
|
|
r2 = r1 + 11 + 2
|
|
ws.write(r2, 1, '2. TERMS OF APPOINTMENT (Calculated)', fmt_header)
|
|
ws.write(r2, 2, 'AMOUNT (ANNUAL)', fmt_header)
|
|
|
|
r2_data = r2 + 1 # Start index for data rows
|
|
ws.write_formula('Z6', '=C7&"_"&C8') # Key
|
|
|
|
# --- RELATIVE ROW MAPPING (0-based offset from r2_data) ---
|
|
# 0: Cash Stipend
|
|
# 1: Header (EPFB Calc)
|
|
# 2: Gross EPFB
|
|
# 3: Manse Deduction
|
|
# 4: Net EPFB
|
|
# 5: Header (Allowances)
|
|
# 6: MEA
|
|
# 7: Super
|
|
# 8: Rent
|
|
# 9: Energy
|
|
# 10: Total
|
|
|
|
# --- BASE FORMULAS ---
|
|
f_load_factor = '(C9 * (1 + C10))'
|
|
f_cash_calc = f'=IFERROR(VLOOKUP(Z6, Hidden_Data!A:E, 2, FALSE) * {f_load_factor}, 0)'
|
|
f_epfb_calc = f'IFERROR(VLOOKUP(Z6, Hidden_Data!A:E, 3, FALSE) * {f_load_factor}, 0)'
|
|
f_manse_ded_calc = 'IF(C11<>"No Manse", IFERROR(VLOOKUP(C7, Hidden_Data!G:H, 2, FALSE), 0), 0)'
|
|
f_mea_calc = '=IFERROR(VLOOKUP(Z6, Hidden_Data!A:E, 4, FALSE) * C9, 0)'
|
|
|
|
# --- WRITE ROWS ---
|
|
|
|
# Row 0: Cash Stipend
|
|
ws.write(r2_data, 1, 'Cash Stipend', fmt_locked)
|
|
ws.write_formula(r2_data, 2, f_cash_calc, fmt_currency_locked)
|
|
|
|
# Row 1: Header
|
|
ws.write(r2_data+1, 1, '--- EPFB CALCULATION ---', fmt_subhead)
|
|
ws.write(r2_data+1, 2, '', fmt_subhead)
|
|
|
|
# Row 2: Gross EPFB
|
|
ws.write(r2_data+2, 1, 'Gross EPFB Entitlement', fmt_locked)
|
|
ws.write_formula(r2_data+2, 2, f_epfb_calc, fmt_currency_locked)
|
|
|
|
# Row 3: Manse Deduction
|
|
ws.write(r2_data+3, 1, 'Less: Manse Deduction', fmt_locked)
|
|
ws.write_formula(r2_data+3, 2, f_manse_ded_calc, fmt_deduction)
|
|
ws.write(r2_data+3, 3, '($26,104 Urban/Prov | $24,388 Rural)', fmt_locked)
|
|
|
|
# Row 4: Net EPFB (Row 2 minus Row 3) -> Excel Row indices are +1
|
|
# Formula uses Excel Index: C{r2_data + 3} - C{r2_data + 4} (Wait, Deduction is negative? No, formula subtraction)
|
|
# Let's check logic: Gross (Pos) - Ded (Pos).
|
|
# Cell C{r2_data+3} is Gross. Cell C{r2_data+4} is Deduction.
|
|
ws.write(r2_data+4, 1, 'Net EPFB Payable', workbook.add_format({'bold': True, 'bg_color': '#D9D9D9', 'border': 1}))
|
|
ws.write_formula(r2_data+4, 2, f'=MAX(0, C{r2_data+3} - C{r2_data+4})', fmt_currency_locked)
|
|
|
|
# Row 5: Header
|
|
ws.write(r2_data+5, 1, '--- ALLOWANCES & SUPER ---', fmt_subhead)
|
|
ws.write(r2_data+5, 2, '', fmt_subhead)
|
|
|
|
# Row 6: MEA
|
|
ws.write(r2_data+6, 1, 'Ministry Expense Allowance (MEA)', fmt_locked)
|
|
ws.write_formula(r2_data+6, 2, f_mea_calc, fmt_currency_locked)
|
|
|
|
# Row 7: Superannuation
|
|
# Logic: 15% * (Cash + EPFB)
|
|
# Cash is at Row 0 -> C{r2_data+1}
|
|
# EPFB is at Row 4 -> C{r2_data+2}
|
|
ws.write(r2_data+7, 1, 'Superannuation (15%)', fmt_locked)
|
|
ws.write_formula(r2_data+7, 2, f'=0.15 * (C{r2_data+1} + C{r2_data+3})', fmt_currency_locked)
|
|
|
|
# Row 8: Rent
|
|
ws.write(r2_data+8, 1, 'Manse Rent (Paid to Landlord)', fmt_locked)
|
|
ws.write_formula(r2_data+8, 2, '=IF(C11="Church Rented Manse", C16, 0)', fmt_currency_locked)
|
|
|
|
# Row 9: Energy
|
|
ws.write(r2_data+9, 1, 'Manse Energy (Paid by Church)', fmt_locked)
|
|
ws.write_formula(r2_data+9, 2, '=IF(C14="Appointee Pays", 0, C15)', fmt_currency_locked)
|
|
|
|
# Row 10: TOTAL
|
|
ws.write(r2_data+10, 1, 'TOTAL COST OF NEW POSITION', workbook.add_format({'bold': True, 'bg_color': '#D9D9D9', 'border': 1}))
|
|
# Sum specific cells: Cash(0), NetEPFB(4), MEA(6), Super(7), Rent(8), Energy(9)
|
|
# Add 1 to all indices for Excel format
|
|
ws.write_formula(r2_data+10, 2, f'=C{r2_data+1} + C{r2_data+5} + C{r2_data+7} + C{r2_data+8} + C{r2_data+9} + C{r2_data+10}', fmt_total)
|
|
|
|
row_total_cost = r2_data + 10 # Reference for later sections
|
|
|
|
# === SECTION 3: CHURCH STATISTICS ===
|
|
r3 = r2_data + 10 + 2
|
|
ws.write(r3, 1, '3. CHURCH STATISTICS (Form B - Sec A)', fmt_header)
|
|
ws.write(r3, 2, 'DATA', fmt_header)
|
|
|
|
r3_data = r3 + 1
|
|
ws.write(r3_data, 1, 'Preaching Place 1', fmt_locked)
|
|
ws.write(r3_data, 2, '', fmt_input)
|
|
ws.write(r3_data+1, 1, 'Preaching Place 2', fmt_locked)
|
|
ws.write(r3_data+1, 2, '', fmt_input)
|
|
ws.write(r3_data+2, 1, 'Preaching Place 3', fmt_locked)
|
|
ws.write(r3_data+2, 2, '', fmt_input)
|
|
ws.write(r3_data+3, 1, 'Avg Attendance (Last 6 Months)', fmt_locked)
|
|
ws.write(r3_data+3, 2, 0, fmt_int_input)
|
|
ws.write(r3_data+4, 1, 'Avg Giving Per Month (Last 6 Months)', fmt_locked)
|
|
ws.write(r3_data+4, 2, 0, fmt_currency_input)
|
|
|
|
# === SECTION 4: FINANCIAL POSITION ===
|
|
r4 = r3_data + 4 + 2
|
|
ws.write(r4, 1, '4. FINANCIAL POSITION (Form B - Sec B & C)', fmt_header)
|
|
ws.write(r4, 2, 'AMOUNT', fmt_header)
|
|
ws.write(r4, 3, 'INTEREST (Est)', fmt_header)
|
|
ws.write(r4, 4, 'TO REVENUE', fmt_header)
|
|
|
|
r4_curr = r4 + 1
|
|
ws.write(r4_curr, 1, 'ASSETS: Bank Balances & Cash', fmt_subhead)
|
|
ws.write(r4_curr, 2, '', fmt_subhead)
|
|
ws.write(r4_curr, 3, '', fmt_subhead)
|
|
ws.write(r4_curr, 4, '', fmt_subhead)
|
|
r4_curr += 1
|
|
|
|
for i in range(4):
|
|
ws.write(r4_curr, 1, f'Account {i+1} Name:', fmt_locked)
|
|
ws.write(r4_curr, 2, 0, fmt_currency_input)
|
|
r4_curr += 1
|
|
|
|
ws.write(r4_curr, 1, 'ASSETS: Investments / Trusts', fmt_subhead)
|
|
ws.write(r4_curr, 2, 'Capital Value', fmt_subhead_c)
|
|
ws.write(r4_curr, 3, 'Interest', fmt_subhead_c)
|
|
ws.write(r4_curr, 4, 'Used in Rev.', fmt_subhead_c)
|
|
r4_curr += 1
|
|
|
|
start_asset_row = r4_curr
|
|
for i in range(3):
|
|
ws.write(r4_curr, 1, f'Asset/Trust {i+1}:', fmt_locked)
|
|
ws.write(r4_curr, 2, 0, fmt_currency_input)
|
|
ws.write(r4_curr, 3, 0, fmt_currency_input)
|
|
ws.write(r4_curr, 4, 0, fmt_currency_input)
|
|
r4_curr += 1
|
|
end_asset_row = r4_curr - 1
|
|
|
|
ws.write(r4_curr, 1, 'TOTAL ASSETS', fmt_total)
|
|
ws.write_formula(r4_curr, 2, f'=SUM(C{r4+2}:C{r4_curr-1})', fmt_total)
|
|
r4_curr += 1
|
|
|
|
ws.write(r4_curr, 1, 'LIABILITIES (Debts & Arrears)', fmt_subhead)
|
|
ws.write(r4_curr, 2, '', fmt_subhead)
|
|
r4_curr += 1
|
|
start_liab = r4_curr
|
|
for item in ['Mortgage / Loan 1', 'Mortgage / Loan 2', 'Arrears: MSF', 'Arrears: Assessments', 'Other Debts']:
|
|
ws.write(r4_curr, 1, item, fmt_locked)
|
|
ws.write(r4_curr, 2, 0, fmt_currency_input)
|
|
r4_curr += 1
|
|
|
|
ws.write(r4_curr, 1, 'TOTAL LIABILITIES', fmt_total)
|
|
ws.write_formula(r4_curr, 2, f'=SUM(C{start_liab}:C{r4_curr-1})', fmt_total)
|
|
r4_last = r4_curr
|
|
|
|
# === SECTION 5: ESTIMATED REVENUE ===
|
|
r5 = r4_last + 2
|
|
ws.write(r5, 1, '5. ESTIMATED REVENUE (Form B - Sec E)', fmt_header)
|
|
ws.write(r5, 2, 'AMOUNT', fmt_header)
|
|
|
|
r5_data = r5 + 1
|
|
ws.write(r5_data, 1, 'Collections / Contributions', fmt_locked)
|
|
ws.write(r5_data, 2, 0, fmt_currency_input)
|
|
|
|
ws.write(r5_data+1, 1, 'Income from Assets or Trusts', fmt_locked)
|
|
ws.write_formula(r5_data+1, 2, f'=SUM(E{start_asset_row+1}:E{end_asset_row+1})', fmt_currency_locked)
|
|
|
|
ws.write(r5_data+2, 1, 'Other Revenues (Donations, Solar, etc.)', fmt_locked)
|
|
ws.write(r5_data+2, 2, 0, fmt_currency_input)
|
|
|
|
r5_total = r5_data + 3
|
|
ws.write(r5_total, 1, 'TOTAL ESTIMATED REVENUE', fmt_subhead)
|
|
ws.write_formula(r5_total, 2, f'=SUM(C{r5_data+1}:C{r5_total-1})', fmt_total)
|
|
|
|
# === SECTION 6: ESTIMATED EXPENDITURE ===
|
|
r6 = r5_total + 2
|
|
ws.write(r6, 1, '6. ESTIMATED EXPENDITURE (Form B - Sec F)', fmt_header)
|
|
ws.write(r6, 2, 'AMOUNT', fmt_header)
|
|
|
|
r6_curr = r6 + 1
|
|
ws.write(r6_curr, 1, 'Staffing Costs', fmt_subhead)
|
|
ws.write(r6_curr, 2, '', fmt_subhead)
|
|
r6_curr += 1
|
|
|
|
# Link to Section 2 Total:
|
|
# "Total Terms" = Total Cost - Super
|
|
# Total Cost is at Row 10 -> C{row_total_cost+1}
|
|
# Super is at Row 7 -> C{row_total_cost+1 - 3}
|
|
ws.write(r6_curr, 1, 'Total Terms of Appt (New Appointee)', fmt_locked)
|
|
ws.write_formula(r6_curr, 2, f'=C{row_total_cost+1} - C{row_total_cost+1-3}', fmt_currency_locked)
|
|
r6_curr += 1
|
|
|
|
ws.write(r6_curr, 1, 'Superannuation (New Appointee)', fmt_locked)
|
|
ws.write_formula(r6_curr, 2, f'=C{row_total_cost+1-3}', fmt_currency_locked)
|
|
r6_curr += 1
|
|
|
|
ws.write(r6_curr, 1, 'Other Ministry Workers', fmt_locked)
|
|
ws.write(r6_curr, 2, 0, fmt_currency_input)
|
|
r6_curr += 1
|
|
ws.write(r6_curr, 1, 'Non-religious workers', fmt_locked)
|
|
ws.write(r6_curr, 2, 0, fmt_currency_input)
|
|
r6_curr += 1
|
|
|
|
ws.write(r6_curr, 1, 'Assessments & Mission', fmt_subhead)
|
|
ws.write(r6_curr, 2, '', fmt_subhead)
|
|
r6_curr += 1
|
|
for item in ['MSF Assessment', 'Assembly Assessments', 'State Mission Program', 'Presbytery Levy', 'Missionaries Support']:
|
|
ws.write(r6_curr, 1, item, fmt_locked)
|
|
ws.write(r6_curr, 2, 0, fmt_currency_input)
|
|
r6_curr += 1
|
|
|
|
ws.write(r6_curr, 1, 'Property & Operations', fmt_subhead)
|
|
ws.write(r6_curr, 2, '', fmt_subhead)
|
|
r6_curr += 1
|
|
for item in ['Rates, Land Tax', 'Electricity / Gas / Maint', 'General Operating', 'Insurances']:
|
|
ws.write(r6_curr, 1, item, fmt_locked)
|
|
ws.write(r6_curr, 2, 0, fmt_currency_input)
|
|
r6_curr += 1
|
|
|
|
ws.write(r6_curr, 1, 'Other', fmt_subhead)
|
|
ws.write(r6_curr, 2, '', fmt_subhead)
|
|
r6_curr += 1
|
|
for item in ['Printing/Ed/Licences', 'Loan/Capital Repayment', 'Major Works', 'Other/Sundry']:
|
|
ws.write(r6_curr, 1, item, fmt_locked)
|
|
ws.write(r6_curr, 2, 0, fmt_currency_input)
|
|
r6_curr += 1
|
|
|
|
ws.write(r6_curr, 1, 'TOTAL ESTIMATED EXPENDITURE', fmt_subhead)
|
|
ws.write_formula(r6_curr, 2, f'=SUM(C{r6+2}:C{r6_curr-1})', fmt_total)
|
|
r6_total_row = r6_curr
|
|
|
|
# === SECTION 7: RESULT ===
|
|
r7 = r6_total_row + 2
|
|
ws.write(r7, 1, '7. ESTIMATED SURPLUS / (DEFICIT)', workbook.add_format({'bold': True, 'font_size': 12, 'border': 1}))
|
|
ws.write_formula(r7, 2, f'=C{r5_total+1}-C{r6_total_row+1}', fmt_result)
|
|
|
|
# --- PROTECT ---
|
|
ws.protect('password', {'select_locked_cells': True, 'select_unlocked_cells': True})
|
|
fmt_input.set_locked(False)
|
|
fmt_currency_input.set_locked(False)
|
|
fmt_percent.set_locked(False)
|
|
fmt_int_input.set_locked(False)
|
|
|
|
print(f"File '{filename}' generated successfully.")
|
|
writer.close() |