Invoice migration
MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -379,7 +379,12 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
// ---------------------------------------------------------------------------------------------------
// { resource: Entity.INVOICES, action: [Action.LIST, Action.SYNCHRONIZE] },
// { resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.CREATE] },
- { resource: Entity.INVOICE, action: [Action.LIST] },
+ {
+ resource: Entity.INVOICE, action: [Action.LIST],
+ attributes: [
+ 'id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions', 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email'
+ ]
+ },
{ resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.READ] },
{
resource: Entity.ASSET, action: [Action.CREATE, Action.READ,
@@ -664,7 +669,12 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
'id', 'name', 'issuer', 'logo', 'address'
]
},
- { resource: Entity.INVOICE, action: [Action.LIST] },
+ {
+ resource: Entity.INVOICE, action: [Action.LIST],
+ attributes: [
+ 'id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions', 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email'
+ ]
+ },
{
resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.READ],
condition: {

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, CarCatalogDataResult, CarDataResult, CompanyDataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
+import { AssetDataResult, BillingInvoiceDataResult, CarCatalogDataResult, CarDataResult, CompanyDataResult, LogDataResult, PricingDefinitionDataResult, RegistrationTokenDataResult, SiteAreaDataResult, SiteDataResult, TagDataResult, UserDataResult } from '../../../../types/DataResult';
import { Car, CarCatalog } from '../../../../types/Car';
import { HttpAssetRequest, HttpAssetsRequest } from '../../../../types/requests/HttpAssetRequest';
import { HttpCarCatalogRequest, HttpCarCatalogsRequest, HttpCarRequest, HttpCarsRequest } from '../../../../types/requests/HttpCarRequest';
@@ -17,10 +17,12 @@ import User, { UserRole } from '../../../../types/User';
import AppAuthError from '../../../../exception/AppAuthError';
import Asset from '../../../../types/Asset';
import Authorizations from '../../../../authorization/Authorizations';
+import { BillingInvoice } from '../../../../types/Billing';
import Company from '../../../../types/Company';
import DynamicAuthorizationFactory from '../../../../authorization/DynamicAuthorizationFactory';
import { EntityData } from '../../../../types/GlobalType';
import { HTTPAuthError } from '../../../../types/HTTPError';
+import { HttpBillingInvoicesRequest } from '../../../../types/requests/HttpBillingRequest';
import { HttpLogRequest } from '../../../../types/requests/HttpLoggingRequest';
import { HttpRegistrationTokenRequest } from '../../../../types/requests/HttpRegistrationToken';
import { Log } from '../../../../types/Log';
@@ -591,6 +593,37 @@ export default class AuthorizationService {
}
// End taxes
+ // Invoices
+ public static async checkAndGetInvoicesAuthorizations(tenant: Tenant, userToken: UserToken, filteredRequest: Partial<HttpBillingInvoicesRequest>): Promise<AuthorizationFilter> {
+ const authorizationFilters: AuthorizationFilter = {
+ filters: {},
+ dataSources: new Map(),
+ projectFields: [],
+ authorized: false
+ };
+ // Check static & dynamic authorization
+ await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.INVOICE, Action.LIST,
+ authorizationFilters, filteredRequest, null, true);
+ return authorizationFilters;
+ }
+
+ public static async addInvoicesAuthorizations(tenant: Tenant, userToken: UserToken, billingInvoices: BillingInvoiceDataResult,
+ authorizationFilter: AuthorizationFilter): Promise<void> {
+ // Add Meta Data
+ billingInvoices.metadata = authorizationFilter.metadata;
+ for (const billingInvoice of billingInvoices.result) {
+ await AuthorizationService.addInvoiceAuthorizations(tenant, userToken, billingInvoice, authorizationFilter);
+ }
+ }
+
+ public static async addInvoiceAuthorizations(tenant: Tenant, userToken: UserToken, billingInvoice: BillingInvoice, authorizationFilter: AuthorizationFilter): Promise<void> {
+ billingInvoice.canRead = true; // Always true as it should be filtered upfront
+ billingInvoice.canDownload = await AuthorizationService.canPerformAuthorizationAction(tenant, userToken, Entity.INVOICE, Action.DOWNLOAD, authorizationFilter);
+ // Optimize data over the net
+ Utils.removeCanPropertiesWithFalseValue(billingInvoice);
+ }
+ // end invoices
+
public static async checkAndGetCarsAuthorizations(tenant: Tenant, userToken: UserToken, filteredRequest: Partial<HttpCarsRequest>): Promise<AuthorizationFilter> {
const authorizationFilters: AuthorizationFilter = {
filters: {},

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
+import { BillingInvoiceDataResult, DataResult } from '../../../../types/DataResult';
import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
@@ -13,7 +14,6 @@ import { BillingSettings } from '../../../../types/Setting';
import BillingStorage from '../../../../storage/mongodb/BillingStorage';
import BillingValidator from '../validator/BillingValidator';
import Constants from '../../../../utils/Constants';
-import { DataResult } from '../../../../types/DataResult';
import LockingHelper from '../../../../locking/LockingHelper';
import LockingManager from '../../../../locking/LockingManager';
import Logging from '../../../../utils/Logging';
@@ -32,16 +32,9 @@ export default class BillingService {
public static async handleClearBillingTestData(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
- Action.CLEAR_BILLING_TEST_DATA, Entity.BILLING, MODULE_NAME, 'handleClearBillingTestData');
- if (!await Authorizations.canClearBillingTestData(req.user)) {
- throw new AppAuthError({
- errorCode: HTTPAuthError.FORBIDDEN,
- user: req.user,
- entity: Entity.BILLING, action: Action.CLEAR_BILLING_TEST_DATA,
- module: MODULE_NAME, method: 'handleClearBillingTestData',
- });
- }
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING, Action.CLEAR_BILLING_TEST_DATA, Entity.BILLING, MODULE_NAME, 'handleClearBillingTestData');
+ // Check dynamic auth
+ await AuthorizationService.checkAndGetBillingAuthorizations(req.tenant, req.user, Action.CLEAR_BILLING_TEST_DATA);
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {
throw new AppError({
@@ -192,8 +185,11 @@ export default class BillingService {
public static async handleGetInvoices(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
- Action.LIST, Entity.INVOICE, MODULE_NAME, 'handleGetInvoices');
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING, Action.LIST, Entity.INVOICE, MODULE_NAME, 'handleGetInvoices');
+ // Filter
+ const filteredRequest = BillingValidator.getInstance().validateBillingInvoicesGetReq(req.query);
+ // Check dynamic authorization
+ const authorizations = await AuthorizationService.checkAndGetInvoicesAuthorizations(req.tenant, req.user, filteredRequest);
if (!await Authorizations.canListInvoicesBilling(req.user)) {
throw new AppAuthError({
errorCode: HTTPAuthError.FORBIDDEN,
@@ -202,14 +198,7 @@ export default class BillingService {
module: MODULE_NAME, method: 'handleGetInvoices',
});
}
- // Check Users
- let userProject: string[] = [];
- // Temporary fix before new auth migration
- if (!Authorizations.isDemo(req.user)) {
- userProject = [ 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email' ];
- }
- // Filter
- const filteredRequest = BillingValidator.getInstance().validateBillingInvoicesGetReq(req.query);
+
// Get invoices
const invoices = await BillingStorage.getInvoices(req.tenant,
{
@@ -225,10 +214,15 @@ export default class BillingService {
sort: UtilsService.httpSortFieldsToMongoDB(filteredRequest.SortFields),
onlyRecordCount: filteredRequest.OnlyRecordCount
},
- [
- 'id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions',
- ...userProject
- ]);
+ authorizations.projectFields
+ );
+ // Assign projected fields
+ if (authorizations.projectFields) {
+ invoices.projectFields = authorizations.projectFields;
+ }
+ // Add Auth flags
+ await AuthorizationService.addInvoicesAuthorizations(
+ req.tenant, req.user, invoices as BillingInvoiceDataResult, authorizations);
res.json(invoices);
next();
}

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -223,6 +223,10 @@ export interface SiteAuthorizationActions extends AuthorizationActions {
canMaintainPricingDefinitions?: boolean;
}
+export interface BillingInvoiceAuthorizationActions extends AuthorizationActions {
+ canDownload?:boolean;
+}
+
export enum DynamicAuthorizationFilterName {
ASSIGNED_SITES_COMPANIES = 'AssignedSitesCompanies',
SITES_ADMIN = 'SitesAdmin',

MODIFY
src/types/Billing.ts
src/types/Billing.ts
import { ActionsResponse } from './GlobalType';
+import { BillingInvoiceAuthorizationActions } from './Authorization';
+import CreatedUpdatedProps from './CreatedUpdatedProps';
import { PricedConsumptionData } from './Pricing';
import User from './User';
@@ -60,7 +62,7 @@ export interface BillingTax {
percentage: number;
}
-export interface BillingInvoice {
+export interface BillingInvoice extends CreatedUpdatedProps, BillingInvoiceAuthorizationActions {
id: string;
invoiceID: string;
liveMode: boolean;

MODIFY
src/types/DataResult.ts
src/types/DataResult.ts
@@ -3,6 +3,7 @@ import Transaction, { TransactionStats } from './Transaction';
import Asset from './Asset';
import { AuthorizationDefinitionFieldMetadata } from './Authorization';
+import { BillingInvoice } from './Billing';
import Company from './Company';
import { Log } from './Log';
import PricingDefinition from './Pricing';
@@ -80,6 +81,9 @@ export interface TransactionDataResult extends DataResult<Transaction> {
stats: TransactionStats;
}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface BillingInvoiceDataResult extends DataResult<BillingInvoice> {}
+
export interface TransactionRefundDataResult {
count: number;
result: Transaction[];

MODIFY
src/types/GlobalType.ts
src/types/GlobalType.ts
import { Car, CarCatalog } from './Car';
+import { BillingInvoice } from './Billing';
import ChargingStation from './ChargingStation';
import Company from './Company';
import JsonCentralSystemServer from '../server/ocpp/json/JsonCentralSystemServer';
@@ -74,7 +75,7 @@ export enum ImportStatus {
ERROR = 'E',
}
-export type EntityData = Car|User|Company|Site|SiteArea|Tag|CarCatalog|ChargingStation|PricingDefinition|Log|RegistrationToken;
+export type EntityData = Car|User|Company|Site|SiteArea|Tag|CarCatalog|ChargingStation|PricingDefinition|Log|RegistrationToken|BillingInvoice;
interface TSGlobal extends Global {
database: MongoDBStorage;

Invoice migration