feat(billing): assign sub accounts to site or company
MODIFY
src/assets/server/rest/v1/schemas/billing/billing-create-sub-account.json
src/assets/server/rest/v1/schemas/billing/billing-create-sub-account.json
"properties": {
"userID": {
"$ref": "common#/definitions/id"
+ },
+ "siteID": {
+ "$ref": "common#/definitions/id"
+ },
+ "companyID": {
+ "$ref": "common#/definitions/id"
}
},
"required": [
"userID"
+ ],
+ "oneOf": [
+ { "required": ["siteID"] },
+ { "required": ["companyID"] }
]
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
+import { BillingAccount, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { BillingInvoiceDataResult, BillingPaymentMethodDataResult } from '../../../../types/DataResult';
-import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { HTTPAuthError, HTTPError } from '../../../../types/HTTPError';
import { NextFunction, Request, Response } from 'express';
@@ -12,6 +12,7 @@ import BillingSecurity from './security/BillingSecurity';
import { BillingSettings } from '../../../../types/Setting';
import BillingStorage from '../../../../storage/mongodb/BillingStorage';
import BillingValidator from '../validator/BillingValidator';
+import CompanyStorage from '../../../../storage/mongodb/CompanyStorage';
import Constants from '../../../../utils/Constants';
import LockingHelper from '../../../../locking/LockingHelper';
import LockingManager from '../../../../locking/LockingManager';
@@ -20,6 +21,7 @@ import NotificationHandler from '../../../../notification/NotificationHandler';
import NotificationService from './NotificationService';
import { ServerAction } from '../../../../types/Server';
import SettingStorage from '../../../../storage/mongodb/SettingStorage';
+import SiteStorage from '../../../../storage/mongodb/SiteStorage';
import { StatusCodes } from 'http-status-codes';
import { TenantComponents } from '../../../../types/Tenant';
import User from '../../../../types/User';
@@ -485,18 +487,30 @@ export default class BillingService {
throw new AppError({
errorCode: HTTPError.GENERAL_ERROR,
message: 'Billing service is not configured',
- module: MODULE_NAME, method: 'handleDownloadInvoice',
+ module: MODULE_NAME, method: 'handleCreateSubAccount',
action: action,
user: req.user
});
}
+ let subAccount: BillingAccount = {} as BillingAccount;
+
// Get the user
const user = await UserStorage.getUser(req.tenant, filteredRequest.userID);
UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
MODULE_NAME, 'handleCreateSubAccount', req.user);
- // Create the sub account
- const subAccount = await billingImpl.createSubAccount();
subAccount.userID = user.id;
+ // Get the company or the site
+ if (filteredRequest.companyID) {
+ const company = await CompanyStorage.getCompany(req.tenant, filteredRequest.companyID);
+ UtilsService.assertObjectExists(action, company, `Company ID '${filteredRequest.companyID}' does not exist`, MODULE_NAME, 'handleCreateSubAccount', req.user);
+ subAccount.companyID = company.id;
+ } else if (filteredRequest.siteID) {
+ const site = await SiteStorage.getSite(req.tenant, filteredRequest.siteID);
+ UtilsService.assertObjectExists(action, site, `Site ID '${filteredRequest.siteID}' does not exist`, MODULE_NAME, 'handleCreateSubAccount', req.user);
+ subAccount.siteID = site.id;
+ }
+ // Create the sub account
+ subAccount = { ...subAccount, ...await billingImpl.createSubAccount() };
// Save the sub account
subAccount.id = await BillingStorage.saveSubAccount(req.tenant, subAccount);
// Notify the user by mail

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -225,7 +225,9 @@ export default class BillingStorage {
_id: subAccount.id ? DatabaseUtils.convertToObjectID(subAccount.id) : new ObjectId(),
accountID: subAccount.accountID,
pending: subAccount.pending,
- userID: DatabaseUtils.convertToObjectID(subAccount.userID)
+ userID: DatabaseUtils.convertToObjectID(subAccount.userID),
+ siteID: subAccount.siteID ? DatabaseUtils.convertToObjectID(subAccount.siteID) : null,
+ companyID: subAccount.companyID ? DatabaseUtils.convertToObjectID(subAccount.companyID) : null
};
// Modify and return the modified document
await global.database.getCollection<any>(tenant.id, 'billingsubaccounts').findOneAndUpdate(

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -159,5 +159,7 @@ export interface BillingAccount {
activationLink?: string;
userID?: string;
pending: boolean;
+ siteID?: string;
+ companyID?: string;
}

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -31,4 +31,6 @@ export interface HttpDeletePaymentMethod {
export interface HttpBillingCreateSubAccount {
userID: string;
+ siteID?: string;
+ companyID: string;
}

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
/* eslint-disable max-len */
-import { BillingChargeInvoiceAction, BillingInvoiceStatus } from '../../src/types/Billing';
+import { BillingAccount, BillingChargeInvoiceAction, BillingInvoiceStatus } from '../../src/types/Billing';
import { BillingSettings, BillingSettingsType } from '../../src/types/Setting';
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';
@@ -394,9 +395,10 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
expect(response.data.result.length).to.be.gt(0);
});
- it('should create a sub account', async () => {
+ it('should create a sub account for a site', async () => {
const response = await billingTestHelper.userService.billingApi.createSubAccount({
userID: billingTestHelper.userContext.id,
+ siteID: billingTestHelper.siteContext.getSite().id,
});
expect(response.status).to.be.eq(StatusCodes.CREATED);
expect(response.data.id).to.not.be.null;
@@ -404,6 +406,40 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
expect(response.data.userID).to.eq(billingTestHelper.userContext.id);
expect(response.data.activationLink).to.not.be.null;
expect(response.data.pending).to.be.true;
+ expect(response.data.siteID).to.be.eq(billingTestHelper.siteContext.getSite().id);
+ expect(response.data.companyID).to.not.exist;
+ });
+
+ it('should create a sub account for a company', async () => {
+ const company = await billingTestHelper.tenantContext.createCompany(CompanyFactory.build());
+ const response = await billingTestHelper.userService.billingApi.createSubAccount({
+ userID: billingTestHelper.userContext.id,
+ companyID: company.id
+ });
+ expect(response.status).to.be.eq(StatusCodes.CREATED);
+ expect(response.data.id).to.not.be.null;
+ expect(response.data.accountID).to.not.be.null;
+ expect(response.data.userID).to.eq(billingTestHelper.userContext.id);
+ expect(response.data.activationLink).to.not.be.null;
+ expect(response.data.pending).to.be.true;
+ expect(response.data.siteID).to.not.exist;
+ expect(response.data.companyID).to.be.eq(company.id);
+ });
+
+ it('should not create a sub account for a company AND a site', async () => {
+ const response = await billingTestHelper.userService.billingApi.createSubAccount({
+ userID: billingTestHelper.userContext.id,
+ companyID: '6283bc3cc746c38fa184d716',
+ siteID: '6283bc3cc746c38fa184d715',
+ });
+ expect(response.status).to.be.eq(StatusCodes.INTERNAL_SERVER_ERROR);
+ });
+
+ it('should not create a sub account without company or site', async () => {
+ const response = await billingTestHelper.userService.billingApi.createSubAccount({
+ userID: billingTestHelper.userContext.id
+ });
+ expect(response.status).to.be.eq(StatusCodes.INTERNAL_SERVER_ERROR);
});
});
@@ -468,6 +504,7 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
it('should not be able to create a sub account', async () => {
const response = await billingTestHelper.userService.billingApi.createSubAccount({
userID: billingTestHelper.userContext.id,
+ siteID: '6283bc3cc746c38fa184d716'
});
expect(response.status).to.be.eq(StatusCodes.FORBIDDEN);
});

feat(billing): assign sub accounts to site or company