diff --git a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts index 63a854e22..baad0cace 100644 --- a/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts +++ b/packages/server/src/api/controllers/PdfTemplates/PdfTemplatesController.ts @@ -21,7 +21,6 @@ export class PdfTemplatesController extends BaseController { this.validationResult, this.deletePdfTemplate.bind(this) ); - router.put( '/:template_id', [ @@ -54,6 +53,12 @@ export class PdfTemplatesController extends BaseController { this.validationResult, this.createPdfInvoiceTemplate.bind(this) ); + router.post( + '/:template_id/assign_default', + [param('template_id').exists().isInt().toInt()], + this.validationResult, + this.assginPdfTemplateAsDefault.bind(this) + ); return router; } @@ -139,4 +144,29 @@ export class PdfTemplatesController extends BaseController { next(error); } } + + async assginPdfTemplateAsDefault( + req: Request, + res: Response, + next: NextFunction + ) { + const { tenantId } = req; + const { template_id: templateId } = req.params; + + try { + await this.pdfTemplateApplication.assignPdfTemplateAsDefault( + tenantId, + Number(templateId) + ); + return res + .status(204) + .send({ + id: templateId, + message: + 'The given pdf template has been assigned as default template', + }); + } catch (error) { + next(error); + } + } } diff --git a/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts new file mode 100644 index 000000000..f4317aec1 --- /dev/null +++ b/packages/server/src/services/PdfTemplate/AssignPdfTemplateDefault.ts @@ -0,0 +1,47 @@ +import { Service, Inject } from 'typedi'; +import HasTenancyService from '../Tenancy/TenancyService'; +import UnitOfWork from '../UnitOfWork'; +import { Knex } from 'knex'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; + +@Service() +export class AssignPdfTemplateDefault { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uow: UnitOfWork; + + @Inject() + private eventPublisher: EventPublisher; + + public async assignDefaultTemplate(tenantId: number, templateId: number) { + const { PdfTemplate } = this.tenancy.models(tenantId); + + const oldPdfTempalte = await PdfTemplate.query() + .findById(templateId) + .throwIfNotFound(); + + return this.uow.withTransaction( + tenantId, + async (trx?: Knex.Transaction) => { + await PdfTemplate.query(trx) + .where('resource', oldPdfTempalte.resource) + .patch({ default: false }); + + await PdfTemplate.query(trx) + .findById(templateId) + .patch({ default: true }); + + await this.eventPublisher.emitAsync( + events.pdfTemplate.onAssignedDefault, + { + tenantId, + templateId, + } + ); + } + ); + } +} diff --git a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts index 2007f9a52..30a08266e 100644 --- a/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts +++ b/packages/server/src/services/PdfTemplate/PdfTemplateApplication.ts @@ -5,6 +5,7 @@ import { DeletePdfTemplate } from './DeletePdfTemplate'; import { GetPdfTemplate } from './GetPdfTemplate'; import { GetPdfTemplates } from './GetPdfTemplates'; import { EditPdfTemplate } from './EditPdfTemplate'; +import { AssignPdfTemplateDefault } from './AssignPdfTemplateDefault'; @Service() export class PdfTemplateApplication { @@ -23,6 +24,9 @@ export class PdfTemplateApplication { @Inject() private editPdfTemplateService: EditPdfTemplate; + @Inject() + private assignPdfTemplateDefaultService: AssignPdfTemplateDefault; + public async createPdfTemplate( tenantId: number, templateName: string, @@ -66,4 +70,14 @@ export class PdfTemplateApplication { ) { return this.getPdfTemplatesService.getPdfTemplates(tenantId, query); } + + public async assignPdfTemplateAsDefault( + tenantId: number, + templateId: number + ) { + return this.assignPdfTemplateDefaultService.assignDefaultTemplate( + tenantId, + templateId + ); + } } diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index ba2cf7103..9e7d71be7 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -695,6 +695,9 @@ export default { onDeleting: 'onPdfTemplateDeleting', onDeleted: 'onPdfTemplateDeleted', + onAssignedDefault: 'onPdfTemplateAssignedDefault', + onAssigningDefault: 'onPdfTemplateAssigningDefault', + onInvoiceCreated: 'onInvoicePdfTemplateCreated', }, }; diff --git a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx index 01f358c65..aeba730dc 100644 --- a/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx +++ b/packages/webapp/src/containers/BrandingTemplates/BrandingTemplatesTable.tsx @@ -42,6 +42,11 @@ function BrandingTemplateTableRoot({ openDrawer(drawerName, { templateId, resource }); }; + // Handle mark as default button click. + const handleMarkDefaultTemplate = (template) => { + openAlert('branding-template-mark-default', { templateId: template.id }); + }; + return ( + {!original.default && ( + <> + + + + )} import('./DeleteBrandingTemplateAlert'), ); +const MarkDefaultBrandingTemplateAlert = React.lazy( + () => import('./MarkDefaultBrandingTemplateAlert'), +); + export const BrandingTemplatesAlerts = [ { name: 'branding-template-delete', component: DeleteBrandingTemplateAlert }, + { + name: 'branding-template-mark-default', + component: MarkDefaultBrandingTemplateAlert, + }, ]; diff --git a/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx b/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx new file mode 100644 index 000000000..41b9c8244 --- /dev/null +++ b/packages/webapp/src/containers/BrandingTemplates/alerts/MarkDefaultBrandingTemplateAlert.tsx @@ -0,0 +1,73 @@ +// @ts-nocheck +import React from 'react'; +import intl from 'react-intl-universal'; +import { AppToaster } from '@/components'; +import { Alert, Intent } from '@blueprintjs/core'; +import { useAssignPdfTemplateAsDefault } from '@/hooks/query/pdf-templates'; + +import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; +import withAlertActions from '@/containers/Alert/withAlertActions'; + +import { compose } from '@/utils'; + +/** + * Mark default branding template alert. + */ +function MarkDefaultBrandingTemplateAlert({ + // #ownProps + name, + + // #withAlertStoreConnect + isOpen, + payload: { templateId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: assignPdfTemplateAsDefault } = + useAssignPdfTemplateAsDefault(); + + const handleConfirmDelete = () => { + assignPdfTemplateAsDefault({ templateId }) + .then(() => { + AppToaster.show({ + message: + 'The branding template has been marked as default successfully.', + intent: Intent.SUCCESS, + }); + closeAlert(name); + }) + .catch((error) => { + AppToaster.show({ + message: 'Something went wrong.', + intent: Intent.DANGER, + }); + closeAlert(name); + }); + }; + + const handleCancel = () => { + closeAlert(name); + }; + + return ( + +

+ Are you sure want to mark the given branding template as default + template? +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(MarkDefaultBrandingTemplateAlert); diff --git a/packages/webapp/src/hooks/query/pdf-templates.ts b/packages/webapp/src/hooks/query/pdf-templates.ts index eee064585..adba19753 100644 --- a/packages/webapp/src/hooks/query/pdf-templates.ts +++ b/packages/webapp/src/hooks/query/pdf-templates.ts @@ -166,3 +166,40 @@ export const useGetPdfTemplates = ( options, ); }; + +export interface AssignPdfTemplateAsDefaultValues { + templateId: number; +} + +export interface AssignPdfTemplateAsDefaultResponse {} + +export const useAssignPdfTemplateAsDefault = ( + options?: UseMutationOptions< + AssignPdfTemplateAsDefaultResponse, + Error, + AssignPdfTemplateAsDefaultValues + >, +): UseMutationResult< + AssignPdfTemplateAsDefaultResponse, + Error, + AssignPdfTemplateAsDefaultValues +> => { + const apiRequest = useApiRequest(); + const queryClient = useQueryClient(); + return useMutation< + AssignPdfTemplateAsDefaultResponse, + Error, + AssignPdfTemplateAsDefaultValues + >( + ({ templateId }) => + apiRequest + .post(`/pdf-templates/${templateId}/assign_default`) + .then((res) => res.data), + { + onSuccess: () => { + queryClient.invalidateQueries([PdfTemplatesQueryKey]); + }, + ...options, + }, + ); +};