feat(subaccounts): assign company to subaccount
MODIFY
src/assets/server/rest/v1/schemas/company/company-create.json
src/assets/server/rest/v1/schemas/company/company-create.json
},
"logo": {
"$ref": "common#/definitions/logo"
+ },
+ "billingSubAccountID": {
+ "$ref": "common#/definitions/id"
}
},
"required": [

MODIFY
src/assets/server/rest/v1/schemas/company/company-update.json
src/assets/server/rest/v1/schemas/company/company-update.json
},
"logo": {
"$ref": "common#/definitions/logo"
+ },
+ "billingSubAccountID": {
+ "$ref": "common#/definitions/id"
}
},
"required": [

MODIFY
src/assets/server/rest/v1/schemas/site/site-create.json
src/assets/server/rest/v1/schemas/site/site-create.json
},
"image": {
"$ref": "site#/definitions/image"
+ },
+ "subAccountID": {
+ "$ref": "common#/definitions/id"
}
},
"required": [

MODIFY
src/assets/server/rest/v1/schemas/site/site-update.json
src/assets/server/rest/v1/schemas/site/site-update.json
},
"image": {
"$ref": "site#/definitions/image"
+ },
+ "subAccountID": {
+ "$ref": "common#/definitions/id"
}
},
"required": [

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -166,7 +166,7 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
attributes: [
'id', 'name', 'address.address1', 'address.address2', 'address.postalCode', 'address.city', 'address.country',
'address.coordinates', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn',
- 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName'
+ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName', 'billingSubAccountID'
]
},
{
@@ -207,7 +207,7 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{
resource: Entity.COMPANY, action: Action.READ,
attributes: [
- 'id', 'name', 'issuer', 'logo', 'address'
+ 'id', 'name', 'issuer', 'logo', 'address', 'billingSubAccountID'
]
},
{

MODIFY
src/server/rest/v1/service/CompanyService.ts
src/server/rest/v1/service/CompanyService.ts
import { Action, Entity } from '../../../../types/Authorization';
import { NextFunction, Request, Response } from 'express';
-import AppAuthError from '../../../../exception/AppAuthError';
+import AppError from '../../../../exception/AppError';
import AuthorizationService from './AuthorizationService';
+import BillingFactory from '../../../../integration/billing/BillingFactory';
+import BillingStorage from '../../../../storage/mongodb/BillingStorage';
import Company from '../../../../types/Company';
import { CompanyDataResult } from '../../../../types/DataResult';
import CompanyStorage from '../../../../storage/mongodb/CompanyStorage';
import CompanyValidator from '../validator/CompanyValidator';
import Constants from '../../../../utils/Constants';
-import { HTTPAuthError } from '../../../../types/HTTPError';
+import { HTTPError } from '../../../../types/HTTPError';
import Logging from '../../../../utils/Logging';
import { ServerAction } from '../../../../types/Server';
import { StatusCodes } from 'http-status-codes';
@@ -147,6 +149,14 @@ export default class CompanyService {
createdBy: { id: req.user.id },
createdOn: new Date()
} as Company;
+ // If the company is assigned to a billing sub-account, check if the billing is active
+ if (filteredRequest.billingSubAccountID) {
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.CREATE, Entity.COMPANY, MODULE_NAME, 'handleCreateCompany');
+ const billingSubAccount = await BillingStorage.getSubAccountByID(req.tenant, filteredRequest.billingSubAccountID);
+ UtilsService.assertObjectExists(action, billingSubAccount, `Billing Sub-Account ID '${filteredRequest.billingSubAccountID}' does not exist`, MODULE_NAME, 'handleCreateCompany', req.user);
+ newCompany.billingSubAccountID = billingSubAccount.id;
+ }
// Save
newCompany.id = await CompanyStorage.saveCompany(req.tenant, newCompany);
await Logging.logInfo({
@@ -156,7 +166,7 @@ export default class CompanyService {
action: action,
detailedMessages: { company: newCompany }
});
- res.json(Object.assign({ id: newCompany.id }, Constants.REST_RESPONSE_SUCCESS));
+ res.status(StatusCodes.CREATED).json(Object.assign({ id: newCompany.id }, Constants.REST_RESPONSE_SUCCESS));
next();
}

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -345,4 +345,11 @@ export default class BillingStorage {
}, Constants.DB_PARAMS_SINGLE_RECORD);
return subAccountMDB.count === 1 ? subAccountMDB.result[0] : null;
}
+
+ public static async getSubAccountByID(tenant: Tenant, id: string): Promise<BillingAccount> {
+ const subAccountMDB = await BillingStorage.getSubAccounts(tenant, {
+ subAccountIDs: [id]
+ }, Constants.DB_PARAMS_SINGLE_RECORD);
+ return subAccountMDB.count === 1 ? subAccountMDB.result[0] : null;
+ }
}

