295 lines
7.3 KiB
TypeScript
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
|
|
}
|