import { apiFetch } from './http' export type SchoolYearStatus = 'draft' | 'active' | 'closed' | 'archived' | string export type SchoolYearRecord = { id: number name: string start_date: string end_date: string status: SchoolYearStatus is_current: boolean closed_at?: string | null closed_by?: number | null } export type SchoolYearSummary = { school_year: SchoolYearRecord totals: { students_considered: number students_blocked: number parents_with_balances: number net_balance_to_transfer: number } } export type SchoolYearPromotionRow = { student_id: number student_name: string parent_id?: number | null current_grade?: string | null current_class?: string | null promotion_action: string target_grade?: string | null target_class?: string | null target_class_section_id?: number | null warnings?: string[] decision?: string | null score?: number | null } export type SchoolYearPromotionPreview = { rows: SchoolYearPromotionRow[] summary: { total_students: number promote: number repeat: number graduate: number transfer: number withdraw: number hold: number } } export type SchoolYearParentBalanceRow = { parent_id: number parent_name?: string | null student_names?: string[] old_unpaid_balance: number credit_balance: number amount_to_transfer: number existing_transfer_conflict?: boolean } export type SchoolYearParentBalancePreview = { rows: SchoolYearParentBalanceRow[] summary: { parents_with_non_zero_balance: number parents_with_credit_balance: number total_old_unpaid_balance: number total_credit_balance: number net_balance_to_transfer: number existing_transfer_conflicts: number } } export type SchoolYearCloseValidation = { can_close: boolean errors: string[] warnings: string[] promotion_summary: SchoolYearPromotionPreview['summary'] financial_summary: SchoolYearParentBalancePreview['summary'] } export type SchoolYearClosePreview = { school_year: SchoolYearRecord validation: SchoolYearCloseValidation promotion_preview: SchoolYearPromotionPreview parent_balances: SchoolYearParentBalancePreview } export type SchoolYearCloseResult = { closed_school_year: SchoolYearRecord new_school_year: SchoolYearRecord promotion_counts?: Record balance_counts?: Record } export type SaveSchoolYearPayload = { name: string start_date: string end_date: string } export type CloseSchoolYearPayload = { new_school_year: SaveSchoolYearPayload transfer_unpaid_balances?: boolean confirmation?: string } type ApiListResponse = { ok?: boolean data?: SchoolYearRecord[] } type ApiRecordResponse = { ok?: boolean data?: SchoolYearRecord } type ApiSummaryResponse = { ok?: boolean data?: SchoolYearSummary } type ApiValidationResponse = { ok?: boolean validation?: SchoolYearCloseValidation } type ApiPreviewResponse = { ok?: boolean data?: SchoolYearClosePreview } type ApiParentBalancesResponse = { ok?: boolean data?: SchoolYearParentBalancePreview } type ApiPromotionPreviewResponse = { ok?: boolean data?: SchoolYearPromotionPreview } type ApiCloseResponse = { ok?: boolean data?: SchoolYearCloseResult } export async function fetchSchoolYears(): Promise { const response = await apiFetch('/api/v1/school-years') return response.data ?? [] } export async function createSchoolYear(payload: SaveSchoolYearPayload): Promise { const response = await apiFetch('/api/v1/school-years', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!response.data) { throw new Error('School year creation returned no record.') } return response.data } export async function updateSchoolYear( schoolYearId: number, payload: SaveSchoolYearPayload, ): Promise { const response = await apiFetch(`/api/v1/school-years/${schoolYearId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!response.data) { throw new Error('School year update returned no record.') } return response.data } export async function fetchSchoolYearSummary(schoolYearId: number): Promise { const response = await apiFetch(`/api/v1/school-years/${schoolYearId}/summary`) if (!response.data) { throw new Error('School year summary returned no data.') } return response.data } export async function validateSchoolYearClose( schoolYearId: number, payload: CloseSchoolYearPayload, ): Promise { const response = await apiFetch( `/api/v1/school-years/${schoolYearId}/validate-close`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, ) if (!response.validation) { throw new Error('School year validation returned no payload.') } return response.validation } export async function previewSchoolYearClose( schoolYearId: number, payload: CloseSchoolYearPayload, ): Promise { const response = await apiFetch( `/api/v1/school-years/${schoolYearId}/preview-close`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, ) if (!response.data) { throw new Error('School year close preview returned no payload.') } return response.data } export async function closeSchoolYear( schoolYearId: number, payload: CloseSchoolYearPayload, ): Promise { const response = await apiFetch(`/api/v1/school-years/${schoolYearId}/close`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) if (!response.data) { throw new Error('School year close returned no payload.') } return response.data } export async function reopenSchoolYear(schoolYearId: number): Promise { const response = await apiFetch(`/api/v1/school-years/${schoolYearId}/reopen`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }) if (!response.data) { throw new Error('School year reopen returned no payload.') } return response.data } export async function fetchSchoolYearParentBalances( schoolYearId: number, toSchoolYear?: string, ): Promise { const query = toSchoolYear ? `?to_school_year=${encodeURIComponent(toSchoolYear)}` : '' const response = await apiFetch( `/api/v1/school-years/${schoolYearId}/parent-balances${query}`, ) if (!response.data) { throw new Error('Parent balances preview returned no payload.') } return response.data } export async function fetchSchoolYearPromotionPreview( schoolYearId: number, toSchoolYear?: string, ): Promise { const query = toSchoolYear ? `?to_school_year=${encodeURIComponent(toSchoolYear)}` : '' const response = await apiFetch( `/api/v1/school-years/${schoolYearId}/promotion-preview${query}`, ) if (!response.data) { throw new Error('Promotion preview returned no payload.') } return response.data }