fix apply the plan docs/alrahma_api_fix_plan_school_year_only_v7
This commit is contained in:
+7
-2
@@ -1,5 +1,5 @@
|
||||
import { apiUrl } from '../lib/apiOrigin'
|
||||
import { selectedSchoolYearHeaders } from '../lib/schoolYearSelection'
|
||||
import { selectedSchoolYearHeaders, withSelectedSchoolYearUrl } from '../lib/schoolYearSelection'
|
||||
|
||||
const TOKEN_KEY = 'alrahma_api_token'
|
||||
|
||||
@@ -39,6 +39,10 @@ export function applyStoredSchoolYearHeaders(headers: Headers): Headers {
|
||||
return headers
|
||||
}
|
||||
|
||||
export function withStoredSchoolYearUrl(url: string): string {
|
||||
return withSelectedSchoolYearUrl(url)
|
||||
}
|
||||
|
||||
export class ApiHttpError extends Error {
|
||||
readonly status: number
|
||||
readonly body: unknown
|
||||
@@ -73,7 +77,8 @@ export async function apiFetch<T>(
|
||||
applyStoredSchoolYearHeaders(headers)
|
||||
}
|
||||
|
||||
const url = apiUrl(path)
|
||||
const method = String(init.method ?? 'GET').toUpperCase()
|
||||
const url = apiUrl(method === 'GET' ? withStoredSchoolYearUrl(path) : path)
|
||||
|
||||
let res: Response
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { apiFetch } from './http'
|
||||
|
||||
export type ParentProfileSummary = {
|
||||
id: number
|
||||
firstname?: string | null
|
||||
lastname?: string | null
|
||||
email?: string | null
|
||||
cellphone?: string | null
|
||||
is_active?: boolean
|
||||
families_count?: number
|
||||
selected_year_students_count?: number
|
||||
balance?: number
|
||||
positive_unpaid_balance?: number
|
||||
credit_balance?: number
|
||||
primary_family?: { id?: number; household_name?: string | null } | null
|
||||
}
|
||||
|
||||
export type ParentProfileDetail = {
|
||||
school_year: string
|
||||
read_only: boolean
|
||||
parent: Record<string, unknown> | null
|
||||
families: Record<string, unknown>[]
|
||||
students: Record<string, unknown>[]
|
||||
emergency_contacts: Record<string, unknown>[]
|
||||
invoices: Record<string, unknown>[]
|
||||
payments: Record<string, unknown>[]
|
||||
finance_summary: Record<string, unknown>
|
||||
}
|
||||
|
||||
type Envelope<T> = { data?: T }
|
||||
|
||||
function unwrap<T>(response: T | Envelope<T>): T {
|
||||
if (response && typeof response === 'object' && 'data' in response) {
|
||||
return (response as Envelope<T>).data as T
|
||||
}
|
||||
return response as T
|
||||
}
|
||||
|
||||
export async function fetchParentProfiles(params: {
|
||||
schoolYear: string
|
||||
q?: string
|
||||
page?: number
|
||||
perPage?: number
|
||||
}): Promise<{
|
||||
school_year: string
|
||||
read_only: boolean
|
||||
parents: ParentProfileSummary[]
|
||||
pagination: { page: number; per_page: number; total: number }
|
||||
}> {
|
||||
const query = new URLSearchParams()
|
||||
query.set('school_year', params.schoolYear)
|
||||
if (params.q?.trim()) query.set('q', params.q.trim())
|
||||
if (params.page) query.set('page', String(params.page))
|
||||
if (params.perPage) query.set('per_page', String(params.perPage))
|
||||
return unwrap(await apiFetch(`/api/v1/parent-profiles?${query.toString()}`))
|
||||
}
|
||||
|
||||
export async function fetchParentProfile(parentId: number, schoolYear: string): Promise<ParentProfileDetail> {
|
||||
const query = new URLSearchParams({ school_year: schoolYear })
|
||||
return unwrap(await apiFetch(`/api/v1/parent-profiles/${parentId}?${query.toString()}`))
|
||||
}
|
||||
|
||||
export async function updateParentProfile(parentId: number, schoolYear: string, payload: Record<string, unknown>): Promise<ParentProfileDetail> {
|
||||
const query = new URLSearchParams({ school_year: schoolYear })
|
||||
return unwrap(await apiFetch(`/api/v1/parent-profiles/${parentId}?${query.toString()}`, {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...payload, school_year: schoolYear }),
|
||||
}))
|
||||
}
|
||||
+64
-31
@@ -20,7 +20,11 @@ export type SchoolYearSummary = {
|
||||
students_considered: number
|
||||
students_blocked: number
|
||||
parents_with_balances: number
|
||||
parents_with_positive_balance?: number
|
||||
net_balance_to_transfer: number
|
||||
net_balance_impact?: number
|
||||
total_positive_unpaid_balance?: number
|
||||
total_credit_balance?: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,19 +61,29 @@ export type SchoolYearParentBalanceRow = {
|
||||
parent_name?: string | null
|
||||
student_names?: string[]
|
||||
old_unpaid_balance: number
|
||||
positive_unpaid_balance?: number
|
||||
credit_balance: number
|
||||
net_balance?: number
|
||||
amount_to_transfer: number
|
||||
positive_invoice_ids?: number[]
|
||||
credit_invoice_ids?: number[]
|
||||
source_invoice_ids?: number[]
|
||||
existing_transfer_conflict?: boolean
|
||||
}
|
||||
|
||||
export type SchoolYearParentBalancePreview = {
|
||||
from_school_year?: string
|
||||
to_school_year?: string
|
||||
rows: SchoolYearParentBalanceRow[]
|
||||
summary: {
|
||||
parents_with_non_zero_balance: number
|
||||
parents_with_positive_balance?: number
|
||||
parents_with_credit_balance: number
|
||||
total_old_unpaid_balance: number
|
||||
total_positive_unpaid_balance?: number
|
||||
total_credit_balance: number
|
||||
net_balance_to_transfer: number
|
||||
net_balance_impact?: number
|
||||
existing_transfer_conflicts: number
|
||||
}
|
||||
}
|
||||
@@ -111,7 +125,9 @@ export type SchoolYearClosingReport = {
|
||||
parents_with_unpaid_balances?: number
|
||||
total_unpaid_balance_transferred?: number
|
||||
parents_with_credit_balances?: number
|
||||
total_credit_carried_or_reported?: number
|
||||
total_credit_transferred?: number
|
||||
net_balance_impact?: number
|
||||
}
|
||||
warnings?: string[]
|
||||
}
|
||||
@@ -251,14 +267,17 @@ export async function createSchoolYear(payload: SaveSchoolYearPayload): Promise<
|
||||
}
|
||||
|
||||
export async function updateSchoolYear(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
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),
|
||||
})
|
||||
const response = await apiFetch<ApiRecordResponse>(
|
||||
`/api/v1/school-years/selected${selectedYearQuery(schoolYear)}`,
|
||||
{
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: selectedYearBody(payload, schoolYear),
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error('School year update returned no record.')
|
||||
@@ -267,8 +286,23 @@ export async function updateSchoolYear(
|
||||
return response.data
|
||||
}
|
||||
|
||||
export async function fetchSchoolYearSummary(schoolYearId: number): Promise<SchoolYearSummary> {
|
||||
const response = await apiFetch<ApiSummaryResponse>(`/api/v1/school-years/${schoolYearId}/summary`)
|
||||
function selectedYearQuery(schoolYear: string, extras?: Record<string, string | undefined>): string {
|
||||
const query = new URLSearchParams()
|
||||
query.set('school_year', schoolYear)
|
||||
for (const [key, value] of Object.entries(extras ?? {})) {
|
||||
if (value) query.set(key, value)
|
||||
}
|
||||
return `?${query.toString()}`
|
||||
}
|
||||
|
||||
function selectedYearBody(payload: object, schoolYear: string): string {
|
||||
return JSON.stringify({ ...payload, school_year: schoolYear })
|
||||
}
|
||||
|
||||
export async function fetchSchoolYearSummary(schoolYear: string): Promise<SchoolYearSummary> {
|
||||
const response = await apiFetch<ApiSummaryResponse>(
|
||||
`/api/v1/school-years/summary${selectedYearQuery(schoolYear)}`,
|
||||
)
|
||||
if (!response.data) {
|
||||
throw new Error('School year summary returned no data.')
|
||||
}
|
||||
@@ -276,15 +310,15 @@ export async function fetchSchoolYearSummary(schoolYearId: number): Promise<Scho
|
||||
}
|
||||
|
||||
export async function validateSchoolYearClose(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
payload: CloseSchoolYearPayload,
|
||||
): Promise<SchoolYearCloseValidation> {
|
||||
const response = await apiFetch<ApiValidationResponse>(
|
||||
`/api/v1/school-years/${schoolYearId}/validate-close`,
|
||||
`/api/v1/school-years/validate-close${selectedYearQuery(schoolYear)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
body: selectedYearBody(payload, schoolYear),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -296,15 +330,15 @@ export async function validateSchoolYearClose(
|
||||
}
|
||||
|
||||
export async function previewSchoolYearClose(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
payload: CloseSchoolYearPayload,
|
||||
): Promise<SchoolYearClosePreview> {
|
||||
const response = await apiFetch<ApiPreviewResponse>(
|
||||
`/api/v1/school-years/${schoolYearId}/preview-close`,
|
||||
`/api/v1/school-years/preview-close${selectedYearQuery(schoolYear)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
body: selectedYearBody(payload, schoolYear),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -316,13 +350,13 @@ export async function previewSchoolYearClose(
|
||||
}
|
||||
|
||||
export async function closeSchoolYear(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
payload: CloseSchoolYearPayload,
|
||||
): Promise<SchoolYearCloseResult> {
|
||||
const response = await apiFetch<ApiCloseResponse>(`/api/v1/school-years/${schoolYearId}/close`, {
|
||||
const response = await apiFetch<ApiCloseResponse>(`/api/v1/school-years/close${selectedYearQuery(schoolYear)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
body: selectedYearBody(payload, schoolYear),
|
||||
})
|
||||
|
||||
if (!response.data) {
|
||||
@@ -332,10 +366,11 @@ export async function closeSchoolYear(
|
||||
return response.data
|
||||
}
|
||||
|
||||
export async function reopenSchoolYear(schoolYearId: number): Promise<SchoolYearRecord> {
|
||||
const response = await apiFetch<ApiRecordResponse>(`/api/v1/school-years/${schoolYearId}/reopen`, {
|
||||
export async function reopenSchoolYear(schoolYear: string): Promise<SchoolYearRecord> {
|
||||
const response = await apiFetch<ApiRecordResponse>(`/api/v1/school-years/reopen${selectedYearQuery(schoolYear)}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ school_year: schoolYear }),
|
||||
})
|
||||
|
||||
if (!response.data) {
|
||||
@@ -346,12 +381,11 @@ export async function reopenSchoolYear(schoolYearId: number): Promise<SchoolYear
|
||||
}
|
||||
|
||||
export async function fetchSchoolYearParentBalances(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
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}`,
|
||||
`/api/v1/school-years/parent-balances${selectedYearQuery(schoolYear, { to_school_year: toSchoolYear })}`,
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
@@ -362,12 +396,11 @@ export async function fetchSchoolYearParentBalances(
|
||||
}
|
||||
|
||||
export async function fetchSchoolYearPromotionPreview(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
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}`,
|
||||
`/api/v1/school-years/promotion-preview${selectedYearQuery(schoolYear, { to_school_year: toSchoolYear })}`,
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
@@ -379,10 +412,10 @@ export async function fetchSchoolYearPromotionPreview(
|
||||
|
||||
|
||||
export async function fetchSchoolYearClosingReport(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
): Promise<SchoolYearClosingReport> {
|
||||
const response = await apiFetch<ApiClosingReportResponse>(
|
||||
`/api/v1/school-years/${schoolYearId}/reports/closing`,
|
||||
`/api/v1/school-years/reports/closing${selectedYearQuery(schoolYear)}`,
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
@@ -393,10 +426,10 @@ export async function fetchSchoolYearClosingReport(
|
||||
}
|
||||
|
||||
export async function fetchSchoolYearBalanceTransferReport(
|
||||
schoolYearId: number,
|
||||
schoolYear: string,
|
||||
): Promise<SchoolYearBalanceTransferReport> {
|
||||
const response = await apiFetch<ApiBalanceTransferReportResponse>(
|
||||
`/api/v1/school-years/${schoolYearId}/parent-balances`,
|
||||
`/api/v1/school-years/parent-balances${selectedYearQuery(schoolYear)}`,
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
@@ -407,11 +440,11 @@ export async function fetchSchoolYearBalanceTransferReport(
|
||||
}
|
||||
|
||||
export async function fetchParentStatement(params: {
|
||||
schoolYearId?: number | null
|
||||
schoolYear?: string | null
|
||||
parentId?: number | null
|
||||
}): Promise<ParentStatement> {
|
||||
const query = new URLSearchParams()
|
||||
if (params.schoolYearId != null) query.set('school_year_id', String(params.schoolYearId))
|
||||
if (params.schoolYear) query.set('school_year', params.schoolYear)
|
||||
if (params.parentId != null) query.set('parent_id', String(params.parentId))
|
||||
|
||||
const response = await apiFetch<ApiParentStatementResponse>(
|
||||
|
||||
+7
-2
@@ -1722,10 +1722,15 @@ export async function sendFamilyComposeEmail(payload: {
|
||||
html: string
|
||||
return_url?: string
|
||||
}): Promise<{ ok?: boolean; message?: string }> {
|
||||
return apiFetch(`/api/v1/family-admin/compose-email/send`, {
|
||||
return apiFetch(`/api/v1/family-admin/compose-email`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
body: JSON.stringify({
|
||||
recipient: payload.to.trim(),
|
||||
subject: payload.subject.trim(),
|
||||
html_message: payload.html,
|
||||
return_url: payload.return_url,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+31
-10
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* Staff directory (legacy CI `staff/*`).
|
||||
* Expected Laravel routes under `/api/v1/administrator/staff`.
|
||||
* Staff directory.
|
||||
*/
|
||||
import { apiFetch } from './http'
|
||||
|
||||
const BASE = '/api/v1/administrator/staff'
|
||||
const BASE = '/api/v1/staff'
|
||||
|
||||
function unwrapData<T>(body: T | { data?: T }): T {
|
||||
if (body && typeof body === 'object' && 'data' in body && (body as { data?: T }).data != null) {
|
||||
@@ -24,6 +23,7 @@ export type StaffListRow = {
|
||||
active_role?: string | null
|
||||
class_section?: string | null
|
||||
verification_issue?: boolean
|
||||
status?: string | null
|
||||
}
|
||||
|
||||
export type StaffIndexResponse = {
|
||||
@@ -32,12 +32,16 @@ export type StaffIndexResponse = {
|
||||
school_year?: string | null
|
||||
semester?: string | null
|
||||
schoolYears?: string[]
|
||||
meta?: unknown
|
||||
}
|
||||
|
||||
export type RoleOption = { id: number; name: string }
|
||||
export type RoleOption = { id: number; name: string; label?: string }
|
||||
|
||||
export type StaffFormOptionsResponse = {
|
||||
roles?: RoleOption[]
|
||||
statuses?: Array<{ value: string; label: string }>
|
||||
school_year?: string | null
|
||||
is_read_only_year?: boolean
|
||||
}
|
||||
|
||||
export type StaffMemberResponse = {
|
||||
@@ -59,28 +63,45 @@ export type StaffMemberResponse = {
|
||||
export async function fetchStaffIndex(params?: {
|
||||
school_year?: string
|
||||
semester?: string
|
||||
role?: string
|
||||
status?: string
|
||||
search?: string
|
||||
page?: number
|
||||
per_page?: number
|
||||
}): Promise<StaffIndexResponse> {
|
||||
const qs = new URLSearchParams()
|
||||
if (params?.school_year) qs.set('school_year', params.school_year)
|
||||
if (params?.semester) qs.set('semester', params.semester)
|
||||
if (params?.role) qs.set('role', params.role)
|
||||
if (params?.status) qs.set('status', params.status)
|
||||
if (params?.search) qs.set('search', params.search)
|
||||
if (params?.page) qs.set('page', String(params.page))
|
||||
if (params?.per_page) qs.set('per_page', String(params.per_page))
|
||||
const suffix = qs.toString() ? `?${qs}` : ''
|
||||
return unwrapData(await apiFetch(`${BASE}${suffix}`))
|
||||
}
|
||||
|
||||
export async function fetchStaffFormOptions(): Promise<StaffFormOptionsResponse> {
|
||||
return unwrapData(await apiFetch(`${BASE}/form-options`))
|
||||
export async function fetchStaffFormOptions(params?: { school_year?: string }): Promise<StaffFormOptionsResponse> {
|
||||
const qs = new URLSearchParams()
|
||||
if (params?.school_year) qs.set('school_year', params.school_year)
|
||||
return unwrapData(await apiFetch(`${BASE}/form-options${qs.toString() ? `?${qs}` : ''}`))
|
||||
}
|
||||
|
||||
export async function fetchStaffMember(id: number): Promise<StaffMemberResponse> {
|
||||
return unwrapData(await apiFetch(`${BASE}/${id}`))
|
||||
export async function fetchStaffMember(id: number, params?: { school_year?: string }): Promise<StaffMemberResponse> {
|
||||
const qs = new URLSearchParams()
|
||||
if (params?.school_year) qs.set('school_year', params.school_year)
|
||||
return unwrapData(await apiFetch(`${BASE}/${id}${qs.toString() ? `?${qs}` : ''}`))
|
||||
}
|
||||
|
||||
export async function createStaff(payload: {
|
||||
firstname: string
|
||||
lastname: string
|
||||
email: string
|
||||
phone?: string
|
||||
password: string
|
||||
role_id: number
|
||||
status?: string
|
||||
school_year: string
|
||||
}): Promise<{ ok?: boolean; message?: string }> {
|
||||
return apiFetch(`${BASE}`, {
|
||||
method: 'POST',
|
||||
@@ -91,10 +112,10 @@ export async function createStaff(payload: {
|
||||
|
||||
export async function updateStaff(
|
||||
id: number,
|
||||
payload: { firstname: string; lastname: string; email: string; phone?: string },
|
||||
payload: { firstname: string; lastname: string; email: string; phone?: string; role_id?: number; status?: string; school_year: string },
|
||||
): Promise<{ ok?: boolean; message?: string }> {
|
||||
return apiFetch(`${BASE}/${id}`, {
|
||||
method: 'PUT',
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user