40227-vm/backend/docs/fee_plans.md
2026-06-10 18:27:19 +02:00

4.3 KiB

Fee Plans Backend

Purpose

fee_plans is the per-organization catalogue of fee plans (billing schedules) that invoices can reference. It is a generic-CRUD slice assembled from the shared factories; the backend is the source of truth for fee-plan records.

Slice Files (by layer)

  • Route: src/routes/fee_plans.tscreateCrudRouter(controller, { permission: 'fee_plans' }).
  • Controller: src/api/controllers/fee_plans.controller.tscreateCrudController(service, { csvFields }).
  • Service (BLL): src/services/fee_plans.tscreateCrudService(DbApi, { notFoundCode: 'fee_plansNotFound' }).
  • Repository (DAL): src/db/api/fee_plans.ts (Fee_plansDBApi) — entity-specific create/bulkImport/update/findBy/findAll; remove/deleteByIds/findAllAutocomplete delegate to db/api/shared/repository.ts.
  • Model: src/db/models/fee_plans.ts.
  • Shared used: CRUD factories (services/shared/crud-service.ts, api/controllers/shared/crud-controller.ts, api/http/crud-router.ts), repository helpers (db/api/shared/repository.ts), shared/constants/pagination.ts (resolvePagination).

API

The standard generic-CRUD surface (all under /api/fee_plans, JWT + ${METHOD}_FEE_PLANS permission, all 200) — see backend-architecture.md for the shared contract:

  • POST / — body { data }, returns true.
  • POST /bulk-import — multipart CSV file, returns true.
  • PUT /:id — body { data, id } (the service reads the id from the body, not the path param), returns true.
  • DELETE /:id — returns true.
  • POST /deleteByIds — body { data: string[] }, returns true.
  • GET / — query filters, returns { rows, count }; ?filetype=csv streams a CSV of csvFields.
  • GET /count — returns { rows: [], count }.
  • GET /autocomplete?query&limit&offset, returns [{ id, label }] where label is name.
  • GET /:id — returns the record with eager associations (see Data Contract).

csvFields: id, name, notes, total_amount.

Access Rules

  • JWT required; the whole router is guarded by checkCrudPermissions('fee_plans'), deriving READ_FEE_PLANS / CREATE_FEE_PLANS / UPDATE_FEE_PLANS / DELETE_FEE_PLANS per HTTP method.
  • Access is granted by role permission or per-user custom_permissions (see permissions.md).

Tenant Scope

  • findAll scopes where.organizationId to currentUser.organizationId; a globalAccess role clears the org filter (sees all tenants).
  • create assigns the organization from currentUser.organizationId; update only reassigns organization for globalAccess users (otherwise it stays the caller's org).

Data Contract

Model columns (paranoid, soft-delete via deletedAt):

  • id (UUID PK), name, notes (TEXT, nullable).
  • billing_cycle — ENUM one_time | monthly | termly | annual.
  • total_amount — DECIMAL.
  • active — BOOLEAN, allowNull: false, default false.
  • importHash (unique), academic_yearId, organizationId, gradeId, createdById, updatedById, timestamps.

Associations: belongsTo organization, academic_year, grade, createdBy/updatedBy (users); hasMany invoices_fee_plan (invoices). findBy/GET /:id eager-load invoices_fee_plan, organization, academic_year, grade in a single Promise.all.

List filters (FeePlansFilter): id, name, notes, total_amountRange, active, billing_cycle, academic_year (id or name, |-separated), grade (id or name, |-separated), organization, createdAtRange, plus field/sort ordering and limit/page pagination.

Behavior / Notes

  • This slice has a real active BOOLEAN column. create/bulkImport default active to false; update sets it when provided.
  • bulkImport offsets createdAt per row by BULK_IMPORT_TIMESTAMP_STEP_MS to preserve order.
  • List pagination uses the shared resolvePagination defaults (page size 10, capped at 100).
  • Note: findAll applies the active filter twice — once via the shared filter.active === true || filter.active === 'true' coercion and again via a redundant if (filter.active) where.active = filter.active block (kept for source accuracy).

Tests

None yet.

  • Generic-CRUD contract: backend-architecture.md; related slices: invoices, academic_years, grades, permissions.md.