Files
alrahma_web_client/src/api/schoolYears.ts
T
2026-06-07 00:52:04 -04:00

295 lines
7.3 KiB
TypeScript

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<string, number>
balance_counts?: Record<string, number>
}
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<SchoolYearRecord[]> {
const response = await apiFetch<ApiListResponse>('/api/v1/school-years')
return response.data ?? []
}
export async function createSchoolYear(payload: SaveSchoolYearPayload): Promise<SchoolYearRecord> {
const response = await apiFetch<ApiRecordResponse>('/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<SchoolYearRecord> {
const response = await apiFetch<ApiRecordResponse>(`/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<SchoolYearSummary> {
const response = await apiFetch<ApiSummaryResponse>(`/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<SchoolYearCloseValidation> {
const response = await apiFetch<ApiValidationResponse>(
`/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<SchoolYearClosePreview> {
const response = await apiFetch<ApiPreviewResponse>(
`/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<SchoolYearCloseResult> {
const response = await apiFetch<ApiCloseResponse>(`/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<SchoolYearRecord> {
const response = await apiFetch<ApiRecordResponse>(`/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<SchoolYearParentBalancePreview> {
const query = toSchoolYear ? `?to_school_year=${encodeURIComponent(toSchoolYear)}` : ''
const response = await apiFetch<ApiParentBalancesResponse>(
`/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<SchoolYearPromotionPreview> {
const query = toSchoolYear ? `?to_school_year=${encodeURIComponent(toSchoolYear)}` : ''
const response = await apiFetch<ApiPromotionPreviewResponse>(
`/api/v1/school-years/${schoolYearId}/promotion-preview${query}`,
)
if (!response.data) {
throw new Error('Promotion preview returned no payload.')
}
return response.data
}