feat(billing-platform): transfer finalization endpoint
MODIFY
src/assets/server/rest/v1/docs/e-mobility-oas.json
src/assets/server/rest/v1/docs/e-mobility-oas.json
}
}
},
+ "/api/billing/transfers/{id}/finalize": {
+ "patch": {
+ "security": [
+ {
+ "bearerAuth": []
+ }
+ ],
+ "description": "Finalize a Transfer",
+ "parameters": [
+ {
+ "in": "path",
+ "name": "id",
+ "description": "Transfer ID",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
+ "tags": [
+ "Billing"
+ ],
+ "responses": {
+ "200": {
+ "description": "Transfer finalized"
+ },
+ "401": {
+ "$ref": "#/components/responses/UnauthorizedError"
+ },
+ "403": {
+ "$ref": "#/components/responses/ForbiddenError"
+ },
+ "500": {
+ "$ref": "#/components/responses/BackendError"
+ },
+ "550": {
+ "$ref": "#/components/responses/ResourceDoesNotExists"
+ }
+ }
+ }
+ },
"/api/invoices": {
"get": {
"security": [

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -400,7 +400,7 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
},
{
resource: Entity.BILLING_TRANSFER,
- action: [Action.LIST]
+ action: [Action.LIST, Action.BILLING_FINALIZE_TRANSFER]
},
{ resource: Entity.TAX, action: [Action.LIST] },
{

MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -48,6 +48,7 @@ export default class BillingRouter {
this.buildRouteBillingGetSubAccount();
this.buildRouteBillingSendSubAccountOnboarding();
this.buildRouteBillingGetTransfers();
+ this.buildRouteBillingFinalizeTransfer();
return this.router;
}
@@ -178,4 +179,10 @@ export default class BillingRouter {
void RouterUtils.handleRestServerAction(BillingService.handleBillingGetTransfers.bind(this), ServerAction.BILLING_TRANSFERS, req, res, next);
});
}
+
+ private buildRouteBillingFinalizeTransfer(): void {
+ this.router.get(`/${RESTServerRoute.REST_BILLING_TRANSFER_FINALIZE}`, (req: Request, res: Response, next: NextFunction) => {
+ void RouterUtils.handleRestServerAction(BillingService.handleFinalizeTransfer.bind(this), ServerAction.BILLING_TRANSFER_FINALIZE, req, res, next);
+ });
+ }
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
-import { BillingAccountStatus, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
+import { BillingAccountStatus, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingTransferStatus } from '../../../../types/Billing';
import { BillingInvoiceDataResult, BillingPaymentMethodDataResult } from '../../../../types/DataResult';
import { NextFunction, Request, Response } from 'express';
@@ -633,6 +633,32 @@ export default class BillingService {
next();
}
+ public static async handleFinalizeTransfer(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING_PLATFORM,
+ Action.BILLING_FINALIZE_TRANSFER, Entity.BILLING_TRANSFER, MODULE_NAME, 'handleFinalizeTransfer');
+ const filteredRequest = BillingValidatorRest.getInstance().validateBillingTransferFinalizeReq(req.params);
+ // Check authorization
+ await AuthorizationService.checkAndGetBillingSubAccountAuthorizations(req.tenant, req.user, filteredRequest, Action.BILLING_FINALIZE_TRANSFER);
+ const transfer = await BillingStorage.getTransferByID(req.tenant, filteredRequest.ID);
+ UtilsService.assertObjectExists(action, transfer, `Transfer ID '${filteredRequest.ID}' does not exist`, MODULE_NAME, 'handleFinalizeTransfer', req.user);
+ // Check if the transfer is in draft status
+ if (transfer.status !== BillingTransferStatus.DRAFT) {
+ throw new AppError({
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Transfer finalization aborted - current status should be DRAFT',
+ module: MODULE_NAME, method: 'handleFinalizeTransfer',
+ action: action,
+ user: req.user
+ });
+ }
+ transfer.status = BillingTransferStatus.FINALIZED;
+ await BillingStorage.saveTransfer(req.tenant, transfer);
+ // TODO Generate an invoice for the transfer
+ res.json(Constants.REST_RESPONSE_SUCCESS);
+ next();
+ }
+
private static async checkActivationPrerequisites(action: ServerAction, req: Request): Promise<void> {
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {

MODIFY
src/server/rest/v1/validator/BillingValidatorRest.ts
src/server/rest/v1/validator/BillingValidatorRest.ts
-import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest, HttpBillingSubAccountActivateRequest, HttpBillingSubAccountCreateRequest, HttpBillingSubAccountGetRequest, HttpBillingSubAccountsGetRequest, HttpBillingTransfersGetRequest, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
+import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest, HttpBillingSubAccountActivateRequest, HttpBillingSubAccountCreateRequest, HttpBillingSubAccountGetRequest, HttpBillingSubAccountsGetRequest, HttpBillingTransferFinalizeRequest, HttpBillingTransfersGetRequest, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
import { BillingSettings } from '../../../../types/Setting';
+import HttpByIDRequest from '../../../../types/requests/HttpByIDRequest';
import Schema from '../../../../types/validator/Schema';
import SchemaValidator from '../../../../validator/SchemaValidator';
import fs from 'fs';
@@ -19,6 +20,7 @@ export default class BillingValidatorRest extends SchemaValidator {
private billingSubAccountsGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-sub-accounts-get.json`, 'utf8'));
private billingSubAccountGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-sub-account-get.json`, 'utf8'));
private billingTransfersGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-transfers-get.json`, 'utf8'));
+ private billingTransferFinalize: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-transfer-finalize.json`, 'utf8'));
private constructor() {
super('BillingValidatorRest');
@@ -74,4 +76,8 @@ export default class BillingValidatorRest extends SchemaValidator {
public validateBillingTransfersGetReq(data: Record<string, unknown>): HttpBillingTransfersGetRequest {
return this.validate(this.billingTransfersGet, data);
}
+
+ public validateBillingTransferFinalizeReq(data: Record<string, unknown>): HttpBillingTransferFinalizeRequest {
+ return this.validate(this.billingTransferFinalize, data);
+ }
}

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -139,6 +139,7 @@ export enum Action {
BILLING_CHARGE_INVOICE = 'BillingChargeInvoice',
BILLING_ACTIVATE_SUB_ACCOUNT = 'BillingSubAccountActivate',
BILLING_ONBOARD_SUB_ACCOUNT = 'BillingSubAccountOnboard',
+ BILLING_FINALIZE_TRANSFER = 'BillingFinalizeTransfer',
CHECK_CONNECTION = 'CheckConnection',
CLEAR_BILLING_TEST_DATA = 'ClearBillingTestData',
RETRIEVE_CONSUMPTION = 'RetrieveConsumption',

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -459,6 +459,7 @@ export enum ServerAction {
BILLING_SUB_ACCOUNT = 'BillingSubAccount',
BILLING_SUB_ACCOUNTS = 'BillingSubAccounts',
BILLING_TRANSFERS = 'BillingTransfers',
+ BILLING_TRANSFER_FINALIZE = 'BillingTransferFinalize',
PRICING = 'Pricing',
PRICING_DEFINITION = 'PricingDefinition',
@@ -699,6 +700,7 @@ export enum RESTServerRoute {
REST_BILLING_DOWNLOAD_INVOICE = 'invoices/:invoiceID/download',
REST_BILLING_TRANSFERS = 'billing/transfers',
+ REST_BILLING_TRANSFER_FINALIZE = 'billing/transfers/:id/finalize',
// PRICING URLs for CRUD operations
REST_PRICING_DEFINITIONS = 'pricing-definitions',

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -63,3 +63,7 @@ export interface HttpBillingTransfersGetRequest extends HttpDatabaseRequest {
TransferExternalID?: string;
Search?: string;
}
+
+export interface HttpBillingTransferFinalizeRequest extends HttpByIDRequest {
+ ID: string;
+}

feat(billing-platform): transfer finalization endpoint