Small refactoring on sync invoices, delete also PDF document when deleting an invoice, Auth renamed, Renamed PDF -> Document + added encoding, type and invoiceID
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -605,11 +605,11 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.INVOICE, Action.DOWNLOAD);
}
- public static canCheckConnectionAsset(loggedUser: UserToken): boolean {
+ public static canCheckAssetConnection(loggedUser: UserToken): boolean {
return Authorizations.canPerformAction(loggedUser, Entity.ASSET, Action.CHECK_CONNECTION);
}
- public static canRefreshAssetConnection(loggedUser: UserToken): boolean {
+ public static canRetrieveAssetConsumption(loggedUser: UserToken): boolean {
return Authorizations.canPerformAction(loggedUser, Entity.ASSET, Action.RETRIEVE_CONSUMPTION);
}

MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
-import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoicePdf, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
-import User, { UserStatus } from '../../types/User';
-import { ActionsResponse } from '../../types/GlobalType';
import BackendError from '../../exception/BackendError';
-import { BillingSetting } from '../../types/Setting';
+import LockingManager from '../../locking/LockingManager';
import BillingStorage from '../../storage/mongodb/BillingStorage';
-import Constants from '../../utils/Constants';
-import Logging from '../../utils/Logging';
-import { ServerAction } from '../../types/Server';
import SettingStorage from '../../storage/mongodb/SettingStorage';
-import Transaction from '../../types/Transaction';
-import { UserInErrorType } from '../../types/InError';
import UserStorage from '../../storage/mongodb/UserStorage';
-import Utils from '../../utils/Utils';
-import LockingManager from '../../locking/LockingManager';
+import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
+import { ActionsResponse } from '../../types/GlobalType';
+import { UserInErrorType } from '../../types/InError';
import { LockEntity } from '../../types/Locking';
+import { ServerAction } from '../../types/Server';
+import { BillingSetting } from '../../types/Setting';
+import Transaction from '../../types/Transaction';
+import User, { UserStatus } from '../../types/User';
+import Constants from '../../utils/Constants';
+import Logging from '../../utils/Logging';
+import Utils from '../../utils/Utils';
const MODULE_NAME = 'BillingIntegration';
@@ -297,10 +297,10 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
for (const invoiceIDInBilling of invoiceIDsInBilling) {
try {
// Get billing invoice
- const invoiceBilling = await this.getInvoice(invoiceIDInBilling);
+ const invoiceInBilling = await this.getInvoice(invoiceIDInBilling);
// Get e-Mobility invoice
const invoice = await BillingStorage.getInvoiceByBillingInvoiceID(tenantID, invoiceIDInBilling);
- if (!invoiceBilling && !invoice) {
+ if (!invoiceInBilling && !invoice) {
actionsDone.inError++;
Logging.logError({
tenantID: tenantID,
@@ -313,7 +313,7 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
continue;
}
// Delete in e-Mobility
- if (!invoiceBilling && invoice) {
+ if (!invoiceInBilling && invoice) {
await BillingStorage.deleteInvoiceByInvoiceID(tenantID, invoiceIDInBilling);
Logging.logDebug({
tenantID: tenantID,
@@ -330,7 +330,7 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
let userInInvoice: User;
if (invoice) {
// If invoice already exists, set back its e-Mobility ID before saving
- invoiceBilling.id = invoice.id;
+ invoiceInBilling.id = invoice.id;
} else {
// Associate e-Mobility user to invoice according to invoice customer ID
if (user) {
@@ -338,11 +338,11 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
userInInvoice = user;
} else {
// Get user
- userInInvoice = await UserStorage.getUserByBillingID(tenantID, invoiceBilling.customerID);
+ userInInvoice = await UserStorage.getUserByBillingID(tenantID, invoiceInBilling.customerID);
}
- Object.assign(invoiceBilling, { user: user });
+ Object.assign(invoiceInBilling, { user: user });
}
- await BillingStorage.saveInvoice(tenantID, invoiceBilling);
+ await BillingStorage.saveInvoice(tenantID, invoiceInBilling);
Logging.logDebug({
tenantID: tenantID,
user: user,
@@ -418,10 +418,10 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
for (const invoiceIDInBilling of invoiceIDsInBilling) {
try {
// Get billing invoice
- const invoiceBilling = await this.getInvoice(invoiceIDInBilling);
+ const invoiceInBilling = await this.getInvoice(invoiceIDInBilling);
// Get e-Mobility invoice
const invoice = await BillingStorage.getInvoiceByBillingInvoiceID(tenantID, invoiceIDInBilling);
- if (!invoiceBilling && !invoice) {
+ if (!invoiceInBilling && !invoice) {
actionsDone.inError++;
Logging.logError({
tenantID: tenantID,
@@ -429,12 +429,12 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
source: Constants.CENTRAL_SERVER,
action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
module: MODULE_NAME, method: 'synchronizeInvoices',
- message: `Billing invoice with ID '${invoiceIDInBilling}' does not exist anymore`
+ message: `Billing invoice with ID '${invoiceIDInBilling}' does not exist`
});
continue;
}
// Delete in e-Mobility
- if (!invoiceBilling && invoice) {
+ if (!invoiceInBilling && invoice) {
await BillingStorage.deleteInvoiceByInvoiceID(tenantID, invoiceIDInBilling);
Logging.logDebug({
tenantID: tenantID,
@@ -451,7 +451,7 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
let userInInvoice: User;
if (invoice) {
// If invoice already exists, set back its e-Mobility ID before saving
- invoiceBilling.id = invoice.id;
+ invoiceInBilling.id = invoice.id;
}
// Associate e-Mobility user to invoice according to invoice customer ID
if (user) {
@@ -459,31 +459,26 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
userInInvoice = user;
} else {
// Get user
- userInInvoice = await UserStorage.getUserByBillingID(tenantID, invoiceBilling.customerID);
+ userInInvoice = await UserStorage.getUserByBillingID(tenantID, invoiceInBilling.customerID);
+ }
+ invoiceInBilling.user = userInInvoice ? userInInvoice : null;
+ // Save invoice
+ invoiceInBilling.userID = userInInvoice ? userInInvoice.id : null;
+ invoiceInBilling.id = await BillingStorage.saveInvoice(tenantID, invoiceInBilling);
+ // Download the document
+ const invoiceDocument = await this.downloadInvoiceDocument(invoiceInBilling);
+ if (invoiceDocument) {
+ // Save it in our DB
+ await BillingStorage.saveInvoiceDocument(tenantID, invoiceDocument);
+ // Update
+ invoiceInBilling.downloadable = true;
+ await BillingStorage.saveInvoice(tenantID, invoiceInBilling);
}
- invoiceBilling.user = userInInvoice ? userInInvoice : null;
- await BillingStorage.saveInvoice(tenantID, invoiceBilling);
- Logging.logDebug({
- tenantID: tenantID,
- user: user,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
- module: MODULE_NAME, method: 'synchronizeInvoices',
- message: `Billing invoice with ID '${invoiceIDInBilling}' does not exist`
- });
if (userInInvoice) {
userInInvoice.billingData.invoicesLastSynchronizedOn = new Date();
await UserStorage.saveUserBillingData(tenantID, userInInvoice.id, userInInvoice.billingData);
}
actionsDone.inSuccess++;
- invoiceBilling.userID = userInInvoice ? userInInvoice.id : null;
- invoiceBilling.id = await BillingStorage.saveInvoice(tenantID, invoiceBilling);
- const invoicePdf = await this.downloadInvoicePdf(invoiceBilling);
- if (invoicePdf) {
- await BillingStorage.saveInvoicePdf(tenantID, invoicePdf);
- invoiceBilling.downloadable = true;
- await BillingStorage.saveInvoice(tenantID, invoiceBilling);
- }
Logging.logDebug({
tenantID: tenantID,
user: user,
@@ -491,13 +486,8 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
module: MODULE_NAME, method: 'synchronizeInvoices',
message: `Invoice with ID '${invoiceIDInBilling}' has been ${invoice ? 'updated' : 'created'} in e-Mobility`,
- detailedMessages: { invoiceBilling }
+ detailedMessages: { invoiceInBilling }
});
- if (userInInvoice) {
- userInInvoice.billingData.invoicesLastSynchronizedOn = new Date();
- await UserStorage.saveUserBillingData(tenantID, userInInvoice.id, userInInvoice.billingData);
- }
- actionsDone.inSuccess++;
} catch (error) {
actionsDone.inError++;
Logging.logError({
@@ -619,5 +609,5 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
async abstract sendInvoiceToUser(invoice: BillingInvoice): Promise<BillingInvoice>;
- async abstract downloadInvoicePdf(invoice: BillingInvoice): Promise<BillingInvoicePdf>;
+ async abstract downloadInvoiceDocument(invoice: BillingInvoice): Promise<BillingInvoiceDocument>;
}

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
-import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoicePdf, BillingInvoiceStatus, BillingMethod, BillingStatus, BillingTax, BillingUser } from '../../../types/Billing';
+import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingMethod, BillingStatus, BillingTax, BillingUser } from '../../../types/Billing';
import Stripe, { IResourceObject } from 'stripe';
import BackendError from '../../../exception/BackendError';
@@ -11,6 +11,7 @@ import Logging from '../../../utils/Logging';
import { ServerAction } from '../../../types/Server';
import { StripeBillingSetting } from '../../../types/Setting';
import Transaction from '../../../types/Transaction';
+import { DocumentEncoding, DocumentType } from '../../../types/GlobalType';
import User from '../../../types/User';
import UserStorage from '../../../storage/mongodb/UserStorage';
import Utils from '../../../utils/Utils';
@@ -406,10 +407,10 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
// Save generated pdf
if (invoice.downloadUrl && invoice.downloadUrl !== '') {
// Download PDF file
- const invoicePdf = await this.downloadInvoicePdf(invoice);
+ const invoiceDocument = await this.downloadInvoiceDocument(invoice);
// Save PDF file into database
try {
- await BillingStorage.saveInvoicePdf(this.tenantID, invoicePdf);
+ await BillingStorage.saveInvoiceDocument(this.tenantID, invoiceDocument);
} catch (error) {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
@@ -436,12 +437,20 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
return invoice;
}
- public async downloadInvoicePdf(invoice: BillingInvoice): Promise<BillingInvoicePdf> {
+ public async downloadInvoiceDocument(invoice: BillingInvoice): Promise<BillingInvoiceDocument> {
if (invoice.downloadUrl && invoice.downloadUrl !== '') {
+ // Get document
const response = await axios.get(invoice.downloadUrl, { responseType: 'arraybuffer' });
+ // Convert
const base64Image = Buffer.from(response.data).toString('base64');
- const encodedPdf = 'data:' + response.headers['content-type'] + ';base64,' + base64Image;
- return { id: invoice.id, encodedPdf: encodedPdf } as BillingInvoicePdf;
+ const content = 'data:' + response.headers['content-type'] + ';base64,' + base64Image;
+ return {
+ id: invoice.id,
+ invoiceID: invoice.invoiceID,
+ content: content,
+ type: DocumentType.PDF,
+ encoding: DocumentEncoding.BASE64
+ };
}
}

MODIFY
src/server/rest/service/AssetService.ts
src/server/rest/service/AssetService.ts
@@ -78,7 +78,7 @@ export default class AssetService {
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.ASSET,
Action.RETRIEVE_CONSUMPTION, Entity.ASSET, MODULE_NAME, 'handleRetrieveConsumption');
// Is authorized to check connection ?
- if (!Authorizations.canRefreshAssetConnection(req.user)) {
+ if (!Authorizations.canRetrieveAssetConsumption(req.user)) {
throw new AppAuthError({
errorCode: HTTPAuthError.ERROR,
user: req.user,

MODIFY
src/server/rest/service/BillingService.ts
src/server/rest/service/BillingService.ts
@@ -368,12 +368,12 @@ export default class BillingService {
});
}
- const invoicepdf = await BillingStorage.getInvoicePdf(req.user.tenantID, invoice.id);
+ const invoicepdf = await BillingStorage.getInvoiceDocument(req.user.tenantID, invoice.id);
UtilsService.assertObjectExists(action, invoicepdf, `Invoice '${filteredRequest.invoiceID}' does not exist`,
MODULE_NAME, 'handleDownloadInvoice', req.user);
- if (invoicepdf && invoicepdf.encodedPdf) {
- const base64RawData = invoicepdf.encodedPdf.split(';base64,').pop();
+ if (invoicepdf && invoicepdf.content) {
+ const base64RawData = invoicepdf.content.split(';base64,').pop();
const filename = 'invoice_' + invoice.invoiceID + '.pdf';
fs.writeFile(filename, base64RawData, { encoding: 'base64' }, (err) => {
if (err) {

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
-import { BillingInvoice, BillingInvoicePdf, BillingInvoiceStatus } from '../../types/Billing';
+import { BillingInvoice, BillingInvoiceDocument, BillingInvoiceStatus } from '../../types/Billing';
import Constants from '../../utils/Constants';
import { DataResult } from '../../types/DataResult';
@@ -179,39 +179,40 @@ export default class BillingStorage {
return invoiceMDB._id.toHexString();
}
- public static async saveInvoicePdf(tenantId: string, invoicePdf: BillingInvoicePdf): Promise<BillingInvoicePdf> {
+ public static async saveInvoiceDocument(tenantId: string, invoiceDocument: BillingInvoiceDocument): Promise<BillingInvoiceDocument> {
// Debug
- const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'saveInvoicePdf');
+ const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'saveInvoiceDocument');
// Build Request
// Properties to save
- const invoiceFileMDB: any = {
- _id: invoicePdf.id ? Utils.convertToObjectID(invoicePdf.id) : new ObjectID(),
- encodedPdf: invoicePdf.encodedPdf
+ const invoiceDocumentMDB: any = {
+ _id: invoiceDocument.id,
+ type: invoiceDocument.type,
+ invoiceID: invoiceDocument.invoiceID,
+ encoding: invoiceDocument.encoding,
+ content: invoiceDocument.content
};
// Modify and return the modified document
- await global.database.getCollection<BillingInvoicePdf>(tenantId, 'invoicespdf').findOneAndReplace(
- { _id: invoiceFileMDB._id },
- invoiceFileMDB,
+ await global.database.getCollection<BillingInvoiceDocument>(tenantId, 'invoicedocuments').findOneAndReplace(
+ { _id: invoiceDocumentMDB._id },
+ invoiceDocumentMDB,
{ upsert: true }
);
// Debug
- Logging.traceEnd(MODULE_NAME, 'saveInvoicePdf', uniqueTimerID, { invoiceFileMDB });
- return invoiceFileMDB._id.toHexString();
+ Logging.traceEnd(MODULE_NAME, 'saveInvoiceDocument', uniqueTimerID, { id: invoiceDocumentMDB._id });
+ return invoiceDocumentMDB._id.toHexString();
}
- public static async getInvoicePdf(tenantID: string, id: string): Promise<BillingInvoicePdf> {
+ public static async getInvoiceDocument(tenantID: string, id: string): Promise<BillingInvoiceDocument> {
// Debug
- const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getInvoicePdf');
+ const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'getInvoiceDocument');
// Check Tenant
await Utils.checkTenant(tenantID);
// Read DB
- const invoicePdfMDB: { _id: string; encodedPdf: string } = await global.database.getCollection(tenantID, 'invoicespdf')
+ const invoiceDocumentMDB = await global.database.getCollection<BillingInvoiceDocument>(tenantID, 'invoicedocuments')
.findOne({ _id: Utils.convertToObjectID(id) });
// Debug
- Logging.traceEnd(MODULE_NAME, 'getInvoicePdf', uniqueTimerID, { id });
- return {
- id: id, encodedPdf: (invoicePdfMDB ? invoicePdfMDB.encodedPdf : null)
- };
+ Logging.traceEnd(MODULE_NAME, 'getInvoiceDocument', uniqueTimerID, { id });
+ return invoiceDocumentMDB;
}
public static async deleteInvoice(tenantID: string, id: string): Promise<void> {
@@ -219,9 +220,12 @@ export default class BillingStorage {
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'deleteInvoice');
// Check Tenant
await Utils.checkTenant(tenantID);
- // Delete the Company
+ // Delete the Invoice
await global.database.getCollection<BillingInvoice>(tenantID, 'invoices')
.findOneAndDelete({ '_id': Utils.convertToObjectID(id) });
+ // Delete the Invoice Document
+ await global.database.getCollection<BillingInvoice>(tenantID, 'invoicedocuments')
+ .findOneAndDelete({ '_id': Utils.convertToObjectID(id) });
// Debug
Logging.traceEnd(MODULE_NAME, 'deleteInvoice', uniqueTimerID, { id });
}
@@ -231,9 +235,12 @@ export default class BillingStorage {
const uniqueTimerID = Logging.traceStart(MODULE_NAME, 'deleteInvoice');
// Check Tenant
await Utils.checkTenant(tenantID);
- // Delete the Company
+ // Delete the Invoice
await global.database.getCollection<BillingInvoice>(tenantID, 'invoices')
.findOneAndDelete({ 'invoiceID': id });
+ // Delete the Invoice Document
+ await global.database.getCollection<BillingInvoice>(tenantID, 'invoicedocuments')
+ .findOneAndDelete({ 'invoiceID': id });
// Debug
Logging.traceEnd(MODULE_NAME, 'deleteInvoice', uniqueTimerID, { id });
}

MODIFY
src/types/Billing.ts
src/types/Billing.ts
-import { ActionsResponse } from './GlobalType';
+import { ActionsResponse, DocumentEncoding, DocumentType } from './GlobalType';
import User from './User';
export interface BillingTransactionData {
@@ -64,6 +64,7 @@ export interface BillingTax {
export interface BillingInvoice {
id: string;
invoiceID: string;
+ userID?: string;
user?: User;
number?: string;
status?: BillingInvoiceStatus;
@@ -88,7 +89,10 @@ export enum BillingInvoiceStatus {
DRAFT = 'draft',
}
-export interface BillingInvoicePdf {
+export interface BillingInvoiceDocument {
id: string;
- encodedPdf: string; // Base64 format
+ invoiceID: string;
+ content: string; // Base64 format
+ type: DocumentType;
+ encoding: DocumentEncoding;
}

MODIFY
src/types/GlobalType.ts
src/types/GlobalType.ts
@@ -26,6 +26,14 @@ export interface ActionsResponse {
inError: number;
}
+export enum DocumentType {
+ PDF = 'pdf',
+}
+
+export enum DocumentEncoding {
+ BASE64 = 'base64',
+}
+
interface TSGlobal extends Global {
database: MongoDBStorage;
appRoot: string;

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -15,7 +15,6 @@ import validator from 'validator';
import Authorizations from '../authorization/Authorizations';
import AppError from '../exception/AppError';
import BackendError from '../exception/BackendError';
-import AssetFactory from '../integration/asset/AssetFactory';
import TenantStorage from '../storage/mongodb/TenantStorage';
import UserStorage from '../storage/mongodb/UserStorage';
import Asset from '../types/Asset';
@@ -42,7 +41,6 @@ import Configuration from './Configuration';
import Constants from './Constants';
import Cypher from './Cypher';
import Logging from './Logging';
-import AssetStorage from '../storage/mongodb/AssetStorage';
const _centralSystemFrontEndConfig = Configuration.getCentralSystemFrontEndConfig();

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -279,7 +279,7 @@ describe('Billing Service', function() {
it('Should download invoice as PDF', async () => {
const response = await testData.userService.billingApi.readAll({ Status: BillingInvoiceStatus.OPEN }, TestConstants.DEFAULT_PAGING, TestConstants.DEFAULT_ORDERING, '/client/api/UserInvoices');
expect(response.data.result.length).to.be.gt(0);
- const downloadResponse = await testData.userService.billingApi.downloadInvoicePdf({ invoiceID : response.data.result[0].invoiceID });
+ const downloadResponse = await testData.userService.billingApi.downloadInvoiceDocument({ invoiceID : response.data.result[0].invoiceID });
expect(downloadResponse.headers['content-type']).to.be.eq('application/pdf');
});
@@ -410,7 +410,7 @@ describe('Billing Service', function() {
it('Should download invoice as PDF', async () => {
const response = await testData.userService.billingApi.readAll({ Status: BillingInvoiceStatus.OPEN }, TestConstants.DEFAULT_PAGING, TestConstants.DEFAULT_ORDERING, '/client/api/UserInvoices');
expect(response.data.result.length).to.be.gt(0);
- const downloadResponse = await testData.userService.billingApi.downloadInvoicePdf({ invoiceID : response.data.result[0].invoiceID });
+ const downloadResponse = await testData.userService.billingApi.downloadInvoiceDocument({ invoiceID : response.data.result[0].invoiceID });
expect(downloadResponse.headers['content-type']).to.be.eq('application/pdf');
});
});

MODIFY
test/api/client/BillingApi.ts
test/api/client/BillingApi.ts
@@ -26,7 +26,7 @@ export default class BillingApi extends CrudApi {
return await super.create(params, '/client/api/BillingSynchronizeInvoices');
}
- public async downloadInvoicePdf(params?) {
+ public async downloadInvoiceDocument(params?) {
return await super.read(params, '/client/api/BillingDownloadInvoice');
}
}

Small refactoring on sync invoices, delete also PDF document when deleting an invoice, Auth renamed, Renamed PDF -> Document + added encoding, type and invoiceID