feat: link pdf template to sales transactions

This commit is contained in:
Ahmed Bouhuolia
2024-09-11 16:49:44 +02:00
parent c0769662bd
commit ef74e250f1
12 changed files with 294 additions and 22 deletions

View File

@@ -236,6 +236,9 @@ export default class PaymentReceivesController extends BaseController {
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}

View File

@@ -167,6 +167,9 @@ export default class PaymentReceivesController extends BaseController {
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}

View File

@@ -168,9 +168,7 @@ export default class SalesEstimatesController extends BaseController {
check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.quantity').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.discount')
.optional({ nullable: true })
.isNumeric()
@@ -186,6 +184,9 @@ export default class SalesEstimatesController extends BaseController {
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}

View File

@@ -224,9 +224,7 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
check('entries.*.description')
.optional({ nullable: true })
.trim(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.tax_code')
.optional({ nullable: true })
.trim()
@@ -257,6 +255,9 @@ export default class SaleInvoicesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toFloat(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}

View File

@@ -148,17 +148,20 @@ export default class SalesReceiptsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('entries.*.description')
.optional({ nullable: true })
.trim(),
check('entries.*.description').optional({ nullable: true }).trim(),
check('entries.*.warehouse_id')
.optional({ nullable: true })
.isNumeric()
.toInt(),
check('receipt_message').optional().trim(),
check('statement').optional().trim(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
// Pdf template id.
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
];
}

View File