MODIFY
src/storage/mongodb/CompanyStorage.ts
src/storage/mongodb/CompanyStorage.ts
@@ -59,6 +59,7 @@ export default class CompanyStorage {
country: companyToSave.address.country,
coordinates: Utils.hasValidGpsCoordinates(companyToSave.address.coordinates) ? companyToSave.address.coordinates.map(
(coordinate) => Utils.convertToFloat(coordinate)) : [],
+ billingSubAccountID: companyToSave.billingSubAccountID ? DatabaseUtils.convertToObjectID(companyToSave.billingSubAccountID) : null,
};
}
// Add Last Changed/Created props

MODIFY
src/types/Company.ts
src/types/Company.ts
@@ -13,4 +13,5 @@ export default interface Company extends CreatedUpdatedProps, AuthorizationActio
sites?: Site[];
distanceMeters?: number;
openingTimes?: OpeningTimes;
+ billingSubAccountID?: string;
}

MODIFY
src/types/Site.ts
src/types/Site.ts
@@ -22,6 +22,7 @@ export default interface Site extends CreatedUpdatedProps, SiteAuthorizationActi
public?: boolean;
openingTimes?: OpeningTimes;
tariffID?: string;
+ billingSubAccountID?: string;
}
export interface SiteUser {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -6,6 +6,7 @@ import chai, { expect } from 'chai';
import { BillingPeriodicOperationTaskConfig } from '../../src/types/TaskConfig';
import BillingTestHelper from './BillingTestHelper';
import CentralServerService from './client/CentralServerService';
+import CompanyFactory from '../factories/CompanyFactory';
import Constants from '../../src/utils/Constants';
import ContextDefinition from './context/ContextDefinition';
import ContextProvider from './context/ContextProvider';
@@ -433,6 +434,20 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.accountID });
expect(activationResponse.status).to.be.eq(StatusCodes.INTERNAL_SERVER_ERROR);
});
+
+ it('should create a company assigned to a sub-account', async () => {
+ const subAccountResponse = await billingTestHelper.userService.billingApi.createSubAccount({
+ userID: billingTestHelper.userContext.id
+ });
+ expect(subAccountResponse.status).to.be.eq(StatusCodes.CREATED);
+
+ let companyResponse = await billingTestHelper.userService.companyApi.create(
+ { ...CompanyFactory.build(), billingSubAccountID: subAccountResponse.data.id }
+ );
+ expect(companyResponse.status).to.be.eq(StatusCodes.CREATED);
+ companyResponse = await billingTestHelper.userService.companyApi.readById(companyResponse.data.id);
+ expect(companyResponse.data.billingSubAccountID).to.eq(subAccountResponse.data.id);
+ });
});
describe('Where basic user', () => {

MODIFY
test/api/CompanyOrgTest.ts
test/api/CompanyOrgTest.ts
import chai, { expect } from 'chai';
+import BillingTestHelper from './BillingTestHelper';
import CentralServerService from './client/CentralServerService';
import Company from '../../src/types/Company';
import ContextDefinition from './context/ContextDefinition';
@@ -29,6 +30,8 @@ class TestData {
}
const testData = new TestData();
+const billingTestHelper = new BillingTestHelper();
+
/**
* @param userRole

feat(subaccounts): assign company to subaccount