mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
- feat: remove unnecessary migrations, controllers and models files.
- feat: metable store - feat: metable store with settings store. - feat: settings middleware to auto-save and load. - feat: DI db manager to master container. - feat: write some logs to sale invoices.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { Service } from "typedi";
|
||||
|
||||
@Service()
|
||||
export default class AuthenticationMailMesssages {
|
||||
|
||||
sendWelcomeMessage() {
|
||||
const Logger = Container.get('logger');
|
||||
const Mail = Container.get('mail');
|
||||
|
||||
const filePath = path.join(global.rootPath, 'views/mail/Welcome.html');
|
||||
const template = fs.readFileSync(filePath, 'utf8');
|
||||
const rendered = Mustache.render(template, {
|
||||
email, organizationName, firstName,
|
||||
});
|
||||
const mailOptions = {
|
||||
to: email,
|
||||
from: `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`,
|
||||
subject: 'Welcome to Bigcapital',
|
||||
html: rendered,
|
||||
};
|
||||
Mail.sendMail(mailOptions, (error) => {
|
||||
if (error) {
|
||||
Logger.error('Failed send welcome mail', { error, form });
|
||||
done(error);
|
||||
return;
|
||||
}
|
||||
Logger.info('User has been sent welcome email successfuly.', { form });
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
sendResetPasswordMessage() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Service } from "typedi";
|
||||
|
||||
@Service()
|
||||
export default class AuthenticationSMSMessages {
|
||||
smsClient: any;
|
||||
|
||||
sendWelcomeMessage() {
|
||||
const message: string = `Hi ${firstName}, Welcome to Bigcapital, You've joined the new workspace,
|
||||
if you need any help please don't hesitate to contact us.`
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import JWT from 'jsonwebtoken';
|
||||
import uniqid from 'uniqid';
|
||||
import { omit } from 'lodash';
|
||||
import {
|
||||
EventDispatcher
|
||||
EventDispatcherInterface
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from '@/decorators/eventDispatcher';
|
||||
import {
|
||||
SystemUser,
|
||||
@@ -22,6 +22,8 @@ import { hashPassword } from '@/utils';
|
||||
import { ServiceError, ServiceErrors } from "@/exceptions";
|
||||
import config from '@/../config/config';
|
||||
import events from '@/subscribers/events';
|
||||
import AuthenticationMailMessages from '@/services/Authentication/AuthenticationMailMessages';
|
||||
import AuthenticationSMSMessages from '@/services/Authentication/AuthenticationSMSMessages';
|
||||
|
||||
@Service()
|
||||
export default class AuthenticationService {
|
||||
@@ -34,6 +36,12 @@ export default class AuthenticationService {
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
@Inject()
|
||||
smsMessages: AuthenticationSMSMessages;
|
||||
|
||||
@Inject()
|
||||
mailMessages: AuthenticationMailMessages;
|
||||
|
||||
/**
|
||||
* Signin and generates JWT token.
|
||||
* @throws {ServiceError}
|
||||
@@ -70,6 +78,7 @@ export default class AuthenticationService {
|
||||
|
||||
this.logger.info('[login] Logging success.', { user, token });
|
||||
|
||||
// Triggers `onLogin` event.
|
||||
this.eventDispatcher.dispatch(events.auth.login, {
|
||||
emailOrPhone, password,
|
||||
});
|
||||
@@ -191,6 +200,7 @@ export default class AuthenticationService {
|
||||
const passwordReset = await PasswordReset.query().insert({ email, token });
|
||||
const user = await SystemUser.query().findOne('email', email);
|
||||
|
||||
// Triggers `onSendResetPassword` event.
|
||||
this.eventDispatcher.dispatch(events.auth.sendResetPassword, { user, token });
|
||||
|
||||
return passwordReset;
|
||||
@@ -225,25 +235,26 @@ export default class AuthenticationService {
|
||||
// Delete the reset password token.
|
||||
await PasswordReset.query().where('email', user.email).delete();
|
||||
|
||||
this.eventDispatcher.dispatch(events.auth.sendResetPassword, { user, token, password });
|
||||
// Triggers `onResetPassword` event.
|
||||
this.eventDispatcher.dispatch(events.auth.resetPassword, { user, token, password });
|
||||
|
||||
this.logger.info('[reset_password] reset password success.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates JWT token for the given user.
|
||||
* @param {IUser} user
|
||||
* @param {ISystemUser} user
|
||||
* @return {string} token
|
||||
*/
|
||||
generateToken(user: IUser): string {
|
||||
generateToken(user: ISystemUser): string {
|
||||
const today = new Date();
|
||||
const exp = new Date(today);
|
||||
exp.setDate(today.getDate() + 60);
|
||||
|
||||
this.logger.silly(`Sign JWT for userId: ${user._id}`);
|
||||
this.logger.silly(`Sign JWT for userId: ${user.id}`);
|
||||
return JWT.sign(
|
||||
{
|
||||
_id: user._id, // We are gonna use this in the middleware 'isAuth'
|
||||
id: user.id, // We are gonna use this in the middleware 'isAuth'
|
||||
exp: exp.getTime() / 1000,
|
||||
},
|
||||
config.jwtSecret,
|
||||
|
||||
31
server/src/services/InviteUsers/InviteUsersMailMessages.ts
Normal file
31
server/src/services/InviteUsers/InviteUsersMailMessages.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Service } from "typedi";
|
||||
|
||||
@Service()
|
||||
export default class InviteUsersMailMessages {
|
||||
|
||||
sendInviteMail() {
|
||||
const filePath = path.join(global.rootPath, 'views/mail/UserInvite.html');
|
||||
const template = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
const rendered = Mustache.render(template, {
|
||||
acceptUrl: `${req.protocol}://${req.hostname}/invite/accept/${invite.token}`,
|
||||
fullName: `${user.firstName} ${user.lastName}`,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
email: user.email,
|
||||
organizationName: organizationOptions.getMeta('organization_name'),
|
||||
});
|
||||
const mailOptions = {
|
||||
to: user.email,
|
||||
from: `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`,
|
||||
subject: `${user.fullName} has invited you to join a Bigcapital`,
|
||||
html: rendered,
|
||||
};
|
||||
mail.sendMail(mailOptions, (error) => {
|
||||
if (error) {
|
||||
Logger.log('error', 'Failed send user invite mail', { error, form });
|
||||
}
|
||||
Logger.log('info', 'User has been sent invite user email successfuly.', { form });
|
||||
});
|
||||
}
|
||||
}
|
||||
172
server/src/services/InviteUsers/index.ts
Normal file
172
server/src/services/InviteUsers/index.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { Service, Inject } from "typedi";
|
||||
import uniqid from 'uniqid';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
} from '@/decorators/eventDispatcher';
|
||||
import { ServiceError, ServiceErrors } from "@/exceptions";
|
||||
import { SystemUser, Invite } from "@/system/models";
|
||||
import { hashPassword } from '@/utils';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import TenantsManager from "@/system/TenantsManager";
|
||||
import InviteUsersMailMessages from "@/services/InviteUsers/InviteUsersMailMessages";
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
ISystemUser,
|
||||
IInviteUserInput,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class InviteUserService {
|
||||
@EventDispatcher()
|
||||
eventDispatcher: EventDispatcherInterface;
|
||||
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
tenantsManager: TenantsManager;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
@Inject()
|
||||
mailMessages: InviteUsersMailMessages;
|
||||
|
||||
/**
|
||||
* Accept the received invite.
|
||||
* @param {string} token
|
||||
* @param {IInviteUserInput} inviteUserInput
|
||||
* @throws {ServiceErrors}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async acceptInvite(token: string, inviteUserInput: IInviteUserInput): Promise<void> {
|
||||
const inviteToken = await this.getInviteOrThrowError(token);
|
||||
await this.validateUserEmailAndPhone(inviteUserInput);
|
||||
|
||||
this.logger.info('[aceept_invite] trying to hash the user password.');
|
||||
const hashedPassword = await hashPassword(inviteUserInput.password);
|
||||
|
||||
const user = SystemUser.query()
|
||||
.where('email', inviteUserInput.email)
|
||||
.patch({
|
||||
...inviteUserInput,
|
||||
active: 1,
|
||||
email: inviteToken.email,
|
||||
invite_accepted_at: moment().format('YYYY/MM/DD'),
|
||||
password: hashedPassword,
|
||||
tenant_id: inviteToken.tenantId,
|
||||
});
|
||||
|
||||
const deleteInviteTokenOper = Invite.query().where('token', inviteToken.token).delete();
|
||||
|
||||
await Promise.all([
|
||||
insertUserOper,
|
||||
deleteInviteTokenOper,
|
||||
]);
|
||||
|
||||
// Triggers `onUserAcceptInvite` event.
|
||||
this.eventDispatcher.dispatch(events.inviteUser.acceptInvite, {
|
||||
inviteToken, user,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends invite mail to the given email from the given tenant and user.
|
||||
* @param {number} tenantId -
|
||||
* @param {string} email -
|
||||
* @param {IUser} authorizedUser -
|
||||
*
|
||||
* @return {Promise<IInvite>}
|
||||
*/
|
||||
public async sendInvite(tenantId: number, email: string, authorizedUser: ISystemUser): Promise<IInvite> {
|
||||
const { Option } = this.tenancy.models(tenantId);
|
||||
await this.throwErrorIfUserEmailExists(email);
|
||||
|
||||
const invite = await Invite.query().insert({
|
||||
email,
|
||||
tenant_id: authorizedUser.tenantId,
|
||||
token: uniqid(),
|
||||
});
|
||||
|
||||
// Triggers `onUserSendInvite` event.
|
||||
this.eventDispatcher.dispatch(events.inviteUser.sendInvite, {
|
||||
invite,
|
||||
});
|
||||
return { invite };
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given invite token.
|
||||
* @param {string} token - the given token string.
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
public async checkInvite(token: string) {
|
||||
const inviteToken = await this.getInviteOrThrowError(token)
|
||||
|
||||
// Find the tenant that associated to the given token.
|
||||
const tenant = await Tenant.query().findOne('id', inviteToken.tenantId);
|
||||
|
||||
const tenantDb = this.tenantsManager.knexInstance(tenant.organizationId);
|
||||
|
||||
const organizationOptions = await Option.bindKnex(tenantDb).query()
|
||||
.where('key', 'organization_name');
|
||||
|
||||
// Triggers `onUserCheckInvite` event.
|
||||
this.eventDispatcher.dispatch(events.inviteUser.checkInvite, {
|
||||
inviteToken, organizationOptions,
|
||||
});
|
||||
return { inviteToken, organizationOptions };
|
||||
}
|
||||
|
||||
private async throwErrorIfUserEmailExists(email: string) {
|
||||
const foundUser = await SystemUser.query().findOne('email', email);
|
||||
|
||||
if (foundUser) {
|
||||
throw new ServiceError('email_already_invited');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve invite model from the given token or throw error.
|
||||
* @param {string} token - Then given token string.
|
||||
* @throws {ServiceError}
|
||||
*/
|
||||
private async getInviteOrThrowError(token: string) {
|
||||
const inviteToken = await Invite.query().findOne('token', token);
|
||||
|
||||
if (!inviteToken) {
|
||||
this.logger.info('[aceept_invite] the invite token is invalid.');
|
||||
throw new ServiceError('invite_token_invalid');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the given user email and phone number uniquine.
|
||||
* @param {IInviteUserInput} inviteUserInput
|
||||
*/
|
||||
private async validateUserEmailAndPhone(inviteUserInput: IInviteUserInput) {
|
||||
const foundUser = await SystemUser.query()
|
||||
.onBuild(query => {
|
||||
query.where('email', inviteUserInput.email);
|
||||
|
||||
if (inviteUserInput.phoneNumber) {
|
||||
query.where('phone_number', inviteUserInput.phoneNumber);
|
||||
}
|
||||
});
|
||||
const serviceErrors: ServiceError[] = [];
|
||||
|
||||
if (foundUser && foundUser.email === inviteUserInput.email) {
|
||||
this.logger.info('[send_user_invite] the given email exists.');
|
||||
serviceErrors.push(new ServiceError('email_exists'));
|
||||
}
|
||||
if (foundUser && foundUser.phoneNumber === inviteUserInput.phoneNumber) {
|
||||
this.logger.info('[send_user_invite] the given phone number exists.');
|
||||
serviceErrors.push(new ServiceError('phone_number_exists'));
|
||||
}
|
||||
if (serviceErrors.length > 0) {
|
||||
throw new ServiceErrors(serviceErrors);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import winston from 'winston';
|
||||
|
||||
const transports = {
|
||||
console: new winston.transports.Console({ level: 'warn' }),
|
||||
file: new winston.transports.File({ filename: 'stdout.log' }),
|
||||
};
|
||||
|
||||
export default winston.createLogger({
|
||||
transports: [
|
||||
transports.console,
|
||||
transports.file,
|
||||
],
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
import Moment from 'moment';
|
||||
import { extendMoment } from 'moment-range';
|
||||
|
||||
const moment = extendMoment(Moment);
|
||||
|
||||
export default moment;
|
||||
56
server/src/services/Organization/index.ts
Normal file
56
server/src/services/Organization/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Service, Inject, Container } from 'typedi';
|
||||
import { Tenant } from '@/system/models';
|
||||
import TenantsManager from '@/system/TenantsManager';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ITenant } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class OrganizationService {
|
||||
@Inject()
|
||||
tenantsManager: TenantsManager;
|
||||
|
||||
@Inject('dbManager')
|
||||
dbManager: any;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Builds the database schema and seed data of the given organization id.
|
||||
* @param {srting} organizationId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async build(organizationId: string): Promise<void> {
|
||||
const tenant = await Tenant.query().findOne('organization_id', organizationId);
|
||||
this.throwIfTenantNotExists(tenant);
|
||||
this.throwIfTenantInitizalized(tenant);
|
||||
|
||||
this.logger.info('[tenant_db_build] tenant DB creating.', { tenant });
|
||||
await this.dbManager.createDb(`bigcapital_tenant_${tenant.organizationId}`);
|
||||
|
||||
const tenantDb = this.tenantsManager.knexInstance(tenant.organizationId);
|
||||
|
||||
this.logger.info('[tenant_db_build] tenant DB migrating to latest version.', { tenant });
|
||||
await tenantDb.migrate.latest();
|
||||
|
||||
this.logger.info('[tenant_db_build] mark tenant as initialized.', { tenant });
|
||||
await tenant.$query().update({ initialized: true });
|
||||
}
|
||||
|
||||
private throwIfTenantNotExists(tenant: ITenant) {
|
||||
if (!tenant) {
|
||||
this.logger.info('[tenant_db_build] organization id not found.');
|
||||
throw new ServiceError('tenant_not_found');
|
||||
}
|
||||
}
|
||||
|
||||
private throwIfTenantInitizalized(tenant: ITenant) {
|
||||
if (tenant.initialized) {
|
||||
throw new ServiceError('tenant_initialized');
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import cache from 'memory-cache';
|
||||
import { difference } from 'lodash';
|
||||
import Role from '@/models/Role';
|
||||
|
||||
export default {
|
||||
cacheKey: 'ratteb.cache,',
|
||||
cacheExpirationTime: null,
|
||||
permissions: [],
|
||||
cache: null,
|
||||
|
||||
/**
|
||||
* Initialize the cache.
|
||||
*/
|
||||
initializeCache() {
|
||||
if (!this.cache) {
|
||||
this.cache = new cache.Cache();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Purge all cached permissions.
|
||||
*/
|
||||
forgetCachePermissions() {
|
||||
this.cache.del(this.cacheKey);
|
||||
this.permissions = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all stored permissions.
|
||||
*/
|
||||
async getPermissions() {
|
||||
if (this.permissions.length <= 0) {
|
||||
const cachedPerms = this.cache.get(this.cacheKey);
|
||||
|
||||
if (!cachedPerms) {
|
||||
this.permissions = await this.getPermissionsFromStorage();
|
||||
this.cache.put(this.cacheKey, this.permissions);
|
||||
} else {
|
||||
this.permissions = cachedPerms;
|
||||
}
|
||||
}
|
||||
return this.permissions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches all roles and permissions from the storage.
|
||||
*/
|
||||
async getPermissionsFromStorage() {
|
||||
const roles = await Role.fetchAll({
|
||||
withRelated: ['resources.permissions'],
|
||||
});
|
||||
return roles.toJSON();
|
||||
},
|
||||
|
||||
/**
|
||||
* Detarmine the given resource has the permissions.
|
||||
* @param {String} resource -
|
||||
* @param {Array} permissions -
|
||||
*/
|
||||
async hasPermissions(resource, permissions) {
|
||||
await this.getPermissions();
|
||||
|
||||
const userRoles = this.permissions.filter((role) => role.id === this.id);
|
||||
const perms = [];
|
||||
|
||||
userRoles.forEach((role) => {
|
||||
const roleResources = role.resources || [];
|
||||
const foundResource = roleResources.find((r) => r.name === resource);
|
||||
|
||||
if (foundResource && foundResource.permissions) {
|
||||
foundResource.permissions.forEach((p) => perms.push(p.name));
|
||||
}
|
||||
});
|
||||
const notAllowedPerms = difference(permissions, perms);
|
||||
return (notAllowedPerms.length <= 0);
|
||||
},
|
||||
};
|
||||
@@ -27,6 +27,9 @@ export default class PaymentReceiveService {
|
||||
@Inject()
|
||||
journalService: JournalPosterService;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Creates a new payment receive and store it to the storage
|
||||
* with associated invoices payment and journal transactions.
|
||||
@@ -43,6 +46,8 @@ export default class PaymentReceiveService {
|
||||
} = this.tenancy.models(tenantId);
|
||||
|
||||
const paymentAmount = sumBy(paymentReceive.entries, 'payment_amount');
|
||||
|
||||
this.logger.info('[payment_receive] inserting to the storage.');
|
||||
const storedPaymentReceive = await PaymentReceive.query()
|
||||
.insert({
|
||||
amount: paymentAmount,
|
||||
@@ -50,12 +55,15 @@ export default class PaymentReceiveService {
|
||||
});
|
||||
const storeOpers: Array<any> = [];
|
||||
|
||||
this.logger.info('[payment_receive] inserting associated entries to the storage.');
|
||||
paymentReceive.entries.forEach((entry: any) => {
|
||||
const oper = PaymentReceiveEntry.query()
|
||||
.insert({
|
||||
payment_receive_id: storedPaymentReceive.id,
|
||||
...entry,
|
||||
});
|
||||
|
||||
this.logger.info('[payment_receive] increment the sale invoice payment amount.');
|
||||
// Increment the invoice payment amount.
|
||||
const invoice = SaleInvoice.query()
|
||||
.where('id', entry.invoice_id)
|
||||
@@ -64,6 +72,8 @@ export default class PaymentReceiveService {
|
||||
storeOpers.push(oper);
|
||||
storeOpers.push(invoice);
|
||||
});
|
||||
|
||||
this.logger.info('[payment_receive] decrementing customer balance.');
|
||||
const customerIncrementOper = Customer.decrementBalance(
|
||||
paymentReceive.customer_id,
|
||||
paymentAmount,
|
||||
|
||||
@@ -16,6 +16,9 @@ export default class SaleEstimateService {
|
||||
@Inject()
|
||||
itemsEntriesService: HasItemsEntries;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Creates a new estimate with associated entries.
|
||||
* @async
|
||||
@@ -31,12 +34,15 @@ export default class SaleEstimateService {
|
||||
amount,
|
||||
...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']),
|
||||
};
|
||||
|
||||
this.logger.info('[sale_estimate] inserting sale estimate to the storage.');
|
||||
const storedEstimate = await SaleEstimate.query()
|
||||
.insert({
|
||||
...omit(estimate, ['entries']),
|
||||
});
|
||||
const storeEstimateEntriesOpers: any[] = [];
|
||||
|
||||
this.logger.info('[sale_estimate] inserting sale estimate entries to the storage.');
|
||||
estimate.entries.forEach((entry: any) => {
|
||||
const oper = ItemEntry.query()
|
||||
.insert({
|
||||
@@ -48,6 +54,8 @@ export default class SaleEstimateService {
|
||||
});
|
||||
await Promise.all([...storeEstimateEntriesOpers]);
|
||||
|
||||
this.logger.info('[sale_estimate] insert sale estimated success.');
|
||||
|
||||
return storedEstimate;
|
||||
}
|
||||
|
||||
@@ -67,6 +75,7 @@ export default class SaleEstimateService {
|
||||
amount,
|
||||
...formatDateFields(estimateDTO, ['estimate_date', 'expiration_date']),
|
||||
};
|
||||
this.logger.info('[sale_estimate] editing sale estimate on the storage.');
|
||||
const updatedEstimate = await SaleEstimate.query()
|
||||
.update({
|
||||
...omit(estimate, ['entries']),
|
||||
@@ -96,14 +105,14 @@ export default class SaleEstimateService {
|
||||
*/
|
||||
async deleteEstimate(tenantId: number, estimateId: number) {
|
||||
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[sale_estimate] delete sale estimate and associated entries from the storage.');
|
||||
await ItemEntry.query()
|
||||
.where('reference_id', estimateId)
|
||||
.where('reference_type', 'SaleEstimate')
|
||||
.delete();
|
||||
|
||||
await SaleEstimate.query()
|
||||
.where('id', estimateId)
|
||||
.delete();
|
||||
await SaleEstimate.query().where('id', estimateId).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,10 +122,10 @@ export default class SaleEstimateService {
|
||||
* @param {Numeric} estimateId
|
||||
* @return {Boolean}
|
||||
*/
|
||||
async isEstimateExists(estimateId: number) {
|
||||
async isEstimateExists(tenantId: number, estimateId: number) {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
const foundEstimate = await SaleEstimate.query()
|
||||
.where('id', estimateId);
|
||||
const foundEstimate = await SaleEstimate.query().where('id', estimateId);
|
||||
|
||||
return foundEstimate.length !== 0;
|
||||
}
|
||||
|
||||
@@ -192,7 +201,6 @@ export default class SaleEstimateService {
|
||||
const foundEstimates = await SaleEstimate.query()
|
||||
.onBuild((query: any) => {
|
||||
query.where('estimate_number', estimateNumber);
|
||||
|
||||
if (excludeEstimateId) {
|
||||
query.whereNot('id', excludeEstimateId);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
@Inject()
|
||||
itemsEntriesService: HasItemsEntries;
|
||||
|
||||
@Inject('logger')
|
||||
logger: any;
|
||||
|
||||
/**
|
||||
* Creates a new sale invoices and store it to the storage
|
||||
* with associated to entries and journal transactions.
|
||||
@@ -43,12 +46,15 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
paymentAmount: 0,
|
||||
invLotNumber,
|
||||
};
|
||||
|
||||
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
||||
const storedInvoice = await SaleInvoice.query()
|
||||
.insert({
|
||||
...omit(saleInvoice, ['entries']),
|
||||
});
|
||||
const opers: Array<any> = [];
|
||||
|
||||
this.logger.info('[sale_invoice] inserting sale invoice entries to the storage.');
|
||||
saleInvoice.entries.forEach((entry: any) => {
|
||||
const oper = ItemEntry.query()
|
||||
.insertAndFetch({
|
||||
@@ -61,15 +67,16 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
opers.push(oper);
|
||||
});
|
||||
|
||||
this.logger.info('[sale_invoice] trying to increment the customer balance.');
|
||||
// Increment the customer balance after deliver the sale invoice.
|
||||
const incrementOper = Customer.incrementBalance(
|
||||
saleInvoice.customer_id,
|
||||
balance,
|
||||
);
|
||||
|
||||
// Await all async operations.
|
||||
await Promise.all([
|
||||
...opers, incrementOper,
|
||||
]);
|
||||
await Promise.all([ ...opers, incrementOper ]);
|
||||
|
||||
// Records the inventory transactions for inventory items.
|
||||
await this.recordInventoryTranscactions(tenantId, saleInvoice, storedInvoice.id);
|
||||
|
||||
@@ -100,6 +107,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
balance,
|
||||
invLotNumber: oldSaleInvoice.invLotNumber,
|
||||
};
|
||||
|
||||
this.logger.info('[sale_invoice] trying to update sale invoice.');
|
||||
const updatedSaleInvoices: ISaleInvoice = await SaleInvoice.query()
|
||||
.where('id', saleInvoiceId)
|
||||
.update({
|
||||
@@ -114,6 +123,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
const patchItemsEntriesOper = this.itemsEntriesService.patchItemsEntries(
|
||||
tenantId, saleInvoice.entries, storedEntries, 'SaleInvoice', saleInvoiceId,
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] change customer different balance.');
|
||||
// Changes the diff customer balance between old and new amount.
|
||||
const changeCustomerBalanceOper = Customer.changeDiffBalance(
|
||||
saleInvoice.customer_id,
|
||||
@@ -155,12 +166,14 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
.findById(saleInvoiceId)
|
||||
.withGraphFetched('entries');
|
||||
|
||||
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
||||
await SaleInvoice.query().where('id', saleInvoiceId).delete();
|
||||
await ItemEntry.query()
|
||||
.where('reference_id', saleInvoiceId)
|
||||
.where('reference_type', 'SaleInvoice')
|
||||
.delete();
|
||||
|
||||
this.logger.info('[sale_invoice] revert the customer balance.');
|
||||
const revertCustomerBalanceOper = Customer.changeBalance(
|
||||
oldSaleInvoice.customerId,
|
||||
oldSaleInvoice.balance * -1,
|
||||
@@ -203,7 +216,13 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
* @param {number} saleInvoiceId -
|
||||
* @param {boolean} override -
|
||||
*/
|
||||
recordInventoryTranscactions(tenantId: number, saleInvoice, saleInvoiceId: number, override?: boolean){
|
||||
recordInventoryTranscactions(
|
||||
tenantId: number,
|
||||
saleInvoice,
|
||||
saleInvoiceId: number,
|
||||
override?: boolean
|
||||
){
|
||||
this.logger.info('[sale_invoice] saving inventory transactions');
|
||||
const inventortyTransactions = saleInvoice.entries
|
||||
.map((entry) => ({
|
||||
...pick(entry, ['item_id', 'quantity', 'rate',]),
|
||||
@@ -228,6 +247,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
async revertInventoryTransactions(tenantId: number, inventoryTransactions: array) {
|
||||
const { InventoryTransaction } = this.tenancy.models(tenantId);
|
||||
const opers: Promise<[]>[] = [];
|
||||
|
||||
this.logger.info('[sale_invoice] reverting inventory transactions');
|
||||
|
||||
inventoryTransactions.forEach((trans: any) => {
|
||||
switch(trans.direction) {
|
||||
@@ -359,7 +380,11 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
* Writes the sale invoice journal entries.
|
||||
* @param {SaleInvoice} saleInvoice -
|
||||
*/
|
||||
async writeNonInventoryInvoiceJournals(tenantId: number, saleInvoice: ISaleInvoice, override: boolean) {
|
||||
async writeNonInventoryInvoiceJournals(
|
||||
tenantId: number,
|
||||
saleInvoice: ISaleInvoice,
|
||||
override: boolean
|
||||
) {
|
||||
const { Account, AccountTransaction } = this.tenancy.models(tenantId);
|
||||
|
||||
const accountsDepGraph = await Account.depGraph().query();
|
||||
|
||||
15
server/src/services/Settings/SettingsStore.ts
Normal file
15
server/src/services/Settings/SettingsStore.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import Knex from 'knex';
|
||||
import MetableStoreDB from '@/lib/Metable/MetableStoreDB';
|
||||
import Setting from '@/models/Setting';
|
||||
|
||||
export default class SettingsStore extends MetableStoreDB {
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId
|
||||
*/
|
||||
constructor(knex: Knex) {
|
||||
super();
|
||||
this.setExtraColumns(['group']);
|
||||
this.setModel(Setting.bindKnex(knex));
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ export default class HasTenancyService {
|
||||
* @param {number} tenantId - The tenant id.
|
||||
*/
|
||||
models(tenantId: number) {
|
||||
console.log(tenantId);
|
||||
return this.tenantContainer(tenantId).get('models');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user