Skip to content

Lease API ​

Implementation: front/src/services/api/leaseService.ts. Shared headers and token behavior are in API overview. The service often maps loosely typed API lease objects into the frontend Lease model. Exact backend schemas and validation are not available from frontend source code.

GET /leases ​

  • Called from: All Leases on mount/filter changes; Reports on data load; lease service/hook callers.
  • When: list load or filter/search change.
  • Query: page, limit, search, status, assetType (only nonempty values are sent). Defaults are page 1, limit 100 in the service.
  • Response used: expects success and data array; each lease is mapped; optional pagination is returned.
  • Errors: logged and converted to { leases: [], pagination: {} }; page may consequently show no records. 401 uses shared interceptor.
  • Request example: GET {VITE_API_BASE_URL}/leases?page=1&limit=100

GET /leases/:id ​

  • Called from: Lease Details and Edit Lease loads.
  • Path: id string.
  • Response used: success, data; lease fields mapped to frontend Lease.
  • Errors: logged; returns null.
  • Request example: GET {VITE_API_BASE_URL}/leases/{id}

POST /leases ​

  • Called from: Lease wizard submit in create flow.
  • Body type: LeaseFormData from front/src/types/lease.ts; exact required-field rules are implemented in form code, not established by backend contract.
  • Response used: success, data; mapped to frontend Lease.
  • Errors: logged and thrown.
  • Request example: POST {VITE_API_BASE_URL}/leases with a LeaseFormData JSON body. A complete sample is omitted because there are numerous optional and form-derived fields; use the TypeScript type.

PUT /leases/:id ​

  • Called from: Edit Lease save flow.
  • Path: id string.
  • Body type: Partial<Lease>.
  • Response used: success, data; mapped to frontend Lease.
  • Errors: logged and thrown.
  • Request example: PUT {VITE_API_BASE_URL}/leases/{id} with a partial Lease JSON body.

DELETE /leases/:id ​

  • Called from: All Leases after user confirms deletion.
  • Path: id string.
  • Response used: outer success and data.success === true.
  • Errors: logged; method returns false.
  • Request example: DELETE {VITE_API_BASE_URL}/leases/{id}

POST /leases/:id/terminate ​

  • Called from: All Leases after confirmation.
  • Path: id string; no request body supplied by service.
  • Response used: success, data; mapped to Lease.
  • Errors: logged and thrown; UI alerts on failure.
  • Request example: POST {VITE_API_BASE_URL}/leases/{id}/terminate

POST /leases/:id/reactivate ​

  • Called from: All Leases after confirmation.
  • Path: id string; no request body supplied by service.
  • Response used: success, data; mapped to Lease.
  • Errors: logged and thrown; UI alerts on failure.
  • Request example: POST {VITE_API_BASE_URL}/leases/{id}/reactivate

GET /leases/summary ​

  • Called from: All Leases alongside list load.
  • Response type: LeaseSummary with totalLeases, activeLeases, draftLeases, expiredOrTerminated; service expects success and data.
  • Errors: logged and thrown.
  • Request example: GET {VITE_API_BASE_URL}/leases/summary

GET /leases/search?q=... ​

  • Called from: leaseService.searchLeases; page call site not confirmed.
  • Query: q string, URL-encoded.
  • Response used: success, data array; mapped to Lease[].
  • Errors: logged and returns an empty array.
  • Request example: GET {VITE_API_BASE_URL}/leases/search?q={encoded-query}

GET /leases/export?format=... ​

  • Called from: All Leases Export action; requests CSV.
  • Query: format is csv or excel; default csv.
  • Response: Blob, MIME type chosen from format.
  • Errors: logged and thrown; page alerts on failure.
  • Request example: GET {VITE_API_BASE_URL}/leases/export?format=csv

GET /leases/next-contract-number ​

  • Called from: service method getNextContractNumber; page call site not confirmed in reviewed sources.
  • Query: required currency string; optional year number.
  • Response used: success, data.contractNumber.
  • Errors: logged and thrown.
  • Request example: GET {VITE_API_BASE_URL}/leases/next-contract-number?currency=USD

POST /leases/calculate-preview ​

  • Endpoint: POST {VITE_API_BASE_URL}/leases/calculate-preview
  • Authentication: Required. The lease router applies authenticate and checkPermission('leases') to all routes. Send Authorization: Bearer <access-token>; never include an actual token in documentation or shared examples.
  • Content type: application/json.
  • Called from: ReviewConfirmStep through leaseService.calculatePreview().
  • When it is called: The review step runs a React effect whenever its formData changes. It is shared by the create and edit lease wizard-step flows, so a request is made when the Review & Confirm step is active and its watched form data changes.
  • Path/query parameters: None.

Request body ​

