feat(subaccounts): list endpoint
ADD
src/assets/server/rest/v1/schemas/billing/billing-sub-accounts-get.json
+{
+ "$id": "billing-sub-accounts-get",
+ "title": "Get billing sub accounts Request",
+ "type": "object",
+ "properties": {
+ "userID": {
+ "$ref": "common#/definitions/ids"
+ },
+ "Search": {
+ "$ref": "common#/definitions/search"
+ },
+ "SortFields": {
+ "$ref": "common#/definitions/sortFields"
+ },
+ "Limit": {
+ "$ref": "common#/definitions/limit"
+ },
+ "Skip": {
+ "$ref": "common#/definitions/skip"
+ },
+ "OnlyRecordCount": {
+ "$ref": "common#/definitions/onlyRecordCount"
+ },
+ "ProjectFields": {
+ "$ref": "common#/definitions/projectFields"
+ }
+ }
+}

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -390,7 +390,14 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
'dimensions.parkingTime.active', 'dimensions.parkingTime.price', 'dimensions.parkingTime.stepSize', 'dimensions.parkingTime.pricedData',
]
},
- { resource: Entity.BILLING, action: [Action.CHECK_CONNECTION, Action.CLEAR_BILLING_TEST_DATA, Action.BILLING_CREATE_SUB_ACCOUNT] },
+ { resource: Entity.BILLING, action: [Action.CHECK_CONNECTION, Action.CLEAR_BILLING_TEST_DATA] },
+ {
+ resource: Entity.BILLING_PLATFORM,
+ action: [Action.BILLING_CREATE_SUB_ACCOUNT, Action.LIST],
+ attributes: [
+ 'id', 'accountID', 'userID', 'user.id', 'user.firstName', 'user.lastName', 'user.email'
+ ]
+ },
{ resource: Entity.TAX, action: [Action.LIST] },
{
resource: Entity.INVOICE, action: [Action.LIST],

MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -44,6 +44,7 @@ export default class BillingRouter {
// ROUTES for SUB-ACCOUNTS
// -----------------------------------
this.buildRouteBillingCreateSubAccount();
+ this.buildRouteBillingGetSubAccounts();
return this.router;
}
@@ -148,4 +149,10 @@ export default class BillingRouter {
void RouterUtils.handleRestServerAction(BillingService.handleCreateSubAccount.bind(this), ServerAction.BILLING_SUB_ACCOUNT_CREATE, req, res, next);
});
}
+
+ private buildRouteBillingGetSubAccounts(): void {
+ this.router.get(`/${RESTServerRoute.REST_BILLING_SUB_ACCOUNTS}`, (req: Request, res: Response, next: NextFunction) => {
+ void RouterUtils.handleRestServerAction(BillingService.handleBillingGetSubAccounts.bind(this), ServerAction.BILLING_SUB_ACCOUNTS, req, res, next);
+ });
+ }
}

