add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
import { Service, Inject } from 'typedi';
import moment from 'moment';
import events from '@/subscribers/events';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import SaleNotifyBySms from '../SaleNotifyBySms';
import SmsNotificationsSettingsService from '@/services/Settings/SmsNotificationsSettings';
import SMSClient from '@/services/SMSClient';
import {
ICustomer,
IPaymentReceiveSmsDetails,
ISaleEstimate,
SMS_NOTIFICATION_KEY,
} from '@/interfaces';
import { Tenant, TenantMetadata } from '@/system/models';
import { formatNumber, formatSmsMessage } from 'utils';
import { ServiceError } from '@/exceptions';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
const ERRORS = {
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
};
@Service()
export default class SaleEstimateNotifyBySms {
@Inject()
tenancy: HasTenancyService;
@Inject()
saleSmsNotification: SaleNotifyBySms;
@Inject()
eventPublisher: EventPublisher;
@Inject()
smsNotificationsSettings: SmsNotificationsSettingsService;
/**
*
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<ISaleEstimate>}
*/
public notifyBySms = async (
tenantId: number,
saleEstimateId: number
): Promise<ISaleEstimate> => {
const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve the sale invoice or throw not found service error.
const saleEstimate = await SaleEstimate.query()
.findById(saleEstimateId)
.withGraphFetched('customer');
// Validates the estimate transaction existance.
this.validateEstimateExistance(saleEstimate);
// Validate the customer phone number existance and number validation.
this.saleSmsNotification.validateCustomerPhoneNumber(
saleEstimate.customer.personalPhone
);
// Triggers `onSaleEstimateNotifySms` event.
await this.eventPublisher.emitAsync(events.saleEstimate.onNotifySms, {
tenantId,
saleEstimate,
});
await this.sendSmsNotification(tenantId, saleEstimate);
// Triggers `onSaleEstimateNotifySms` event.
await this.eventPublisher.emitAsync(events.saleEstimate.onNotifiedSms, {
tenantId,
saleEstimate,
});
return saleEstimate;
};
/**
*
* @param {number} tenantId
* @param {ISaleEstimate} saleEstimate
* @returns
*/
private sendSmsNotification = async (
tenantId: number,
saleEstimate: ISaleEstimate & { customer: ICustomer }
) => {
const smsClient = this.tenancy.smsClient(tenantId);
const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
// Retrieve the formatted sms notification message for estimate details.
const formattedSmsMessage = this.formattedEstimateDetailsMessage(
tenantId,
saleEstimate,
tenantMetadata
);
const phoneNumber = saleEstimate.customer.personalPhone;
// Runs the send message job.
return smsClient.sendMessageJob(phoneNumber, formattedSmsMessage);
};
/**
* Notify via SMS message after estimate creation.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<void>}
*/
public notifyViaSmsNotificationAfterCreation = async (
tenantId: number,
saleEstimateId: number
): Promise<void> => {
const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
tenantId,
SMS_NOTIFICATION_KEY.SALE_ESTIMATE_DETAILS
);
// Can't continue if the sms auto-notification is not enabled.
if (!notification.isNotificationEnabled) return;
await this.notifyBySms(tenantId, saleEstimateId);
};
/**
*
* @param {number} tenantId
* @param {ISaleEstimate} saleEstimate
* @param {TenantMetadata} tenantMetadata
* @returns {string}
*/
private formattedEstimateDetailsMessage = (
tenantId: number,
saleEstimate: ISaleEstimate,
tenantMetadata: TenantMetadata
): string => {
const notification = this.smsNotificationsSettings.getSmsNotificationMeta(
tenantId,
SMS_NOTIFICATION_KEY.SALE_ESTIMATE_DETAILS
);
return this.formateEstimateDetailsMessage(
notification.smsMessage,
saleEstimate,
tenantMetadata
);
};
/**
* Formattes the estimate sms notification details message.
* @param {string} smsMessage
* @param {ISaleEstimate} saleEstimate
* @param {TenantMetadata} tenantMetadata
* @returns {string}
*/
private formateEstimateDetailsMessage = (
smsMessage: string,
saleEstimate: ISaleEstimate & { customer: ICustomer },
tenantMetadata: TenantMetadata
) => {
const formattedAmount = formatNumber(saleEstimate.amount, {
currencyCode: saleEstimate.currencyCode,
});
return formatSmsMessage(smsMessage, {
EstimateNumber: saleEstimate.estimateNumber,
ReferenceNumber: saleEstimate.reference,
EstimateDate: moment(saleEstimate.estimateDate).format('YYYY/MM/DD'),
ExpirationDate: saleEstimate.expirationDate
? moment(saleEstimate.expirationDate).format('YYYY/MM/DD')
: '',
CustomerName: saleEstimate.customer.displayName,
Amount: formattedAmount,
CompanyName: tenantMetadata.name,
});
};
/**
* Retrieve the SMS details of the given payment receive transaction.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<IPaymentReceiveSmsDetails>}
*/
public smsDetails = async (
tenantId: number,
saleEstimateId: number
): Promise<IPaymentReceiveSmsDetails> => {
const { SaleEstimate } = this.tenancy.models(tenantId);
// Retrieve the sale invoice or throw not found service error.
const saleEstimate = await SaleEstimate.query()
.findById(saleEstimateId)
.withGraphFetched('customer');
this.validateEstimateExistance(saleEstimate);
// Retrieve the current tenant metadata.
const tenantMetadata = await TenantMetadata.query().findOne({ tenantId });
// Retrieve the formatted sms message from the given estimate model.
const formattedSmsMessage = this.formattedEstimateDetailsMessage(
tenantId,
saleEstimate,
tenantMetadata
);
return {
customerName: saleEstimate.customer.displayName,
customerPhoneNumber: saleEstimate.customer.personalPhone,
smsMessage: formattedSmsMessage,
};
};
/**
* Validates the sale estimate existance.
* @param {ISaleEstimate} saleEstimate -
*/
private validateEstimateExistance(saleEstimate: ISaleEstimate) {
if (!saleEstimate) {
throw new ServiceError(ERRORS.SALE_ESTIMATE_NOT_FOUND);
}
}
}