The frontend builds this object in front/src/components/forms/wizard-steps/ReviewConfirmStep.tsx. The backend controller requires the top-level terms, payments, and discounting objects. The costs object is included by the frontend; the calculation service reads its values optionally, defaulting missing cost values to zero.

json
{
  "terms": {
    "leaseStartDate": "YYYY-MM-DD",
    "leaseEndDate": "YYYY-MM-DD",
    "rentFreePeriod": 0,
    "rentFreeStartDate": "",
    "rentFreeEndDate": ""
  },
  "payments": {
    "paymentBands": [
      {
        "periodStart": "YYYY-MM-DD",
        "periodEnd": "YYYY-MM-DD",
        "monthlyAmount": 0
      }
    ],
    "paymentAmount": 0,
    "monthlyRentYear1": 0,
    "monthlyRentYears2To5": 0,
    "paymentInAdvance": false,
    "paymentInArrear": false,
    "paymentDueDate": "1",
    "prepaidLeasePayment": 0
  },
  "discounting": {
    "discountRate": 0
  },
  "costs": {
    "initialDirectCosts": 0,
    "leaseIncentiveAmount": 0,
    "restorationCost": 0
  }
}

The sample shows the shape, not actual lease values. paymentBands is built from the current form bands; numeric values are converted with Number(...), timing flags with boolean coercion, and empty rent-free dates become empty strings. The service/controller accept untyped request data beyond their top-level checks, so nested requiredness and field validation are not established by this endpoint contract.

FieldFrontend value/typeRequired status known from source
termsObjectRequired by controller
terms.leaseStartDate, terms.leaseEndDateForm date valuesIncluded by frontend; nested validation not determined
terms.rentFreePeriodForm valueIncluded
terms.rentFreeStartDate, terms.rentFreeEndDateString; empty when absentIncluded
paymentsObjectRequired by controller
payments.paymentBandsArray of { periodStart, periodEnd, monthlyAmount }Included; may be empty
payments.paymentAmountNumberIncluded
payments.monthlyRentYear1, payments.monthlyRentYears2To5NumbersIncluded
payments.paymentInAdvance, payments.paymentInArrearBooleansIncluded
payments.paymentDueDateString; fallback "1"Included
payments.prepaidLeasePaymentNumberIncluded
discountingObjectRequired by controller
discounting.discountRateForm valueIncluded; nested validation not determined
costsObjectIncluded by frontend, but not required by controller's top-level guard
costs.initialDirectCosts, leaseIncentiveAmount, restorationCostNumbersIncluded; backend defaults missing values to zero

Success response ​

The backend controller responds with HTTP 200 and an ApiResponse wrapper. Its data is returned by the calculation service:

json
{
  "success": true,
  "data": {
    "totalPayments": "<number>",
    "presentValue": "<number>",
    "leaseLiability": "<number>",
    "rouAsset": "<number>",
    "interestExpense": "<number>"
  },
  "message": "Preview calculated successfully"
}

The values are placeholders, not sample calculated results. The frontend service returns response.data; ReviewConfirmStep reads totalPayments, leaseLiability, rouAsset, and interestExpense. presentValue is returned by the backend but is not consumed by this component.

Calculation behavior visible in backend source ​

The service calculates raw present value and total payments from the supplied lease data. It derives interest expense as totalPayments - rawPresentValue. When payment is in advance and a prepaid amount is greater than zero, it subtracts the prepaid amount from present value before deriving lease liability. Lease liability is the adjusted present value plus initial direct costs minus lease incentives. ROU asset is lease liability plus restoration cost, with the prepaid amount added in the payment-in-advance/prepaid case.

These statements describe the current implementation, not an independent accounting validation.

Errors and frontend behavior ​

  • 400: Controller throws Missing required lease data for calculation if terms, payments, or discounting is absent.
  • 401: Authentication middleware rejects missing/invalid/expired credentials or inactive/missing user.
  • 403: Lease permission middleware rejects missing permissions or NONE lease permission.
  • 500: Calculation service wraps calculation failures in a CustomError with status 500.
  • Exact error response JSON envelope is defined by backend error middleware, not by this endpoint's frontend service contract.
  • The Axios response interceptor attempts token refresh on 401; on refresh failure it clears auth data and redirects to /login.
  • ReviewConfirmStep catches preview errors, logs them to the console, and clears the loading flag. It does not display a dedicated preview error message; the initial/default preview values may remain visible.
  • Frontend service: front/src/services/api/leaseService.ts
  • Frontend request builder/consumer: front/src/components/forms/wizard-steps/ReviewConfirmStep.tsx
  • Backend route: api/src/routes/lease.routes.ts
  • Backend controller: api/src/controllers/lease.controller.ts
  • Calculation: api/src/services/lease.service.ts
  • Related pages: Create Lease, Edit Lease