@@ -3,13 +3,49 @@
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema.createTable('pdf_templates', (table) => {
table.increments('id').primary();
table.text('resource');
table.text('template_name');
table.json('attributes');
table.timestamps();
});
return knex.schema
.createTable('pdf_templates', (table) => {
table.increments('id').primary();
table.text('resource');
table.text('template_name');
table.json('attributes');
table.timestamps();
})
.table('sales_invoices', (table) => {
table
.integer('pdf_template_id')
.unsigned()
.references('id')
.inTable('pdf_templates');
})
.table('sales_estimates', (table) => {
table
.integer('pdf_template_id')
.unsigned()
.references('id')
.inTable('pdf_templates');
})
.table('sales_receipts', (table) => {
table
.integer('pdf_template_id')
.unsigned()
.references('id')
.inTable('pdf_templates');
})
.table('credit_notes', (table) => {
table
.integer('pdf_template_id')
.unsigned()
.references('id')
.inTable('pdf_templates');
})
.table('payment_receives', (table) => {
table
.integer('pdf_template_id')
.unsigned()
.references('id')
.inTable('pdf_templates');
});
};
/**
@@ -17,5 +53,21 @@ exports.up = function (knex) {
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.dropTableIfExists('pdf_templates');
return knex.schema
.table('payment_receives', (table) => {
table.dropColumn('pdf_template_id');
})
.table('credit_notes', (table) => {
table.dropColumn('pdf_template_id');
})
.table('sales_receipts', (table) => {
table.dropColumn('pdf_template_id');
})
.table('sales_estimates', (table) => {
table.dropColumn('pdf_template_id');
})
.table('sales_invoices', (table) => {
table.dropColumn('pdf_template_id');
})
.dropTableIfExists('pdf_templates');
};

View File

@@ -31,13 +31,18 @@ export class CreatePdfTemplate {
const attributes = invoiceTemplateDTO;
return this.uow.withTransaction(tenantId, async (trx) => {
// Triggers `onPdfTemplateCreating` event.
await this.eventPublisher.emitAsync(events.pdfTemplate.onCreating, {
tenantId,
});
await PdfTemplate.query(trx).insert({
templateName,
resource,
attributes,
});
await this.eventPublisher.emitAsync(events.pdfTemplate.onInvoiceCreated, {
// Triggers `onPdfTemplateCreated` event.
await this.eventPublisher.emitAsync(events.pdfTemplate.onCreated, {
tenantId,
});
});

View File

@@ -24,8 +24,14 @@ export class DeletePdfTemplate {
const { PdfTemplate } = this.tenancy.models(tenantId);
return this.uow.withTransaction(tenantId, async (trx) => {
// Triggers `onPdfTemplateDeleting` event.
await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleting, {
tenantId,
templateId,
});
await PdfTemplate.query(trx).deleteById(templateId);
// Triggers `onPdfTemplateDeleted` event.
await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleted, {
tenantId,
templateId,

View File

@@ -692,6 +692,7 @@ export default {
onEditing: 'onPdfTemplateEditing',
onEdited: 'onPdfTemplatedEdited',
onDeleting: 'onPdfTemplateDeleting',
onDeleted: 'onPdfTemplateDeleted',
onInvoiceCreated: 'onInvoicePdfTemplateCreated',

View File

@@ -1,7 +1,7 @@
import React from 'react';
import * as R from 'ramda';
import { Box } from '@/components';
import { Classes } from '@blueprintjs/core';
import { AppToaster, Box } from '@/components';
import { Classes, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import {
InvoicePaperTemplate,
@@ -12,9 +12,51 @@ import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
import { InvoiceCustomizeValues } from './types';
import { initialValues } from './constants';
import {
useCreatePdfTemplate,
useEditPdfTemplate,
} from '@/hooks/query/pdf-templates';
import { transformToEditRequest, transformToNewRequest } from './utils';
export default function InvoiceCustomizeContent() {
const handleFormSubmit = (values: InvoiceCustomizeValues) => {};
const { mutateAsync: createPdfTemplate } = useCreatePdfTemplate();
const { mutateAsync: editPdfTemplate } = useEditPdfTemplate();
const templateId: number = 1;
const handleFormSubmit = (values: InvoiceCustomizeValues) => {
const handleSuccess = (message: string) => {
AppToaster.show({
intent: Intent.SUCCESS,
message,
});
};
const handleError = (message: string) => {
AppToaster.show({
intent: Intent.DANGER,
message,
});
};
if (templateId) {
const reqValues = transformToEditRequest(values, templateId);
// Edit existing template
editPdfTemplate({ ...reqValues })
.then(() => handleSuccess('PDF template updated successfully!'))
.catch(() =>
handleError('An error occurred while updating the PDF template.'),
);
} else {
const reqValues = transformToNewRequest(values);
// Create new template
createPdfTemplate(reqValues)
.then(() => handleSuccess('PDF template created successfully!'))
.catch(() =>
handleError('An error occurred while creating the PDF template.'),
);
}
};
return (
<Box className={Classes.DRAWER_BODY}>

View File

@@ -0,0 +1,24 @@
import { omit } from 'lodash';
import { InvoiceCustomizeValues } from './types';
import { CreatePdfTemplateValues, EditPdfTemplateValues } from '@/hooks/query/pdf-templates';
export const transformToEditRequest = (
values: InvoiceCustomizeValues,
templateId: number,
): EditPdfTemplateValues => {
return {
templateId,
templateName: 'Template Name',
attributes: omit(values, ['templateName']),
};
};
export const transformToNewRequest = (
values: InvoiceCustomizeValues,
): CreatePdfTemplateValues => {
return {
resource: 'SaleInvoice',
templateName: 'Template Name',
attributes: omit(values, ['templateName']),
};
};

View File

@@ -0,0 +1,131 @@
import {
useMutation,
useQuery,
UseMutationOptions,
UseQueryOptions,
UseMutationResult,
UseQueryResult,
} from 'react-query';
import useApiRequest from '../useRequest';
export interface CreatePdfTemplateValues {
templateName: string;
resource: string;
attributes: Record<string, any>;
}
export interface CreatePdfTemplateResponse {}
export interface EditPdfTemplateValues {
templateId: string | number;
templateName: string;
attributes: Record<string, any>;
}
export interface EditPdfTemplateResponse {}
export interface DeletePdfTemplateValues {
templateId: number | string;
}
export interface DeletePdfTemplateResponse {}
export interface GetPdfTemplateValues {}
export interface GetPdfTemplateResponse {}
export interface GetPdfTemplatesValues {}
export interface GetPdfTemplatesResponse {}
// Hook for creating a PDF template
export const useCreatePdfTemplate = (
options?: UseMutationOptions<
CreatePdfTemplateResponse,
Error,
CreatePdfTemplateValues
>,
): UseMutationResult<
CreatePdfTemplateResponse,
Error,
CreatePdfTemplateValues
> => {
const apiRequest = useApiRequest();
return useMutation<CreatePdfTemplateResponse, Error, CreatePdfTemplateValues>(
(values) =>
apiRequest.post('/pdf-templates', values).then((res) => res.data),
options,
);
};
// Hook for editing a PDF template
export const useEditPdfTemplate = (
options?: UseMutationOptions<
EditPdfTemplateResponse,
Error,
{ templateId: number; values: EditPdfTemplateValues }
>,
): UseMutationResult<
EditPdfTemplateResponse,
Error,
{ templateId: number; values: EditPdfTemplateValues }
> => {
const apiRequest = useApiRequest();
return useMutation<
EditPdfTemplateResponse,
Error,
{ templateId: number; values: EditPdfTemplateValues }
>(
({ templateId, values }) =>
apiRequest
.put(`/pdf-templates/${templateId}`, values)
.then((res) => res.data),
options,
);
};
// Hook for deleting a PDF template
export const useDeletePdfTemplate = (
options?: UseMutationOptions<
DeletePdfTemplateResponse,
Error,
{ templateId: number }
>,
): UseMutationResult<
DeletePdfTemplateResponse,
Error,
{ templateId: number }
> => {
const apiRequest = useApiRequest();
return useMutation<DeletePdfTemplateResponse, Error, { templateId: number }>(
({ templateId }) =>
apiRequest.delete(`/pdf-templates/${templateId}`).then((res) => res.data),
options,
);
};
// Hook for getting a single PDF template
export const useGetPdfTemplate = (
templateId: number,
options?: UseQueryOptions<GetPdfTemplateResponse, Error>,
): UseQueryResult<GetPdfTemplateResponse, Error> => {
const apiRequest = useApiRequest();
return useQuery<GetPdfTemplateResponse, Error>(
['pdfTemplate', templateId],
() =>
apiRequest.get(`/pdf-templates/${templateId}`).then((res) => res.data),
options,
);
};
// Hook for getting multiple PDF templates
export const useGetPdfTemplates = (
options?: UseQueryOptions<GetPdfTemplatesResponse, Error>,
): UseQueryResult<GetPdfTemplatesResponse, Error> => {
const apiRequest = useApiRequest();
return useQuery<GetPdfTemplatesResponse, Error>(
'pdfTemplates',
() => apiRequest.get('/pdf-templates').then((res) => res.data),
options,
);
};