mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
feat: link pdf template to sales transactions
This commit is contained in:
@@ -236,6 +236,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,9 +168,7 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
check('entries.*.item_id').exists().isNumeric().toInt(),
|
check('entries.*.item_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.quantity').exists().isNumeric().toInt(),
|
check('entries.*.quantity').exists().isNumeric().toInt(),
|
||||||
check('entries.*.rate').exists().isNumeric().toFloat(),
|
check('entries.*.rate').exists().isNumeric().toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim(),
|
|
||||||
check('entries.*.discount')
|
check('entries.*.discount')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
@@ -186,6 +184,9 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
|
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -224,9 +224,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim(),
|
|
||||||
check('entries.*.tax_code')
|
check('entries.*.tax_code')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.trim()
|
.trim()
|
||||||
@@ -257,6 +255,9 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toFloat(),
|
.toFloat(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,17 +148,20 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toInt(),
|
.toInt(),
|
||||||
check('entries.*.description')
|
check('entries.*.description').optional({ nullable: true }).trim(),
|
||||||
.optional({ nullable: true })
|
|
||||||
.trim(),
|
|
||||||
check('entries.*.warehouse_id')
|
check('entries.*.warehouse_id')
|
||||||
.optional({ nullable: true })
|
.optional({ nullable: true })
|
||||||
.isNumeric()
|
.isNumeric()
|
||||||
.toInt(),
|
.toInt(),
|
||||||
|
|
||||||
check('receipt_message').optional().trim(),
|
check('receipt_message').optional().trim(),
|
||||||
|
|
||||||
check('statement').optional().trim(),
|
check('statement').optional().trim(),
|
||||||
check('attachments').isArray().optional(),
|
check('attachments').isArray().optional(),
|
||||||
check('attachments.*.key').exists().isString(),
|
check('attachments.*.key').exists().isString(),
|
||||||
|
|
||||||
|
// Pdf template id.
|
||||||
|
check('pdf_template_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,49 @@
|
|||||||
* @returns { Promise<void> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.up = function (knex) {
|
exports.up = function (knex) {
|
||||||
return knex.schema.createTable('pdf_templates', (table) => {
|
return knex.schema
|
||||||
table.increments('id').primary();
|
.createTable('pdf_templates', (table) => {
|
||||||
table.text('resource');
|
table.increments('id').primary();
|
||||||
table.text('template_name');
|
table.text('resource');
|
||||||
table.json('attributes');
|
table.text('template_name');
|
||||||
table.timestamps();
|
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> }
|
* @returns { Promise<void> }
|
||||||
*/
|
*/
|
||||||
exports.down = function (knex) {
|
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');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,13 +31,18 @@ export class CreatePdfTemplate {
|
|||||||
const attributes = invoiceTemplateDTO;
|
const attributes = invoiceTemplateDTO;
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
return this.uow.withTransaction(tenantId, async (trx) => {
|
||||||
|
// Triggers `onPdfTemplateCreating` event.
|
||||||
|
await this.eventPublisher.emitAsync(events.pdfTemplate.onCreating, {
|
||||||
|
tenantId,
|
||||||
|
});
|
||||||
|
|
||||||
await PdfTemplate.query(trx).insert({
|
await PdfTemplate.query(trx).insert({
|
||||||
templateName,
|
templateName,
|
||||||
resource,
|
resource,
|
||||||
attributes,
|
attributes,
|
||||||
});
|
});
|
||||||
|
// Triggers `onPdfTemplateCreated` event.
|
||||||
await this.eventPublisher.emitAsync(events.pdfTemplate.onInvoiceCreated, {
|
await this.eventPublisher.emitAsync(events.pdfTemplate.onCreated, {
|
||||||
tenantId,
|
tenantId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,8 +24,14 @@ export class DeletePdfTemplate {
|
|||||||
const { PdfTemplate } = this.tenancy.models(tenantId);
|
const { PdfTemplate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
return this.uow.withTransaction(tenantId, async (trx) => {
|
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);
|
await PdfTemplate.query(trx).deleteById(templateId);
|
||||||
|
|
||||||
|
// Triggers `onPdfTemplateDeleted` event.
|
||||||
await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleted, {
|
await this.eventPublisher.emitAsync(events.pdfTemplate.onDeleted, {
|
||||||
tenantId,
|
tenantId,
|
||||||
templateId,
|
templateId,
|
||||||
|
|||||||
@@ -692,6 +692,7 @@ export default {
|
|||||||
onEditing: 'onPdfTemplateEditing',
|
onEditing: 'onPdfTemplateEditing',
|
||||||
onEdited: 'onPdfTemplatedEdited',
|
onEdited: 'onPdfTemplatedEdited',
|
||||||
|
|
||||||
|
onDeleting: 'onPdfTemplateDeleting',
|
||||||
onDeleted: 'onPdfTemplateDeleted',
|
onDeleted: 'onPdfTemplateDeleted',
|
||||||
|
|
||||||
onInvoiceCreated: 'onInvoicePdfTemplateCreated',
|
onInvoiceCreated: 'onInvoicePdfTemplateCreated',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { Box } from '@/components';
|
import { AppToaster, Box } from '@/components';
|
||||||
import { Classes } from '@blueprintjs/core';
|
import { Classes, Intent } from '@blueprintjs/core';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import {
|
import {
|
||||||
InvoicePaperTemplate,
|
InvoicePaperTemplate,
|
||||||
@@ -12,9 +12,51 @@ import { InvoiceCustomizeGeneralField } from './InvoiceCustomizeGeneralFields';
|
|||||||
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
|
import { InvoiceCustomizeContentFields } from './InvoiceCutomizeContentFields';
|
||||||
import { InvoiceCustomizeValues } from './types';
|
import { InvoiceCustomizeValues } from './types';
|
||||||
import { initialValues } from './constants';
|
import { initialValues } from './constants';
|
||||||
|
import {
|
||||||
|
useCreatePdfTemplate,
|
||||||
|
useEditPdfTemplate,
|
||||||
|
} from '@/hooks/query/pdf-templates';
|
||||||
|
import { transformToEditRequest, transformToNewRequest } from './utils';
|
||||||
|
|
||||||
export default function InvoiceCustomizeContent() {
|
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 (
|
return (
|
||||||
<Box className={Classes.DRAWER_BODY}>
|
<Box className={Classes.DRAWER_BODY}>
|
||||||
|
|||||||
@@ -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']),
|
||||||
|
};
|
||||||
|
};
|
||||||
131
packages/webapp/src/hooks/query/pdf-templates.ts
Normal file
131
packages/webapp/src/hooks/query/pdf-templates.ts
Normal 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,
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user