MODIFY
src/server/rest/v1/service/AuthorizationService.ts
src/server/rest/v1/service/AuthorizationService.ts
import { Action, AuthorizationActions, AuthorizationContext, AuthorizationFilter, Entity } from '../../../../types/Authorization';
-import { AssetDataResult, BillingInvoiceDataResult, BillingPaymentMethodDataResult, CarCatalogDataResult, CarDataResult, CompanyDataResult, DataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
+import { AssetDataResult, BillingInvoiceDataResult, BillingPaymentMethodDataResult, BillingSubaccountsDataResult, CarCatalogDataResult, CarDataResult, CompanyDataResult, DataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
import { BillingInvoice, BillingPaymentMethod } from '../../../../types/Billing';
import { Car, CarCatalog } from '../../../../types/Car';
import { HttpAssetGetRequest, HttpAssetsGetRequest } from '../../../../types/requests/HttpAssetRequest';
@@ -601,6 +601,10 @@ export default class AuthorizationService {
return AuthorizationService.checkAndGetEntityAuthorizations(tenant, Entity.BILLING, userToken, {}, {}, authAction, entityData);
}
+ public static async checkAndGetBillingPlatformAuthorizations(tenant: Tenant, userToken: UserToken, authAction: Action, entityData?: EntityData): Promise<AuthorizationFilter> {
+ return AuthorizationService.checkAndGetEntityAuthorizations(tenant, Entity.BILLING_PLATFORM, userToken, {}, {}, authAction, entityData);
+ }
+
public static async checkAndGetTaxesAuthorizations(tenant: Tenant, userToken: UserToken, failsWithException = true): Promise<AuthorizationFilter> {
const authorizations: AuthorizationFilter = {
filters: {},
@@ -653,6 +657,14 @@ export default class AuthorizationService {
Utils.removeCanPropertiesWithFalseValue(billingInvoice);
}
+ public static async addSubAccountsAuthorizations(tenant: Tenant, userToken: UserToken, billingSubAccounts: BillingSubaccountsDataResult,
+ authorizationFilter: AuthorizationFilter): Promise<void> {
+ // Add Meta Data
+ billingSubAccounts.metadata = authorizationFilter.metadata;
+ billingSubAccounts.canListUsers = await AuthorizationService.canPerformAuthorizationAction(
+ tenant, userToken, Entity.USER, Action.LIST, authorizationFilter);
+ }
+
public static async checkAndGetPaymentMethodsAuthorizations(tenant: Tenant, userToken: UserToken,
filteredRequest: Partial<HttpPaymentMethods>, failsWithException = true): Promise<AuthorizationFilter> {
const authorizations: AuthorizationFilter = {

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
-import { BillingInvoiceDataResult, BillingPaymentMethodDataResult } from '../../../../types/DataResult';
+import { BillingInvoiceDataResult, BillingPaymentMethodDataResult, BillingSubaccountsDataResult } from '../../../../types/DataResult';
import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { NextFunction, Request, Response } from 'express';
@@ -545,6 +545,33 @@ export default class BillingService {
next();
}
+ public static async handleBillingGetSubAccounts(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Filter
+ const filteredRequest = BillingValidatorRest.getInstance().validateBillingSubAccountsGetReq(req.query);
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING_PLATFORM,
+ Action.LIST, Entity.BILLING_PLATFORM, MODULE_NAME, 'handleBillingGetSubaccounts');
+ const authorizations = await AuthorizationService.checkAndGetBillingPlatformAuthorizations(req.tenant, req.user, Action.LIST);
+ if (!authorizations.authorized) {
+ UtilsService.sendEmptyDataResult(res, next);
+ return;
+ }
+ // Get the billing subacounts
+ const subAccounts = await BillingStorage.getSubAccounts(req.tenant, {
+ subAccountIDs: filteredRequest.SubAccountID ? filteredRequest.SubAccountID.split('|') : null,
+ userIDs: filteredRequest.UserID ? filteredRequest.UserID.split('|') : null,
+ }, {
+ sort: UtilsService.httpSortFieldsToMongoDB(filteredRequest.SortFields),
+ skip: filteredRequest.Skip,
+ limit: filteredRequest.Limit,
+ onlyRecordCount: filteredRequest.OnlyRecordCount
+ },
+ authorizations.projectFields);
+ await AuthorizationService.addSubAccountsAuthorizations(req.tenant, req.user, subAccounts, authorizations);
+ res.json(subAccounts);
+ 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, HttpBillingSubAccountActivate, HttpBillingSubAccountCreate, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
+import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest, HttpBillingSubAccountActivateRequest, HttpBillingSubAccountCreateRequest, HttpBillingSubAccountsGetRequest, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
import { BillingSettings } from '../../../../types/Setting';
import Schema from '../../../../types/validator/Schema';
@@ -16,6 +16,7 @@ export default class BillingValidatorRest extends SchemaValidator {
private billingInvoiceGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/invoice-get.json`, 'utf8'));
private billingCreateSubAccount: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-create-sub-account.json`, 'utf8'));
private billingActivateSubAccount: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-activate-sub-account.json`, 'utf8'));
+ private billingSubAccountsGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-sub-accounts-get.json`, 'utf8'));
private constructor() {
super('BillingValidatorRest');
@@ -52,11 +53,15 @@ export default class BillingValidatorRest extends SchemaValidator {
return this.validate(this.billingInvoiceGet, data);
}
- public validateBillingCreateSubAccountReq(data: Record<string, unknown>): HttpBillingSubAccountCreate {
+ public validateBillingCreateSubAccountReq(data: Record<string, unknown>): HttpBillingSubAccountCreateRequest {
return this.validate(this.billingCreateSubAccount, data);
}
- public validateBillingActivateSubAccountReq(data: Record<string, unknown>): HttpBillingSubAccountActivate {
+ public validateBillingActivateSubAccountReq(data: Record<string, unknown>): HttpBillingSubAccountActivateRequest {
return this.validate(this.billingActivateSubAccount, data);
}
+
+ public validateBillingSubAccountsGetReq(data: Record<string, unknown>): HttpBillingSubAccountsGetRequest {
+ return this.validate(this.billingSubAccountsGet, data);
+ }
}

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -76,6 +76,7 @@ export enum Entity {
PRICING = 'Pricing',
PRICING_DEFINITION = 'PricingDefinition',
BILLING = 'Billing',
+ BILLING_PLATFORM = 'BillingPlatform',
SETTING = 'Setting',
ASYNC_TASK = 'AsyncTask',
OCPI_ENDPOINT = 'OcpiEndpoint',

MODIFY
src/types/DataResult.ts
src/types/DataResult.ts
-import { BillingInvoice, BillingPaymentMethod } from './Billing';
+import { BillingAccount, BillingInvoice, BillingPaymentMethod } from './Billing';
import { Car, CarCatalog } from './Car';
import Transaction, { TransactionStats } from './Transaction';
@@ -90,6 +90,10 @@ export interface BillingPaymentMethodDataResult extends DataResult<BillingPaymen
canCreate?: boolean;
}
+export interface BillingSubaccountsDataResult extends DataResult<BillingAccount> {
+ canListUsers?: boolean;
+}
+
export interface TransactionRefundDataResult {
count: number;
result: Transaction[];

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -454,6 +454,8 @@ export enum ServerAction {
BILLING_BILL_PENDING_TRANSACTION = 'BillingBillPendingTransaction',
BILLING_SUB_ACCOUNT_CREATE = 'BillingSubAccountCreate',
BILLING_SUB_ACCOUNT_ACTIVATE = 'BillingSubAccountActivate',
+ BILLING_SUB_ACCOUNT = 'BillingSubAccount',
+ BILLING_SUB_ACCOUNTS = 'BillingSubAccounts',
PRICING = 'Pricing',
PRICING_DEFINITION = 'PricingDefinition',
@@ -682,6 +684,7 @@ export enum RESTServerRoute {
REST_BILLING_TAXES = 'billing/taxes',
REST_BILLING_SUB_ACCOUNTS = 'billing/sub-accounts',
+ REST_BILLING_SUB_ACCOUNT = 'billing/sub-accounts/:id',
REST_BILLING_SUB_ACCOUNT_ACTIVATE = 'billing/sub-accounts/:id/activate',
// BILLING URLs for CRUD operations on INVOICES

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -29,11 +29,20 @@ export interface HttpDeletePaymentMethod {
paymentMethodId: string;
}
-export interface HttpBillingSubAccountCreate {
+export interface HttpBillingSubAccountCreateRequest {
userID: string;
}
-export interface HttpBillingSubAccountActivate {
+export interface HttpBillingSubAccountActivateRequest {
ID: string;
TenantID: string;
}
+
+export interface HttpBillingSubAccountsGetRequest extends HttpDatabaseRequest {
+ SubAccountID?: string;
+ UserID?: string;
+ Status?: BillingInvoiceStatus;
+ StartDateTime?: Date;
+ EndDateTime?: Date;
+ Search?: string;
+}

feat(subaccounts): list endpoint