Skip to content

Commit

Permalink
feat: wip send estimate mail preview
Browse files Browse the repository at this point in the history
  • Loading branch information
abouolia committed Nov 18, 2024
1 parent 53ab40a commit 7df316a
Show file tree
Hide file tree
Showing 34 changed files with 405 additions and 459 deletions.
14 changes: 7 additions & 7 deletions packages/server/src/api/controllers/Sales/SalesEstimates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ export default class SalesEstimatesController extends BaseController {
this.handleServiceErrors
);
router.get(
'/:id/mail',
'/:id/mail/state',
[...this.validateSpecificEstimateSchema],
this.validationResult,
asyncMiddleware(this.getSaleEstimateMail.bind(this)),
asyncMiddleware(this.getSaleEstimateMailState.bind(this)),
this.handleServiceErrors
);
return router;
Expand Down Expand Up @@ -540,18 +540,18 @@ export default class SalesEstimatesController extends BaseController {
* @param {Response} res
* @param {NextFunction} next
*/
private getSaleEstimateMail = async (
req: Request,
private getSaleEstimateMailState = async (
req: Request<{ id: number }>,
res: Response,
next: NextFunction
) => {
const { tenantId } = req;
const { id: invoiceId } = req.params;
const { id: estimateId } = req.params;

try {
const data = await this.saleEstimatesApplication.getSaleEstimateMail(
const data = await this.saleEstimatesApplication.getSaleEstimateMailState(
tenantId,
invoiceId
estimateId
);
return res.status(200).send({ data });
} catch (error) {
Expand Down
13 changes: 13 additions & 0 deletions packages/server/src/models/SaleEstimate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export default class SaleEstimate extends mixin(TenantModel, [
const Branch = require('models/Branch');
const Warehouse = require('models/Warehouse');
const Document = require('models/Document');
const { PdfTemplate } = require('models/PdfTemplate');

return {
customer: {
Expand Down Expand Up @@ -252,6 +253,18 @@ export default class SaleEstimate extends mixin(TenantModel, [
query.where('model_ref', 'SaleEstimate');
},
},

/**
* Sale estimate may belongs to pdf branding template.
*/
pdfTemplate: {
relation: Model.BelongsToOneRelation,
modelClass: PdfTemplate,
join: {
from: 'sales_estimates.pdfTemplateId',
to: 'pdf_templates.id',
},
},
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Inject } from 'typedi';
import { SendSaleEstimateMail } from './SendSaleEstimateMail';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetSaleEstimateMailStateTransformer } from './GetSaleEstimateMailStateTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';

export class GetSaleEstimateMailState {
@Inject()
private estimateMail: SendSaleEstimateMail;

@Inject()
private tenancy: HasTenancyService;

@Inject()
private transformer: TransformerInjectable;

/**
* Retrieves the estimate mail state of the given sale estimate.
* Estimate mail state includes the mail options, branding attributes and the estimate details.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<SaleEstimateMailState>}
*/
async getEstimateMailState(
tenantId: number,
saleEstimateId: number
): Promise<SaleEstimateMailState> {
const { SaleEstimate } = this.tenancy.models(tenantId);

const saleEstimate = await SaleEstimate.query()
.findById(saleEstimateId)
.withGraphFetched('customer')
.withGraphFetched('entries.item')
.withGraphFetched('pdfTemplate')
.throwIfNotFound();

const mailOptions = await this.estimateMail.getMailOptions(
tenantId,
saleEstimateId
);
const transformed = await this.transformer.transform(
tenantId,
saleEstimate,
new GetSaleEstimateMailStateTransformer(),
{
mailOptions,
}
);
return transformed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ItemEntryTransformer } from '../Invoices/ItemEntryTransformer';
import { SaleEstimateTransfromer } from './SaleEstimateTransformer';

export class GetSaleEstimateMailStateTransformer extends SaleEstimateTransfromer {
public excludeAttributes = (): string[] => {
return ['*'];
};

public includeAttributes = (): string[] => {
return [
'estimateDate',
'formattedEstimateDate',

'total',
'totalFormatted',

'subtotal',
'subtotalFormatted',

'estimateNo',

'entries',

'companyName',
'companyLogoUri',

'primaryColor',
'customerName',
];
};

/**
* Retrieves the customer name of the invoice.
* @returns {string}
*/
protected customerName = (invoice) => {
return invoice.customer.displayName;
};

/**
* Retrieves the company name.
* @returns {string}
*/
protected companyName = () => {
return this.context.organization.name;
};

/**
* Retrieves the company logo uri.
* @returns {string | null}
*/
protected companyLogoUri = (invoice) => {
return invoice.pdfTemplate?.companyLogoUri;
};

/**
* Retrieves the primary color.
* @returns {string}
*/
protected primaryColor = (invoice) => {
return invoice.pdfTemplate?.attributes?.primaryColor;
};

/**
*
* @param invoice
* @returns
*/
protected entries = (invoice) => {
return this.item(
invoice.entries,
new GetSaleEstimateMailStateEntryTransformer(),
{
currencyCode: invoice.currencyCode,
}
);
};

/**
* Merges the mail options with the invoice object.
*/
public transform = (object: any) => {
return {
...this.options.mailOptions,
...object,
};
};
}

class GetSaleEstimateMailStateEntryTransformer extends ItemEntryTransformer {
public excludeAttributes = (): string[] => {
return ['*'];
};

public includeAttributes = (): string[] => {
return [
'description',
'quantity',
'unitPrice',
'unitPriceFormatted',
'total',
'totalFormatted',
];
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SaleEstimateNotifyBySms } from './SaleEstimateSmsNotify';
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
import { SendSaleEstimateMail } from './SendSaleEstimateMail';
import { GetSaleEstimateState } from './GetSaleEstimateState';
import { GetSaleEstimateMailState } from './GetSaleEstimateMailState';

@Service()
export class SaleEstimatesApplication {
Expand Down Expand Up @@ -57,6 +58,9 @@ export class SaleEstimatesApplication {
@Inject()
private sendEstimateMailService: SendSaleEstimateMail;

@Inject()
private getSaleEstimateMailStateService: GetSaleEstimateMailState;

@Inject()
private getSaleEstimateStateService: GetSaleEstimateState;

Expand Down Expand Up @@ -250,6 +254,22 @@ export class SaleEstimatesApplication {
);
}

/**
* Retrieves the sale estimate mail state.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<SaleEstimateMailOptions>}
*/
public getSaleEstimateMailState(
tenantId: number,
saleEstimateId: number
): Promise<SaleEstimateMailOptions> {
return this.getSaleEstimateMailStateService.getEstimateMailState(
tenantId,
saleEstimateId
);
}

/**
* Retrieves the default mail options of the given sale estimate.
* @param {number} tenantId
Expand Down
2 changes: 0 additions & 2 deletions packages/webapp/src/components/DialogsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/P
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
import { DialogsName } from '@/constants/dialogs';
import InvoiceExchangeRateChangeDialog from '@/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog';
import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog';
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
import PaymentMailDialog from '@/containers/Sales/PaymentsReceived/PaymentMailDialog/PaymentMailDialog';
import { ExportDialog } from '@/containers/Dialogs/ExportDialog';
Expand Down Expand Up @@ -143,7 +142,6 @@ export default function DialogsContainer() {
<InvoiceExchangeRateChangeDialog
dialogName={DialogsName.InvoiceExchangeRateChangeNotice}
/>
<EstimateMailDialog dialogName={DialogsName.EstimateMail} />
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
<PaymentMailDialog dialogName={DialogsName.PaymentMail} />
<ExportDialog dialogName={DialogsName.Export} />
Expand Down
2 changes: 2 additions & 0 deletions packages/webapp/src/components/DrawersContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { BrandingTemplatesDrawer } from '@/containers/BrandingTemplates/Branding

import { DRAWERS } from '@/constants/drawers';
import { InvoiceSendMailDrawer } from '@/containers/Sales/Invoices/InvoiceSendMailDrawer/InvoiceSendMailDrawer';
import { EstimateSendMailDrawer } from '@/containers/Sales/Estimates/EstimateSendMailDrawer';

/**
* Drawers container of the dashboard.
Expand Down Expand Up @@ -81,6 +82,7 @@ export default function DrawersContainer() {
/>
<BrandingTemplatesDrawer name={DRAWERS.BRANDING_TEMPLATES} />
<InvoiceSendMailDrawer name={DRAWERS.INVOICE_SEND_MAIL} />
<EstimateSendMailDrawer name={DRAWERS.ESTIMATE_SEND_MAIL} />
</div>
);
}
3 changes: 2 additions & 1 deletion packages/webapp/src/constants/drawers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ export enum DRAWERS {
BRANDING_TEMPLATES = 'BRANDING_TEMPLATES',
PAYMENT_INVOICE_PREVIEW = 'PAYMENT_INVOICE_PREVIEW',
STRIPE_PAYMENT_INTEGRATION_EDIT = 'STRIPE_PAYMENT_INTEGRATION_EDIT',
INVOICE_SEND_MAIL = 'INVOICE_SEND_MAIL'
INVOICE_SEND_MAIL = 'INVOICE_SEND_MAIL',
ESTIMATE_SEND_MAIL = 'ESTIMATE_SEND_MAIL',
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 7df316a

Please sign in to comment.