View File

@@ -0,0 +1,77 @@
import { Service } from 'typedi';
import { ISaleEstimate } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export default class SaleEstimateTransfromer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'formattedEstimateDate',
'formattedExpirationDate',
'formattedDeliveredAtDate',
'formattedApprovedAtDate',
'formattedRejectedAtDate',
];
};
/**
* Retrieve formatted estimate date.
* @param {ISaleEstimate} invoice
* @returns {String}
*/
protected formattedEstimateDate = (estimate: ISaleEstimate): string => {
return this.formatDate(estimate.estimateDate);
};
/**
* Retrieve formatted estimate date.
* @param {ISaleEstimate} invoice
* @returns {String}
*/
protected formattedExpirationDate = (estimate: ISaleEstimate): string => {
return this.formatDate(estimate.expirationDate);
};
/**
* Retrieve formatted estimate date.
* @param {ISaleEstimate} invoice
* @returns {String}
*/
protected formattedDeliveredAtDate = (estimate: ISaleEstimate): string => {
return this.formatDate(estimate.deliveredAt);
};
/**
* Retrieve formatted estimate date.
* @param {ISaleEstimate} invoice
* @returns {String}
*/
protected formattedApprovedAtDate = (estimate: ISaleEstimate): string => {
return this.formatDate(estimate.approvedAt);
};
/**
* Retrieve formatted estimate date.
* @param {ISaleEstimate} invoice
* @returns {String}
*/
protected formattedRejectedAtDate = (estimate: ISaleEstimate): string => {
return this.formatDate(estimate.rejectedAt);
};
/**
* Retrieve formatted invoice amount.
* @param {ISaleEstimate} estimate
* @returns {string}
*/
protected formattedAmount = (estimate: ISaleEstimate): string => {
return formatNumber(estimate.amount, {
currencyCode: estimate.currencyCode,
});
};
}

View File

@@ -0,0 +1,36 @@
import { Inject, Service } from 'typedi';
import PdfService from '@/services/PDF/PdfService';
import { templateRender } from 'utils';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Tenant } from '@/system/models';
@Service()
export default class SaleEstimatesPdf {
@Inject()
pdfService: PdfService;
@Inject()
tenancy: HasTenancyService;
/**
* Retrieve sale invoice pdf content.
* @param {} saleInvoice -
*/
async saleEstimatePdf(tenantId: number, saleEstimate) {
const i18n = this.tenancy.i18n(tenantId);
const organization = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
const htmlContent = templateRender('modules/estimate-regular', {
saleEstimate,
organizationName: organization.metadata.name,
organizationEmail: organization.metadata.email,
...i18n,
});
const pdfContent = await this.pdfService.pdfDocument(htmlContent);
return pdfContent;
}
}

View File

@@ -0,0 +1,109 @@
export const ERRORS = {
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
SALE_ESTIMATE_NUMBER_EXISTANCE: 'SALE_ESTIMATE_NUMBER_EXISTANCE',
SALE_ESTIMATE_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_CONVERTED_TO_INVOICE',
SALE_ESTIMATE_NOT_DELIVERED: 'SALE_ESTIMATE_NOT_DELIVERED',
SALE_ESTIMATE_ALREADY_REJECTED: 'SALE_ESTIMATE_ALREADY_REJECTED',
CUSTOMER_HAS_SALES_ESTIMATES: 'CUSTOMER_HAS_SALES_ESTIMATES',
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
SALE_ESTIMATE_ALREADY_DELIVERED: 'SALE_ESTIMATE_ALREADY_DELIVERED',
SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED'
};
export const DEFAULT_VIEW_COLUMNS = [];
export const DEFAULT_VIEWS = [
{
name: 'Draft',
slug: 'draft',
rolesLogicExpression: '1',
roles: [
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' },
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Delivered',
slug: 'delivered',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'delivered',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Approved',
slug: 'approved',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'approved',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Rejected',
slug: 'rejected',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'rejected',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Invoiced',
slug: 'invoiced',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'invoiced',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Expired',
slug: 'expired',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'expired',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'Closed',
slug: 'closed',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'closed',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
];