add school year model
This commit is contained in:
@@ -0,0 +1,294 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user