Pricing - pricing storage
MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -210,6 +210,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.LOGGINGS, action: Action.LIST },
{ resource: Entity.LOGGING, action: Action.READ },
{ resource: Entity.PRICING, action: [Action.READ, Action.UPDATE] },
+ { resource: Entity.PRICING_MODELS, action: [Action.LIST] },
{ resource: Entity.PRICING_MODEL, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE] },
{ resource: Entity.BILLING, action: [Action.CHECK_CONNECTION, Action.CLEAR_BILLING_TEST_DATA] },
{ resource: Entity.TAXES, action: [Action.LIST] },

MODIFY
src/server/rest/v1/service/PricingService.ts
src/server/rest/v1/service/PricingService.ts
@@ -20,7 +20,7 @@ export default class PricingService {
public static async handleDeletePricingModel(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.PRICING,
Action.DELETE, Entity.COMPANY, MODULE_NAME, 'handleDeletePricingModel');
// Filter
const pricingModelID = PricingSecurity.filterPricingModelRequestByID(req.query);
@@ -46,10 +46,10 @@ export default class PricingService {
public static async handleGetPricingModel(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.PRICING,
Action.READ, Entity.COMPANY, MODULE_NAME, 'handleGetPricingModel');
// Filter
- const filteredRequest = PricingSecurity.filterPricingRequest(req.query);
+ const filteredRequest = PricingSecurity.filterPricingModelRequest(req.query);
UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetPricingModel', req.user);
// Check and Get Pricing
const pricing = await UtilsService.checkAndGetPricingModelAuthorization(
@@ -62,7 +62,7 @@ export default class PricingService {
public static async handleGetPricingModels(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.PRICING,
Action.LIST, Entity.PRICING_MODELS, MODULE_NAME, 'handleGetPricingModels');
// Filter
const filteredRequest = PricingSecurity.filterPricingModelsRequest(req.query);
@@ -96,7 +96,7 @@ export default class PricingService {
public static async handleCreatePricingModel(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.PRICING,
Action.CREATE, Entity.COMPANY, MODULE_NAME, 'handleCreatePricingModel');
// Filter
const filteredRequest = PricingSecurity.filterPricingModelCreateRequest(req.body);
@@ -137,7 +137,7 @@ export default class PricingService {
public static async handleUpdatePricingModel(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ORGANIZATION,
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.PRICING,
Action.UPDATE, Entity.COMPANY, MODULE_NAME, 'handleUpdatePricingModel');
// Filter
const filteredRequest = PricingSecurity.filterPricingModelUpdateRequest(req.body);

MODIFY
src/server/rest/v1/service/UtilsService.ts
src/server/rest/v1/service/UtilsService.ts
@@ -108,31 +108,31 @@ export default class UtilsService {
return chargingStation;
}
- public static async checkAndGetPricingModelAuthorization(tenant: Tenant, userToken: UserToken, pricingID: string, authAction: Action,
+ public static async checkAndGetPricingModelAuthorization(tenant: Tenant, userToken: UserToken, pricingModelID: string, authAction: Action,
action: ServerAction, entityData?: EntityDataType, additionalFilters: Record<string, any> = {}, applyProjectFields = false, checkIssuer = true): Promise<PricingModel> {
// Check mandatory fields
- UtilsService.assertIdIsProvided(action, pricingID, MODULE_NAME, 'checkAndGetPricingModelAuthorization', userToken);
+ UtilsService.assertIdIsProvided(action, pricingModelID, MODULE_NAME, 'checkAndGetPricingModelAuthorization', userToken);
// Get dynamic auth
const authorizationFilter = await AuthorizationService.checkAndGetPricingModelAuthorizations(
- tenant, userToken, { ID: pricingID }, authAction, entityData);
+ tenant, userToken, { ID: pricingModelID }, authAction, entityData);
if (!authorizationFilter.authorized) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
user: userToken,
action: authAction, entity: Entity.COMPANY,
module: MODULE_NAME, method: 'checkAndGetPricingModelAuthorization',
- value: pricingID
+ value: pricingModelID
});
}
// Get Pricing
- const pricingModel = await PricingStorage.getPricingModel(tenant, pricingID,
+ const pricingModel = await PricingStorage.getPricingModel(tenant, pricingModelID,
{
...additionalFilters,
...authorizationFilter.filters
},
applyProjectFields ? authorizationFilter.projectFields : null
);
- UtilsService.assertObjectExists(action, pricingModel, `Pricing ID '${pricingID}' does not exist`,
+ UtilsService.assertObjectExists(action, pricingModel, `Pricing Model ID '${pricingModelID}' does not exist`,
MODULE_NAME, 'checkAndGetPricingModelAuthorization', userToken);
// Add actions
await AuthorizationService.addPricingAuthorizations(tenant, userToken, pricingModel, authorizationFilter);
@@ -144,7 +144,7 @@ export default class UtilsService {
user: userToken,
action: authAction, entity: Entity.COMPANY,
module: MODULE_NAME, method: 'checkAndGetPricingModelAuthorization',
- value: pricingID
+ value: pricingModelID
});
}
return pricingModel;

MODIFY
src/server/rest/v1/service/security/PricingSecurity.ts
src/server/rest/v1/service/security/PricingSecurity.ts
@@ -10,7 +10,7 @@ export default class PricingSecurity {
return sanitize(request.ID);
}
- public static filterPricingRequest(request: any): HttpPricingModelRequest {
+ public static filterPricingModelRequest(request: any): HttpPricingModelRequest {
return {
ID: sanitize(request.ID)
};

MODIFY
src/storage/mongodb/PricingStorage.ts
src/storage/mongodb/PricingStorage.ts
@@ -14,18 +14,18 @@ const MODULE_NAME = 'PricingStorage';
export default class PricingStorage {
- public static async savePricingModel(tenant: Tenant, pricing: PricingModel): Promise<string> {
+ public static async savePricingModel(tenant: Tenant, pricingModel: PricingModel): Promise<string> {
// Debug
const uniqueTimerID = Logging.traceStart(tenant.id, MODULE_NAME, 'savePricingModel');
// Check Tenant
DatabaseUtils.checkTenantObject(tenant);
const pricingModelMDB = {
- _id: pricing.id ? DatabaseUtils.convertToObjectID(pricing.id) : new ObjectId(),
- contextID: DatabaseUtils.convertToObjectID(pricing.contextID),
- pricingDefinitions: pricing.pricingDefinitions, // TODO - check here some data consistency
+ _id: pricingModel.id ? DatabaseUtils.convertToObjectID(pricingModel.id) : new ObjectId(),
+ contextID: DatabaseUtils.convertToObjectID(pricingModel.contextID),
+ pricingDefinitions: pricingModel.pricingDefinitions, // TODO - check here some data consistency
};
// Check Created/Last Changed By
- DatabaseUtils.addLastChangedCreatedProps(pricingModelMDB, pricing);
+ DatabaseUtils.addLastChangedCreatedProps(pricingModelMDB, pricingModel);
// Save
await global.database.getCollection<any>(tenant.id, 'pricingmodels').findOneAndUpdate(
{ '_id': pricingModelMDB._id },
@@ -36,7 +36,7 @@ export default class PricingStorage {
return pricingModelMDB._id.toString();
}
- public static async deletePricingModel(tenant: Tenant, pricingID: string): Promise<void> {
+ public static async deletePricingModel(tenant: Tenant, pricingModelID: string): Promise<void> {
// Debug
const uniqueTimerID = Logging.traceStart(tenant.id, MODULE_NAME, 'deletePricing');
// Check Tenant
@@ -44,23 +44,25 @@ export default class PricingStorage {
// Delete
await global.database.getCollection<any>(tenant.id, 'pricingmodels').deleteOne(
{
- '_id': pricingID,
+ '_id': pricingModelID,
}
);
// Debug
- await Logging.traceEnd(tenant.id, MODULE_NAME, 'deletePricingModel', uniqueTimerID, { id: pricingID });
+ await Logging.traceEnd(tenant.id, MODULE_NAME, 'deletePricingModel', uniqueTimerID, { id: pricingModelID });
}
public static async getPricingModel(tenant: Tenant, id: string,
params: { contextIDs?: string[]; } = {}, projectFields?: string[]): Promise<PricingModel> {
- const pricingMDB = await PricingStorage.getPricingModels(tenant, {
+ const pricingModelMDB = await PricingStorage.getPricingModels(tenant, {
+ pricingModelIDs: [id],
contextIDs: params.contextIDs,
}, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
- return pricingMDB.count === 1 ? pricingMDB.result[0] : null;
+ return pricingModelMDB.count === 1 ? pricingModelMDB.result[0] : null;
}
public static async getPricingModels(tenant: Tenant,
params: {
+ pricingModelIDs?: string[],
contextIDs?: string[];
},
dbParams: DbParams, projectFields?: string[]): Promise<DataResult<PricingModel>> {
@@ -76,8 +78,24 @@ export default class PricingStorage {
// Create Aggregation
const aggregation = [];
const filters: FilterParams = {};
+ // Model IDs
+ if (!Utils.isEmptyArray(params.pricingModelIDs)) {
+ filters._id = {
+ $in: params.pricingModelIDs.map((pricingModelID) => DatabaseUtils.convertToObjectID(pricingModelID))
+ };
+ }
+ // Context IDs
+ if (!Utils.isEmptyArray(params.contextIDs)) {
+ filters.contextID = { $in: params.contextIDs.map((contextID) => DatabaseUtils.convertToObjectID(contextID)) };
+ }
// Remove deleted
filters.deleted = { '$ne': true };
+ // Filters
+ if (!Utils.isEmptyJSon(filters)) {
+ aggregation.push({
+ $match: filters
+ });
+ }
// Limit records?
if (!dbParams.onlyRecordCount) {
// Always limit the nbr of record to avoid performances issues
@@ -121,18 +139,18 @@ export default class PricingStorage {
// Project
DatabaseUtils.projectFields(aggregation, projectFields);
// Read DB
- const pricingModelMDB = await global.database.getCollection<PricingModel>(tenant.id, 'pricingmodels')
+ const pricingModelsMDB = await global.database.getCollection<PricingModel>(tenant.id, 'pricingmodels')
.aggregate(aggregation, {
allowDiskUse: true
})
.toArray();
// Debug
- await Logging.traceEnd(tenant.id, MODULE_NAME, 'getPricingModels', uniqueTimerID, pricingModelMDB);
+ await Logging.traceEnd(tenant.id, MODULE_NAME, 'getPricingModels', uniqueTimerID, pricingModelsMDB);
// Ok
return {
count: (pricingModelsCountMDB.length > 0 ?
(pricingModelsCountMDB[0].count === Constants.DB_RECORD_COUNT_CEIL ? -1 : pricingModelsCountMDB[0].count) : 0),
- result: pricingModelMDB,
+ result: pricingModelsMDB,
projectedFields: projectFields
};
}

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -551,7 +551,7 @@ export enum ServerRoute {
// PRICING URLs for CRUD operations
REST_PRICING_MODELS = 'pricing-models',
- REST_PRICING_MODEL = 'pricing-models/:pricingModelID'
+ REST_PRICING_MODEL = 'pricing-models/:id'
}
export enum ServerProtocol {

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
@@ -436,8 +436,15 @@ export default class StripeIntegrationTestData {
contextID: null, // a pricing model for the tenant
pricingDefinitions: []
};
- const response = await this.adminUserService.pricingApi.createPricingModel(pricingModel);
+ let response = await this.adminUserService.pricingApi.createPricingModel(pricingModel);
assert(response?.data?.status === 'Success', 'The operation should succeed');
+ assert(response?.data?.id, 'The ID should not be null');
+
+ const pricingModelId = response?.data?.id;
+ response = await this.adminUserService.pricingApi.readPricingModel(pricingModelId);
+ assert(response?.data?.status === 'Success', 'The operation should succeed');
+ assert(response?.data?.id === pricingModelId, 'The ID should be: ' + pricingModelId);
+
}
}

Pricing - pricing storage