4077-login_recover-password & account_verifyEmail #1063

Merged
alexm merged 52 commits from 4077-login_recover-password into dev 2022-11-28 11:34:03 +00:00
34 changed files with 339 additions and 48 deletions
Showing only changes of commit a07a45c1b6 - Show all commits

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Receipt', 'receiptPdf', '*', 'ALLOW', 'ROLE', 'salesAssistant');

View File

@ -0,0 +1,2 @@
INSERT INTO `vn`.`payDem` (id,payDem)
VALUES (7,'0');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,principalId)
VALUES ('Supplier','newSupplier','WRITE','administrative');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`supplier` MODIFY COLUMN payMethodFk tinyint(3) unsigned NULL;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`supplier` MODIFY COLUMN supplierActivityFk varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL NULL;

View File

@ -1,7 +1,9 @@
drop procedure `vn`.`ticket_closeByTicket`; drop procedure `vn`.`ticket_closeByTicket`;
DELIMITER $$
$$
create create
definer = root@localhost procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int) definer = `root`@`localhost` procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int)
BEGIN BEGIN
/** /**
@ -27,5 +29,7 @@ BEGIN
CALL ticket_close(); CALL ticket_close();
DROP TEMPORARY TABLE tmp.ticket_close; DROP TEMPORARY TABLE tmp.ticket_close;
END; END$$
DELIMITER ;

View File

@ -0,0 +1,5 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('ClaimRma', '*', 'READ', 'ALLOW', 'ROLE', 'claimManager'),
('ClaimRma', '*', 'WRITE', 'ALLOW', 'ROLE', 'claimManager');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`claim` ADD rma varchar(100) NULL ;

View File

@ -0,0 +1,7 @@
CREATE TABLE `vn`.`claimRma` (
id INT UNSIGNED auto_increment NOT NULL PRIMARY KEY,
code varchar(100) NOT NULL,
created timestamp DEFAULT current_timestamp() NOT NULL,
workerFk INTEGER UNSIGNED NOT NULL
)
ENGINE=InnoDB;

View File

@ -1380,13 +1380,6 @@ INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed
(7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'), (7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'),
(8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 8', 1, 1, '', ''); (8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 8', 1, 1, '', '');
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(1101, 500, NULL, 0.00, 0.00, 1.00),
(1102, 1000, 2.00, 0.01, 0.05, 1.00),
(1103, 2000, 0.00, 0.00, 0.02, 1.00),
(1104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`) INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`)
VALUES VALUES
('CharlesXavier', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', 1, 1, '1062', '51', '4.8'), ('CharlesXavier', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', 1, 1, '1062', '51', '4.8'),
@ -1743,12 +1736,12 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`,
( 6, 'mana', 'Mana', 1, 4, 0), ( 6, 'mana', 'Mana', 1, 4, 0),
( 7, 'lack', 'Faltas', 1, 2, 0); ( 7, 'lack', 'Faltas', 1, 2, 0);
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`) INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`)
VALUES VALUES
(1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0), (1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0, '02676A049183'),
(2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1), (2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1, NULL),
(3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5), (3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5, NULL),
(4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10); (4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10, NULL);
INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`) INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`)
VALUES VALUES
@ -1790,6 +1783,23 @@ INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`)
(1, 'Contact description', 50), (1, 'Contact description', 50),
(2, 'Contact description', 30); (2, 'Contact description', 30);
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(1101, 500, NULL, 0.00, 0.00, 1.00),
(1102, 1000, 2.00, 0.01, 0.05, 1.00),
(1103, 2000, 0.00, 0.00, 0.02, 1.00),
(1104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`)
VALUES
(1, '02676A049183', DEFAULT, 1106),
(2, '02676A049183', DEFAULT, 1106),
(3, '02676A049183', DEFAULT, 1107),
(4, '02676A049183', DEFAULT, 1107),
(5, '01837B023653', DEFAULT, 1106);
INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`) INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`)
VALUES VALUES
(1, 'Arkham Bank', 442, 1, 'h12387193H10238'), (1, 'Arkham Bank', 442, 1, 'h12387193H10238'),

View File

@ -31,7 +31,7 @@ describe('Supplier fiscal data path', () => {
await page.clearInput(selectors.supplierFiscalData.taxNumber); await page.clearInput(selectors.supplierFiscalData.taxNumber);
await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number'); await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number');
await page.clearInput(selectors.supplierFiscalData.account); await page.clearInput(selectors.supplierFiscalData.account);
await page.write(selectors.supplierFiscalData.account, 'edited account number'); await page.write(selectors.supplierFiscalData.account, '0123456789');
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva'); await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva');
await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas'); await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas');
@ -70,7 +70,7 @@ describe('Supplier fiscal data path', () => {
it('should check the account was edited', async() => { it('should check the account was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value'); const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value');
expect(result).toEqual('edited account number'); expect(result).toEqual('0123456789');
}); });
it('should check the sageWihholding was edited', async() => { it('should check the sageWihholding was edited', async() => {

View File

@ -47,7 +47,7 @@ module.exports = Self => {
{ {
relation: 'claimState', relation: 'claimState',
scope: { scope: {
fields: ['id', 'description'] fields: ['id', 'code', 'description']
} }
}, },
{ {

View File

@ -2,6 +2,9 @@
"Claim": { "Claim": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ClaimContainer": {
"dataSource": "claimStorage"
},
"ClaimBeginning": { "ClaimBeginning": {
"dataSource": "vn" "dataSource": "vn"
}, },
@ -41,7 +44,7 @@
"ClaimObservation": { "ClaimObservation": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ClaimContainer": { "ClaimRma": {
"dataSource": "claimStorage" "dataSource": "vn"
} }
} }

View File

@ -0,0 +1,9 @@
const LoopBackContext = require('loopback-context');
module.exports = Self => {
Self.observe('before save', async function(ctx) {
const changes = ctx.data || ctx.instance;
const loopBackContext = LoopBackContext.getCurrentContext();
changes.workerFk = loopBackContext.active.accessToken.userId;
});
};

View File

@ -0,0 +1,30 @@
{
"name": "ClaimRma",
"base": "VnModel",
"options": {
"mysql": {
"table": "claimRma"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"code": {
"type": "string",
"required": true
},
"created": {
"type": "date"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
}
}
}

View File

@ -46,6 +46,9 @@
}, },
"packages": { "packages": {
"type": "number" "type": "number"
},
"rma": {
"type": "string"
} }
}, },
"relations": { "relations": {
@ -54,6 +57,12 @@
"model": "ClaimState", "model": "ClaimState",
"foreignKey": "claimStateFk" "foreignKey": "claimStateFk"
}, },
"claimRma": {
"type": "belongsTo",
"model": "ClaimRma",
"foreignKey": "rma",
"primaryKey": "code"
},
"client": { "client": {
"type": "belongsTo", "type": "belongsTo",
"model": "Client", "model": "Client",

View File

@ -25,10 +25,9 @@ module.exports = Self => {
const client = await Self.app.models.Client.findById(id, myOptions); const client = await Self.app.models.Client.findById(id, myOptions);
const emails = client.email ? client.email.split(',') : null;
const findParams = []; const findParams = [];
if (emails.length) { if (client.email) {
const emails = client.email.split(',');
for (let email of emails) for (let email of emails)
findParams.push({email: email}); findParams.push({email: email});
} }

View File

@ -0,0 +1,55 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('receiptPdf', {
description: 'Returns the receipt pdf',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The claim id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/receipt-pdf',
verb: 'GET'
}
});
Self.receiptPdf = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('receipt', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -2,6 +2,7 @@ const LoopBackContext = require('loopback-context');
module.exports = function(Self) { module.exports = function(Self) {
require('../methods/receipt/filter')(Self); require('../methods/receipt/filter')(Self);
require('../methods/receipt/receiptPdf')(Self);
Self.validateBinded('amountPaid', isNotZero, { Self.validateBinded('amountPaid', isNotZero, {
message: 'Amount cannot be zero', message: 'Amount cannot be zero',

View File

@ -144,12 +144,8 @@ class Controller extends Dialog {
}) })
.then(() => this.vnApp.showSuccess(this.$t('Data saved!'))) .then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => { .then(() => {
if (this.viewReceipt) { if (this.viewReceipt)
this.vnReport.show('receipt', { this.vnReport.show(`Receipts/${receiptId}/receipt-pdf`);
receiptId: receiptId,
companyId: this.companyFk
});
}
}); });
} }

View File

@ -85,6 +85,8 @@ describe('Client', () => {
}); });
it('should make an http POST query and then call to the report show() method', () => { it('should make an http POST query and then call to the report show() method', () => {
const receiptId = 1;
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.vnReport, 'show'); jest.spyOn(controller.vnReport, 'show');
window.open = jest.fn(); window.open = jest.fn();
@ -92,14 +94,12 @@ describe('Client', () => {
controller.$params = {id: 1101}; controller.$params = {id: 1101};
controller.viewReceipt = true; controller.viewReceipt = true;
$httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: 1}); $httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: receiptId});
controller.responseHandler('accept'); controller.responseHandler('accept');
$httpBackend.flush(); $httpBackend.flush();
const expectedParams = {receiptId: 1, companyId: 442};
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.vnReport.show).toHaveBeenCalledWith('receipt', expectedParams); expect(controller.vnReport.show).toHaveBeenCalledWith(`Receipts/${receiptId}/receipt-pdf`);
}); });
}); });

View File

@ -95,8 +95,8 @@ module.exports = Self => {
pm.name AS payMethod, pm.name AS payMethod,
pd.payDem AS payDem pd.payDem AS payDem
FROM vn.supplier s FROM vn.supplier s
JOIN vn.payMethod pm ON pm.id = s.payMethodFk LEFT JOIN vn.payMethod pm ON pm.id = s.payMethodFk
JOIN vn.payDem pd ON pd.id = s.payDemFk` LEFT JOIN vn.payDem pd ON pd.id = s.payDemFk`
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));

View File

@ -0,0 +1,45 @@
module.exports = Self => {
Self.remoteMethod('newSupplier', {
description: 'Creates a new supplier and returns it',
accessType: 'WRITE',
accepts: [{
arg: 'params',
type: 'object',
http: {source: 'body'}
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/newSupplier`,
verb: 'POST'
}
});
Self.newSupplier = async params => {
const models = Self.app.models;
const myOptions = {};
if (typeof(params) == 'string')
params = JSON.parse(params);
params.nickname = params.name;
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const supplier = await models.Supplier.create(params, myOptions);
if (tx) await tx.commit();
return supplier;
} catch (e) {
if (tx) await tx.rollback();
return params;
}
};
};

View File

@ -10,7 +10,7 @@ describe('Supplier filter()', () => {
let result = await app.models.Supplier.filter(ctx); let result = await app.models.Supplier.filter(ctx);
expect(result.length).toEqual(1); expect(result.length).toBeGreaterThanOrEqual(1);
expect(result[0].id).toEqual(1); expect(result[0].id).toEqual(1);
}); });

View File

@ -0,0 +1,30 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Supplier newSupplier()', () => {
const newSupp = {
name: 'TestSupplier-1'
};
const administrativeId = 5;
it('should create a new supplier containing only the name', async() => {
const activeCtx = {
accessToken: {userId: administrativeId},
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
let result = await app.models.Supplier.newSupplier(JSON.stringify(newSupp));
expect(result.name).toEqual('TestSupplier-1');
expect(result.id).toEqual(443);
const createdSupplier = await app.models.Supplier.findById(result.id);
expect(createdSupplier.id).toEqual(result.id);
expect(createdSupplier.name).toEqual(result.name);
expect(createdSupplier.payDemFk).toEqual(7);
expect(createdSupplier.nickname).toEqual(result.name);
});
});

View File

@ -10,6 +10,7 @@ module.exports = Self => {
require('../methods/supplier/freeAgencies')(Self); require('../methods/supplier/freeAgencies')(Self);
require('../methods/supplier/campaignMetricsPdf')(Self); require('../methods/supplier/campaignMetricsPdf')(Self);
require('../methods/supplier/campaignMetricsEmail')(Self); require('../methods/supplier/campaignMetricsEmail')(Self);
require('../methods/supplier/newSupplier')(Self);
Self.validatesPresenceOf('name', { Self.validatesPresenceOf('name', {
message: 'The social name cannot be empty' message: 'The social name cannot be empty'
@ -19,13 +20,17 @@ module.exports = Self => {
message: 'The supplier name must be unique' message: 'The supplier name must be unique'
}); });
if (this.city) {
Self.validatesPresenceOf('city', { Self.validatesPresenceOf('city', {
message: 'City cannot be empty' message: 'City cannot be empty'
}); });
}
if (this.nif) {
Self.validatesPresenceOf('nif', { Self.validatesPresenceOf('nif', {
message: 'The nif cannot be empty' message: 'The nif cannot be empty'
}); });
}
Self.validatesUniquenessOf('nif', { Self.validatesUniquenessOf('nif', {
message: 'TIN must be unique' message: 'TIN must be unique'
@ -57,6 +62,9 @@ module.exports = Self => {
} }
async function tinIsValid(err, done) { async function tinIsValid(err, done) {
if (!this.countryFk)
return done();
const filter = { const filter = {
fields: ['code'], fields: ['code'],
where: {id: this.countryFk} where: {id: this.countryFk}
@ -80,6 +88,7 @@ module.exports = Self => {
}); });
async function hasSupplierAccount(err, done) { async function hasSupplierAccount(err, done) {
if (!this.payMethodFk) return done();
const payMethod = await Self.app.models.PayMethod.findById(this.payMethodFk); const payMethod = await Self.app.models.PayMethod.findById(this.payMethodFk);
const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}}); const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}});
const hasIban = supplierAccount && supplierAccount.iban; const hasIban = supplierAccount && supplierAccount.iban;
@ -92,6 +101,7 @@ module.exports = Self => {
} }
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
if (ctx.isNewInstance) return;
const loopbackContext = LoopBackContext.getCurrentContext(); const loopbackContext = LoopBackContext.getCurrentContext();
const changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance; const orgData = ctx.currentInstance;

View File

@ -0,0 +1,29 @@
<vn-watcher
vn-id="watcher"
url="suppliers/newSupplier"
data="$ctrl.supplier"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Supplier name"
ng-model="$ctrl.supplier.name"
vn-focus>
</vn-textfield>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="supplier.index">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,23 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
onSubmit() {
this.$.watcher.submit().then(
json => {
this.$state.go(`supplier.card.fiscalData`, {id: json.data.id});
}
);
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnSupplierCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -20,3 +20,4 @@ import './address/create';
import './address/edit'; import './address/edit';
import './agency-term/index'; import './agency-term/index';
import './agency-term/create'; import './agency-term/create';
import './create/index';

View File

@ -59,3 +59,6 @@
supplier="$ctrl.supplierSelected"> supplier="$ctrl.supplierSelected">
</vn-supplier-summary> </vn-supplier-summary>
</vn-popup> </vn-popup>
<a vn-acl-action="remove" vn-acl="administrative" ui-sref="supplier.create" vn-tooltip="New supplier" vn-bind="+" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -3,3 +3,4 @@ Pay day: Dia de pago
Account: Cuenta Account: Cuenta
Pay method: Metodo de pago Pay method: Metodo de pago
Tax number: Nif Tax number: Nif
New supplier: Nuevo proveedor

View File

@ -52,6 +52,13 @@
"supplier": "$ctrl.supplier" "supplier": "$ctrl.supplier"
} }
}, },
{
"url": "/create",
"state": "supplier.create",
"component": "vn-supplier-create",
"acl": ["administrative"],
"description": "New supplier"
},
{ {
"url": "/basic-data", "url": "/basic-data",
"state": "supplier.card.basicData", "state": "supplier.card.basicData",

View File

@ -7,8 +7,12 @@ const componentsPath = path.resolve(__dirname, './core/components');
module.exports = { module.exports = {
async boot(app) { async boot(app) {
// Init database instance // Extended locale intl polyfill
const IntlPolyfill = require('intl');
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
// Init database instance
require('./core/database').init(app.dataSources); require('./core/database').init(app.dataSources);
require('./core/smtp').init(); require('./core/smtp').init();
require('./core/cluster').init(); require('./core/cluster').init();