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
successanddataarray; each lease is mapped; optionalpaginationis 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:
idstring. - Response used:
success,data; lease fields mapped to frontendLease. - 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:
LeaseFormDatafromfront/src/types/lease.ts; exact required-field rules are implemented in form code, not established by backend contract. - Response used:
success,data; mapped to frontendLease. - Errors: logged and thrown.
- Request example:
POST {VITE_API_BASE_URL}/leaseswith aLeaseFormDataJSON 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:
idstring. - Body type:
Partial<Lease>. - Response used:
success,data; mapped to frontendLease. - Errors: logged and thrown.
- Request example:
PUT {VITE_API_BASE_URL}/leases/{id}with a partialLeaseJSON body.
DELETE /leases/:id
- Called from: All Leases after user confirms deletion.
- Path:
idstring. - Response used: outer
successanddata.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:
idstring; no request body supplied by service. - Response used:
success,data; mapped toLease. - 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:
idstring; no request body supplied by service. - Response used:
success,data; mapped toLease. - 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:
LeaseSummarywithtotalLeases,activeLeases,draftLeases,expiredOrTerminated; service expectssuccessanddata. - 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:
qstring, URL-encoded. - Response used:
success,dataarray; mapped toLease[]. - 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:
formatiscsvorexcel; defaultcsv. - 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
currencystring; optionalyearnumber. - 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
authenticateandcheckPermission('leases')to all routes. SendAuthorization: Bearer <access-token>; never include an actual token in documentation or shared examples. - Content type:
application/json. - Called from:
ReviewConfirmStepthroughleaseService.calculatePreview(). - When it is called: The review step runs a React effect whenever its
formDatachanges. 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.
{
"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.
| Field | Frontend value/type | Required status known from source |
|---|---|---|
terms | Object | Required by controller |
terms.leaseStartDate, terms.leaseEndDate | Form date values | Included by frontend; nested validation not determined |
terms.rentFreePeriod | Form value | Included |
terms.rentFreeStartDate, terms.rentFreeEndDate | String; empty when absent | Included |
payments | Object | Required by controller |
payments.paymentBands | Array of { periodStart, periodEnd, monthlyAmount } | Included; may be empty |
payments.paymentAmount | Number | Included |
payments.monthlyRentYear1, payments.monthlyRentYears2To5 | Numbers | Included |
payments.paymentInAdvance, payments.paymentInArrear | Booleans | Included |
payments.paymentDueDate | String; fallback "1" | Included |
payments.prepaidLeasePayment | Number | Included |
discounting | Object | Required by controller |
discounting.discountRate | Form value | Included; nested validation not determined |
costs | Object | Included by frontend, but not required by controller's top-level guard |
costs.initialDirectCosts, leaseIncentiveAmount, restorationCost | Numbers | Included; 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:
{
"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 calculationifterms,payments, ordiscountingis absent. - 401: Authentication middleware rejects missing/invalid/expired credentials or inactive/missing user.
- 403: Lease permission middleware rejects missing permissions or
NONElease permission. - 500: Calculation service wraps calculation failures in a
CustomErrorwith 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. ReviewConfirmStepcatches 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.
Implementations and related pages
- 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