diff --git a/packages/server/src/api/controllers/Expenses/Expenses.ts b/packages/server/src/api/controllers/Expenses/Expenses.ts index fe3746910..b9257a6bd 100644 --- a/packages/server/src/api/controllers/Expenses/Expenses.ts +++ b/packages/server/src/api/controllers/Expenses/Expenses.ts @@ -84,7 +84,7 @@ export class ExpensesController extends BaseController { /** * Expense DTO schema. */ - get expenseDTOSchema() { + private get expenseDTOSchema() { return [ check('reference_no') .optional({ nullable: true }) @@ -130,6 +130,9 @@ export class ExpensesController extends BaseController { .optional({ nullable: true }) .isInt({ max: DATATYPES_LENGTH.INT_10 }) .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } @@ -183,6 +186,9 @@ export class ExpensesController extends BaseController { .optional({ nullable: true }) .isInt({ max: DATATYPES_LENGTH.INT_10 }) .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } @@ -269,7 +275,7 @@ export class ExpensesController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async deleteExpense(req: Request, res: Response, next: NextFunction) { + private async deleteExpense(req: Request, res: Response, next: NextFunction) { const { tenantId, user } = req; const { id: expenseId } = req.params; @@ -291,7 +297,11 @@ export class ExpensesController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async publishExpense(req: Request, res: Response, next: NextFunction) { + private async publishExpense( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId, user } = req; const { id: expenseId } = req.params; @@ -313,7 +323,11 @@ export class ExpensesController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async getExpensesList(req: Request, res: Response, next: NextFunction) { + private async getExpensesList( + req: Request, + res: Response, + next: NextFunction + ) { const { tenantId } = req; const filter = { sortOrder: 'desc', @@ -343,7 +357,7 @@ export class ExpensesController extends BaseController { * @param {Response} res * @param {NextFunction} next */ - async getExpense(req: Request, res: Response, next: NextFunction) { + private async getExpense(req: Request, res: Response, next: NextFunction) { const { tenantId } = req; const { id: expenseId } = req.params; diff --git a/packages/server/src/api/controllers/ManualJournals.ts b/packages/server/src/api/controllers/ManualJournals.ts index 520400658..5ebc57176 100644 --- a/packages/server/src/api/controllers/ManualJournals.ts +++ b/packages/server/src/api/controllers/ManualJournals.ts @@ -148,6 +148,9 @@ export default class ManualJournalsController extends BaseController { .optional({ nullable: true }) .isNumeric() .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Purchases/Bills.ts b/packages/server/src/api/controllers/Purchases/Bills.ts index 4f65eab26..7a013f5cc 100644 --- a/packages/server/src/api/controllers/Purchases/Bills.ts +++ b/packages/server/src/api/controllers/Purchases/Bills.ts @@ -118,7 +118,6 @@ export default class BillsController extends BaseController { check('is_inclusive_tax').default(false).isBoolean().toBoolean(), check('entries').isArray({ min: 1 }), - check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.rate').exists().isNumeric().toFloat(), @@ -148,6 +147,9 @@ export default class BillsController extends BaseController { .optional({ nullable: true }) .isNumeric() .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } @@ -190,6 +192,9 @@ export default class BillsController extends BaseController { .optional({ nullable: true }) .isBoolean() .toBoolean(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Purchases/BillsPayments.ts b/packages/server/src/api/controllers/Purchases/BillsPayments.ts index 984ab6fba..dfe46cf4d 100644 --- a/packages/server/src/api/controllers/Purchases/BillsPayments.ts +++ b/packages/server/src/api/controllers/Purchases/BillsPayments.ts @@ -122,6 +122,9 @@ export default class BillsPayments extends BaseController { check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.bill_id').exists().isNumeric().toInt(), check('entries.*.payment_amount').exists().isNumeric().toFloat(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Purchases/VendorCredit.ts b/packages/server/src/api/controllers/Purchases/VendorCredit.ts index 02f82b2d1..9b3869302 100644 --- a/packages/server/src/api/controllers/Purchases/VendorCredit.ts +++ b/packages/server/src/api/controllers/Purchases/VendorCredit.ts @@ -186,6 +186,9 @@ export default class VendorCreditController extends BaseController { .optional({ nullable: true }) .isNumeric() .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } @@ -228,6 +231,9 @@ export default class VendorCreditController extends BaseController { .optional({ nullable: true }) .isNumeric() .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Sales/CreditNotes.ts b/packages/server/src/api/controllers/Sales/CreditNotes.ts index 20b002e33..9b9553323 100644 --- a/packages/server/src/api/controllers/Sales/CreditNotes.ts +++ b/packages/server/src/api/controllers/Sales/CreditNotes.ts @@ -236,6 +236,9 @@ export default class PaymentReceivesController extends BaseController { .optional({ nullable: true }) .isNumeric() .toInt(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Sales/PaymentReceives.ts b/packages/server/src/api/controllers/Sales/PaymentReceives.ts index 818fcb713..5acd359e4 100644 --- a/packages/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/packages/server/src/api/controllers/Sales/PaymentReceives.ts @@ -164,6 +164,9 @@ export default class PaymentReceivesController extends BaseController { check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.invoice_id').exists().isNumeric().toInt(), check('entries.*.payment_amount').exists().isNumeric().toFloat(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Sales/SalesEstimates.ts b/packages/server/src/api/controllers/Sales/SalesEstimates.ts index e26b40c30..4bbcee9bb 100644 --- a/packages/server/src/api/controllers/Sales/SalesEstimates.ts +++ b/packages/server/src/api/controllers/Sales/SalesEstimates.ts @@ -184,6 +184,9 @@ export default class SalesEstimatesController extends BaseController { check('note').optional().trim().escape(), check('terms_conditions').optional().trim().escape(), check('send_to_email').optional().trim().escape(), + + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ]; } diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts index b45604e43..70fbdae6b 100644 --- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts @@ -36,6 +36,8 @@ export default class SaleInvoicesController extends BaseController { [ ...this.saleInvoiceValidationSchema, check('from_estimate_id').optional().isNumeric().toInt(), + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ], this.validationResult, asyncMiddleware(this.newSaleInvoice.bind(this)), @@ -98,6 +100,8 @@ export default class SaleInvoicesController extends BaseController { [ ...this.saleInvoiceValidationSchema, ...this.specificSaleInvoiceValidation, + check('attachments').isArray().optional(), + check('attachments.*.key').exists().isString(), ], this.validationResult, asyncMiddleware(this.editSaleInvoice.bind(this)), diff --git a/packages/server/src/database/migrations/20231108170207_create_documents_links.ts b/packages/server/src/database/migrations/20231108170207_create_documents_links.ts index 5fa27ad63..7cdb91429 100644 --- a/packages/server/src/database/migrations/20231108170207_create_documents_links.ts +++ b/packages/server/src/database/migrations/20231108170207_create_documents_links.ts @@ -4,6 +4,7 @@ exports.up = function (knex) { table.string('model_ref').notNullable(); table.string('model_id').notNullable(); table.integer('document_id').unsigned(); + table.datetime('expires_at').nullable(); table.timestamps(); }); }; diff --git a/packages/server/src/interfaces/Attachments.ts b/packages/server/src/interfaces/Attachments.ts new file mode 100644 index 000000000..d1699ed12 --- /dev/null +++ b/packages/server/src/interfaces/Attachments.ts @@ -0,0 +1,3 @@ +export interface AttachmentLinkDTO { + key: string; +} diff --git a/packages/server/src/interfaces/Bill.ts b/packages/server/src/interfaces/Bill.ts index 8276a4623..0ccc72723 100644 --- a/packages/server/src/interfaces/Bill.ts +++ b/packages/server/src/interfaces/Bill.ts @@ -2,6 +2,7 @@ import { Knex } from 'knex'; import { IDynamicListFilterDTO } from './DynamicFilter'; import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IBillLandedCost } from './LandedCost'; +import { AttachmentLinkDTO } from './Attachments'; export interface IBillDTO { vendorId: number; @@ -20,6 +21,7 @@ export interface IBillDTO { warehouseId?: number; projectId?: number; isInclusiveTax?: boolean; + attachments?: AttachmentLinkDTO[]; } export interface IBillEditDTO { @@ -38,6 +40,7 @@ export interface IBillEditDTO { branchId?: number; warehouseId?: number; projectId?: number; + attachments?: AttachmentLinkDTO[]; } export interface IBill { @@ -105,6 +108,7 @@ export interface IBillsService { export interface IBillCreatedPayload { tenantId: number; bill: IBill; + billDTO: IBillDTO; billId: number; trx: Knex.Transaction; } @@ -126,6 +130,7 @@ export interface IBillEditedPayload { billId: number; oldBill: IBill; bill: IBill; + billDTO: IBillDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/BillPayment.ts b/packages/server/src/interfaces/BillPayment.ts index f941616fa..002bf867b 100644 --- a/packages/server/src/interfaces/BillPayment.ts +++ b/packages/server/src/interfaces/BillPayment.ts @@ -1,5 +1,6 @@ import { Knex } from 'knex'; import { IBill } from './Bill'; +import { AttachmentLinkDTO } from './Attachments'; export interface IBillPaymentEntry { id?: number; @@ -45,6 +46,7 @@ export interface IBillPaymentDTO { reference: string; entries: IBillPaymentEntryDTO[]; branchId?: number; + attachments?: AttachmentLinkDTO[]; } export interface IBillReceivePageEntry { @@ -66,6 +68,7 @@ export interface IBillPaymentsService { export interface IBillPaymentEventCreatedPayload { tenantId: number; billPayment: IBillPayment; + billPaymentDTO: IBillPaymentDTO; billPaymentId: number; trx: Knex.Transaction; } @@ -87,6 +90,7 @@ export interface IBillPaymentEventEditedPayload { billPaymentId: number; billPayment: IBillPayment; oldBillPayment: IBillPayment; + billPaymentDTO: IBillPaymentDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/CreditNote.ts b/packages/server/src/interfaces/CreditNote.ts index 5eea94d34..73f4bbc14 100644 --- a/packages/server/src/interfaces/CreditNote.ts +++ b/packages/server/src/interfaces/CreditNote.ts @@ -1,6 +1,7 @@ import { Knex } from 'knex'; import { IDynamicListFilter, IItemEntry, IVendorCredit } from '@/interfaces'; import { ILedgerEntry } from './Ledger'; +import { AttachmentLinkDTO } from './Attachments'; export interface ICreditNoteEntryNewDTO { index: number; @@ -21,6 +22,7 @@ export interface ICreditNoteNewDTO { entries: ICreditNoteEntryNewDTO[]; branchId?: number; warehouseId?: number; + attachments?: AttachmentLinkDTO[] } export interface ICreditNoteEditDTO { @@ -33,6 +35,7 @@ export interface ICreditNoteEditDTO { entries: ICreditNoteEntryNewDTO[]; branchId?: number; warehouseId?: number; + attachments?: AttachmentLinkDTO[] } export interface ICreditNoteEntry extends IItemEntry {} diff --git a/packages/server/src/interfaces/Expenses.ts b/packages/server/src/interfaces/Expenses.ts index 6a47bef01..ad779db7c 100644 --- a/packages/server/src/interfaces/Expenses.ts +++ b/packages/server/src/interfaces/Expenses.ts @@ -2,6 +2,7 @@ import { Knex } from 'knex'; import { ISystemUser } from './User'; import { IFilterRole } from './DynamicFilter'; import { IAccount } from './Account'; +import { AttachmentLinkDTO } from './Attachments'; export interface IPaginationMeta { total: number; @@ -81,6 +82,7 @@ export interface IExpenseCommonDTO { categories: IExpenseCategoryDTO[]; branchId?: number; + attachments?: AttachmentLinkDTO[]; } export interface IExpenseCreateDTO extends IExpenseCommonDTO {} @@ -152,6 +154,7 @@ export interface IExpenseCreatedPayload { expenseId: number; authorizedUser: ISystemUser; expense: IExpense; + expenseDTO: IExpenseCreateDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/ManualJournal.ts b/packages/server/src/interfaces/ManualJournal.ts index 863a23bdd..8cdc112f9 100644 --- a/packages/server/src/interfaces/ManualJournal.ts +++ b/packages/server/src/interfaces/ManualJournal.ts @@ -2,6 +2,7 @@ import { Knex } from 'knex'; import { IDynamicListFilterDTO } from './DynamicFilter'; import { ISystemUser } from './User'; import { IAccount } from './Account'; +import { AttachmentLinkDTO } from './Attachments'; export interface IManualJournal { id?: number; @@ -56,6 +57,7 @@ export interface IManualJournalDTO { publish?: boolean; branchId?: number; entries: IManualJournalEntryDTO[]; + attachments?: AttachmentLinkDTO[]; } export interface IManualJournalsFilter extends IDynamicListFilterDTO { @@ -142,6 +144,7 @@ export interface IManualJournalEventEditedPayload { tenantId: number; manualJournal: IManualJournal; oldManualJournal: IManualJournal; + manualJournalDTO: IManualJournalDTO; trx: Knex.Transaction; } export interface IManualJournalEditingPayload { @@ -161,6 +164,7 @@ export interface IManualJournalEventCreatedPayload { tenantId: number; manualJournal: IManualJournal; manualJournalId: number; + manualJournalDTO: IManualJournalDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/PaymentReceive.ts b/packages/server/src/interfaces/PaymentReceive.ts index 329d0d944..68ecb0eb6 100644 --- a/packages/server/src/interfaces/PaymentReceive.ts +++ b/packages/server/src/interfaces/PaymentReceive.ts @@ -6,6 +6,7 @@ import { } from '@/interfaces'; import { ILedgerEntry } from './Ledger'; import { ISaleInvoice } from './SaleInvoice'; +import { AttachmentLinkDTO } from './Attachments'; export interface IPaymentReceive { id?: number; @@ -37,6 +38,7 @@ export interface IPaymentReceiveCreateDTO { entries: IPaymentReceiveEntryDTO[]; branchId?: number; + attachments?: AttachmentLinkDTO[]; } export interface IPaymentReceiveEditDTO { @@ -50,6 +52,7 @@ export interface IPaymentReceiveEditDTO { statement: string; entries: IPaymentReceiveEntryDTO[]; branchId?: number; + attachments?: AttachmentLinkDTO[]; } export interface IPaymentReceiveEntry { @@ -114,6 +117,7 @@ export interface IPaymentReceiveCreatedPayload { paymentReceive: IPaymentReceive; paymentReceiveId: number; authorizedUser: ISystemUser; + paymentReceiveDTO: IPaymentReceiveCreateDTO; trx: Knex.Transaction; } @@ -122,6 +126,7 @@ export interface IPaymentReceiveEditedPayload { paymentReceiveId: number; paymentReceive: IPaymentReceive; oldPaymentReceive: IPaymentReceive; + paymentReceiveDTO: IPaymentReceiveEditDTO; authorizedUser: ISystemUser; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/SaleEstimate.ts b/packages/server/src/interfaces/SaleEstimate.ts index 9ac17295c..9db21c029 100644 --- a/packages/server/src/interfaces/SaleEstimate.ts +++ b/packages/server/src/interfaces/SaleEstimate.ts @@ -2,6 +2,7 @@ import { Knex } from 'knex'; import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter'; import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; +import { AttachmentLinkDTO } from './Attachments'; export interface ISaleEstimate { id?: number; @@ -38,6 +39,7 @@ export interface ISaleEstimateDTO { branchId?: number; warehouseId?: number; + attachments?: AttachmentLinkDTO[]; } export interface ISalesEstimatesFilter extends IDynamicListFilterDTO { @@ -70,6 +72,7 @@ export interface ISaleEstimateEditedPayload { estimateId: number; saleEstimate: ISaleEstimate; oldSaleEstimate: ISaleEstimate; + estimateDTO: ISaleEstimateDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts index 61594306a..6fd5e753b 100644 --- a/packages/server/src/interfaces/SaleInvoice.ts +++ b/packages/server/src/interfaces/SaleInvoice.ts @@ -3,6 +3,7 @@ import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces'; import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; import { IDynamicListFilter } from '@/interfaces/DynamicFilter'; import { IItemEntry, IItemEntryDTO } from './ItemEntry'; +import { AttachmentLinkDTO } from './Attachments'; export interface ISaleInvoice { id: number; @@ -64,6 +65,8 @@ export interface ISaleInvoiceDTO { branchId?: number | null; isInclusiveTax?: boolean; + + attachments?: AttachmentLinkDTO[]; } export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO { diff --git a/packages/server/src/interfaces/SaleReceipt.ts b/packages/server/src/interfaces/SaleReceipt.ts index 8904767c6..36e84e0db 100644 --- a/packages/server/src/interfaces/SaleReceipt.ts +++ b/packages/server/src/interfaces/SaleReceipt.ts @@ -1,6 +1,7 @@ import { Knex } from 'knex'; import { IItemEntry } from './ItemEntry'; import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; +import { AttachmentLinkDTO } from './Attachments'; export interface ISaleReceipt { id?: number; @@ -43,6 +44,7 @@ export interface ISaleReceiptDTO { closed: boolean; entries: any[]; branchId?: number; + attachments?: AttachmentLinkDTO[]; } export interface ISalesReceiptsService { @@ -85,6 +87,7 @@ export interface ISaleReceiptCreatedPayload { tenantId: number; saleReceipt: ISaleReceipt; saleReceiptId: number; + saleReceiptDTO: ISaleReceiptDTO; trx: Knex.Transaction; } @@ -93,6 +96,7 @@ export interface ISaleReceiptEditedPayload { oldSaleReceipt: number; saleReceipt: ISaleReceipt; saleReceiptId: number; + saleReceiptDTO: ISaleReceiptDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/interfaces/VendorCredit.ts b/packages/server/src/interfaces/VendorCredit.ts index dd8f6b6e4..d7c8f14d3 100644 --- a/packages/server/src/interfaces/VendorCredit.ts +++ b/packages/server/src/interfaces/VendorCredit.ts @@ -1,5 +1,6 @@ import { IDynamicListFilter, IItemEntry, IItemEntryDTO } from '@/interfaces'; import { Knex } from 'knex'; +import { AttachmentLinkDTO } from './Attachments'; export enum VendorCreditAction { Create = 'Create', @@ -61,6 +62,7 @@ export interface IVendorCreditDTO { branchId?: number; warehouseId?: number; + attachments?: AttachmentLinkDTO[]; } export interface IVendorCreditCreateDTO extends IVendorCreditDTO {} @@ -118,6 +120,7 @@ export interface IVendorCreditEditedPayload { oldVendorCredit: IVendorCredit; vendorCredit: IVendorCredit; vendorCreditId: number; + vendorCreditDTO: IVendorCreditEditDTO; trx: Knex.Transaction; } diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index 9da52fd86..ad83f6dec 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -92,7 +92,16 @@ import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/sub import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete'; import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity'; import { SendVerfiyMailOnSignUp } from '@/services/Authentication/events/SendVerfiyMailOnSignUp'; - +import { AttachmentsOnSaleInvoiceCreated } from '@/services/Attachments/events/AttachmentsOnSaleInvoice'; +import { AttachmentsOnSaleReceipt } from '@/services/Attachments/events/AttachmentsOnSaleReceipts'; +import { AttachmentsOnManualJournals } from '@/services/Attachments/events/AttachmentsOnManualJournals'; +import { AttachmentsOnExpenses } from '@/services/Attachments/events/AttachmentsOnExpenses'; +import { AttachmentsOnBills } from '@/services/Attachments/events/AttachmentsOnBills'; +import { AttachmentsOnPaymentsReceived } from '@/services/Attachments/events/AttachmentsOnPaymentsReceived'; +import { AttachmentsOnVendorCredits } from '@/services/Attachments/events/AttachmentsOnVendorCredits'; +import { AttachmentsOnCreditNote } from '@/services/Attachments/events/AttachmentsOnCreditNote'; +import { AttachmentsOnBillPayments } from '@/services/Attachments/events/AttachmentsOnPaymentsMade'; +import { AttachmentsOnSaleEstimates } from '@/services/Attachments/events/AttachmentsOnSaleEstimates'; export default () => { return new EventPublisher(); @@ -224,6 +233,18 @@ export const susbcribers = () => { PreventDeleteTransactionOnDelete, SubscribeFreeOnSignupCommunity, - SendVerfiyMailOnSignUp + SendVerfiyMailOnSignUp, + + // Attachments + AttachmentsOnSaleInvoiceCreated, + AttachmentsOnSaleEstimates, + AttachmentsOnSaleReceipt, + AttachmentsOnPaymentsReceived, + AttachmentsOnCreditNote, + AttachmentsOnVendorCredits, + AttachmentsOnBills, + AttachmentsOnBillPayments, + AttachmentsOnManualJournals, + AttachmentsOnExpenses, ]; }; diff --git a/packages/server/src/models/Bill.ts b/packages/server/src/models/Bill.ts index 17dd07fa4..439669ad5 100644 --- a/packages/server/src/models/Bill.ts +++ b/packages/server/src/models/Bill.ts @@ -403,6 +403,7 @@ export default class Bill extends mixin(TenantModel, [ const BillLandedCost = require('models/BillLandedCost'); const Branch = require('models/Branch'); const TaxRateTransaction = require('models/TaxRateTransaction'); + const Document = require('models/Document'); return { vendor: { @@ -465,6 +466,25 @@ export default class Bill extends mixin(TenantModel, [ builder.where('reference_type', 'Bill'); }, }, + + /** + * Bill may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'bills.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'Bill'); + }, + }, }; } diff --git a/packages/server/src/models/BillPayment.ts b/packages/server/src/models/BillPayment.ts index 488f32793..d3f0dfe21 100644 --- a/packages/server/src/models/BillPayment.ts +++ b/packages/server/src/models/BillPayment.ts @@ -56,6 +56,7 @@ export default class BillPayment extends mixin(TenantModel, [ const Vendor = require('models/Vendor'); const Account = require('models/Account'); const Branch = require('models/Branch'); + const Document = require('models/Document'); return { entries: { @@ -114,6 +115,25 @@ export default class BillPayment extends mixin(TenantModel, [ to: 'branches.id', }, }, + + /** + * Bill payment may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'bills_payments.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'BillPayment'); + }, + }, }; } diff --git a/packages/server/src/models/CreditNote.ts b/packages/server/src/models/CreditNote.ts index f9b901397..a682753c4 100644 --- a/packages/server/src/models/CreditNote.ts +++ b/packages/server/src/models/CreditNote.ts @@ -174,6 +174,7 @@ export default class CreditNote extends mixin(TenantModel, [ const ItemEntry = require('models/ItemEntry'); const Customer = require('models/Customer'); const Branch = require('models/Branch'); + const Document = require('models/Document'); return { /** @@ -233,6 +234,25 @@ export default class CreditNote extends mixin(TenantModel, [ to: 'branches.id', }, }, + + /** + * Credit note may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'credit_notes.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'CreditNote'); + }, + }, }; } diff --git a/packages/server/src/models/Expense.ts b/packages/server/src/models/Expense.ts index b2fce9a65..21fa0ac9c 100644 --- a/packages/server/src/models/Expense.ts +++ b/packages/server/src/models/Expense.ts @@ -180,7 +180,7 @@ export default class Expense extends mixin(TenantModel, [ static get relationMappings() { const Account = require('models/Account'); const ExpenseCategory = require('models/ExpenseCategory'); - const Media = require('models/Media'); + const Document = require('models/Document'); const Branch = require('models/Branch'); return { @@ -217,21 +217,21 @@ export default class Expense extends mixin(TenantModel, [ }, /** - * + * Expense transaction may has many attached attachments. */ - media: { + attachments: { relation: Model.ManyToManyRelation, - modelClass: Media.default, + modelClass: Document.default, join: { from: 'expenses_transactions.id', through: { - from: 'media_links.model_id', - to: 'media_links.media_id', + from: 'document_links.modelId', + to: 'document_links.documentId', }, - to: 'media.id', + to: 'documents.id', }, filter(query) { - query.where('model_name', 'Expense'); + query.where('model_ref', 'Expense'); }, }, }; diff --git a/packages/server/src/models/ManualJournal.ts b/packages/server/src/models/ManualJournal.ts index ab605b51e..e4380acb8 100644 --- a/packages/server/src/models/ManualJournal.ts +++ b/packages/server/src/models/ManualJournal.ts @@ -94,9 +94,9 @@ export default class ManualJournal extends mixin(TenantModel, [ * Relationship mapping. */ static get relationMappings() { - const Media = require('models/Media'); const AccountTransaction = require('models/AccountTransaction'); const ManualJournalEntry = require('models/ManualJournalEntry'); + const ManualJournal = require('models/ManualJournal'); return { entries: { @@ -121,19 +121,23 @@ export default class ManualJournal extends mixin(TenantModel, [ query.where('referenceType', 'Journal'); }, }, - media: { + + /** + * Manual journal may has many attached attachments. + */ + attachments: { relation: Model.ManyToManyRelation, - modelClass: Media.default, + modelClass: ManualJournal.default, join: { from: 'manual_journals.id', through: { - from: 'media_links.model_id', - to: 'media_links.media_id', + from: 'document_links.modelId', + to: 'document_links.documentId', }, - to: 'media.id', + to: 'documents.id', }, filter(query) { - query.where('model_name', 'ManualJournal'); + query.where('model_ref', 'ManualJournal'); }, }, }; diff --git a/packages/server/src/models/PaymentReceive.ts b/packages/server/src/models/PaymentReceive.ts index e27559dbd..b9c2b87bf 100644 --- a/packages/server/src/models/PaymentReceive.ts +++ b/packages/server/src/models/PaymentReceive.ts @@ -56,6 +56,7 @@ export default class PaymentReceive extends mixin(TenantModel, [ const Customer = require('models/Customer'); const Account = require('models/Account'); const Branch = require('models/Branch'); + const Document = require('models/Document'); return { customer: { @@ -111,6 +112,25 @@ export default class PaymentReceive extends mixin(TenantModel, [ to: 'branches.id', }, }, + + /** + * Payment transaction may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'payment_receives.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'PaymentReceive'); + }, + }, }; } diff --git a/packages/server/src/models/SaleEstimate.ts b/packages/server/src/models/SaleEstimate.ts index 31d40eb82..0df7f2468 100644 --- a/packages/server/src/models/SaleEstimate.ts +++ b/packages/server/src/models/SaleEstimate.ts @@ -182,6 +182,7 @@ export default class SaleEstimate extends mixin(TenantModel, [ const ItemEntry = require('models/ItemEntry'); const Customer = require('models/Customer'); const Branch = require('models/Branch'); + const Document = require('models/Document'); return { customer: { @@ -219,6 +220,25 @@ export default class SaleEstimate extends mixin(TenantModel, [ to: 'branches.id', }, }, + + /** + * Sale estimate transaction may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'sales_estimates.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'Expense'); + }, + }, }; } diff --git a/packages/server/src/models/SaleInvoice.ts b/packages/server/src/models/SaleInvoice.ts index 3ec2ba85b..6c86bdaac 100644 --- a/packages/server/src/models/SaleInvoice.ts +++ b/packages/server/src/models/SaleInvoice.ts @@ -410,7 +410,7 @@ export default class SaleInvoice extends mixin(TenantModel, [ const Branch = require('models/Branch'); const Account = require('models/Account'); const TaxRateTransaction = require('models/TaxRateTransaction'); - const DocumentLink = require('models/DocumentLink'); + const Document = require('models/Document'); return { /** @@ -526,17 +526,21 @@ export default class SaleInvoice extends mixin(TenantModel, [ }, /** - * Invoice may has many attachments. + * Sale invoice transaction may has many attached attachments. */ attachments: { - relation: Model.HasManyRelation, - modelClass: DocumentLink.default, + relation: Model.ManyToManyRelation, + modelClass: Document.default, join: { from: 'sales_invoices.id', - to: 'document_links.modelId', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', }, - filter: (builder) => { - builder.where('modelRef', 'SaleInvoice'); + filter(query) { + query.where('model_ref', 'Expense'); }, }, }; diff --git a/packages/server/src/models/SaleReceipt.ts b/packages/server/src/models/SaleReceipt.ts index 4b20ce78f..ba39af02e 100644 --- a/packages/server/src/models/SaleReceipt.ts +++ b/packages/server/src/models/SaleReceipt.ts @@ -108,6 +108,7 @@ export default class SaleReceipt extends mixin(TenantModel, [ const AccountTransaction = require('models/AccountTransaction'); const ItemEntry = require('models/ItemEntry'); const Branch = require('models/Branch'); + const Document = require('models/Document'); return { customer: { @@ -167,6 +168,25 @@ export default class SaleReceipt extends mixin(TenantModel, [ to: 'branches.id', }, }, + + /** + * Sale receipt transaction may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'sales_receipts.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'SaleReceipt'); + }, + }, }; } diff --git a/packages/server/src/models/VendorCredit.ts b/packages/server/src/models/VendorCredit.ts index 61f7e8241..1dd949205 100644 --- a/packages/server/src/models/VendorCredit.ts +++ b/packages/server/src/models/VendorCredit.ts @@ -177,6 +177,7 @@ export default class VendorCredit extends mixin(TenantModel, [ const Vendor = require('models/Vendor'); const ItemEntry = require('models/ItemEntry'); const Branch = require('models/Branch'); + const Document = require('models/Document'); return { vendor: { @@ -215,6 +216,25 @@ export default class VendorCredit extends mixin(TenantModel, [ to: 'branches.id', }, }, + + /** + * Vendor credit may has many attached attachments. + */ + attachments: { + relation: Model.ManyToManyRelation, + modelClass: Document.default, + join: { + from: 'vendor_credits.id', + through: { + from: 'document_links.modelId', + to: 'document_links.documentId', + }, + to: 'documents.id', + }, + filter(query) { + query.where('model_ref', 'VendorCredit'); + }, + }, }; } diff --git a/packages/server/src/services/Attachments/AttachmentTransformer.ts b/packages/server/src/services/Attachments/AttachmentTransformer.ts new file mode 100644 index 000000000..8297b4f9c --- /dev/null +++ b/packages/server/src/services/Attachments/AttachmentTransformer.ts @@ -0,0 +1,19 @@ +import { Transformer } from '@/lib/Transformer/Transformer'; + +export class AttachmentTransformer extends Transformer { + /** + * Exclude attributes. + * @returns {string[]} + */ + public excludeAttributes = (): string[] => { + return ['*']; + }; + + /** + * Includeded attributes. + * @returns {string[]} + */ + public includeAttributes = (): string[] => { + return ['extension', 'key']; + }; +} diff --git a/packages/server/src/services/Attachments/LinkAttachment.ts b/packages/server/src/services/Attachments/LinkAttachment.ts index 1233e265a..4ed677185 100644 --- a/packages/server/src/services/Attachments/LinkAttachment.ts +++ b/packages/server/src/services/Attachments/LinkAttachment.ts @@ -1,4 +1,6 @@ import { Inject, Service } from 'typedi'; +import bluebird from 'bluebird'; +import { Knex } from 'knex'; import { validateLinkModelEntryExists, validateLinkModelExists, @@ -12,38 +14,68 @@ export class LinkAttachment { private tenancy: HasTenancyService; /** - * + * Links the given file key to the given model type and id. * @param {number} tenantId * @param {string} filekey * @param {string} modelRef * @param {number} modelId + * @returns {Promise} */ async link( tenantId: number, filekey: string, modelRef: string, - modelId: number + modelId: number, + trx?: Knex.Transaction ) { const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId); const LinkModel = models[modelRef]; validateLinkModelExists(LinkModel); - const foundLinkModel = await LinkModel.query().findById(modelId); + const foundLinkModel = await LinkModel.query(trx).findById(modelId); validateLinkModelEntryExists(foundLinkModel); - const foundLinks = await DocumentLink.query() + const foundLinks = await DocumentLink.query(trx) .where('modelRef', modelRef) .where('modelId', modelId); if (foundLinks.length > 0) { - throw new ServiceError(''); + throw new ServiceError('DOCUMENT_LINK_ALREADY_LINKED'); } - const foundFile = await Document.query().findOne('key', filekey); + const foundFile = await Document.query(trx).findOne('key', filekey); - await DocumentLink.query().insert({ + await DocumentLink.query(trx).insert({ modelRef, modelId, documentId: foundFile.id, }); } + + /** + * Links the given file keys to the given model type and id. + * @param {number} tenantId + * @param {string[]} filekeys + * @param {string} modelRef + * @param {number} modelId + * @param {Knex.Transaction} trx + * @returns {Promise} + */ + async bulkLink( + tenantId: number, + filekeys: string[], + modelRef: string, + modelId: number, + trx?: Knex.Transaction + ) { + await bluebird.map( + filekeys, + (fieldKey: string) => + this.link(tenantId, fieldKey, modelRef, modelId, trx), + { + concurrency: CONCURRENCY_ASYNC, + } + ); + } } + +const CONCURRENCY_ASYNC = 10; diff --git a/packages/server/src/services/Attachments/UnlinkAttachment.ts b/packages/server/src/services/Attachments/UnlinkAttachment.ts index 9aa24b34f..78339a42e 100644 --- a/packages/server/src/services/Attachments/UnlinkAttachment.ts +++ b/packages/server/src/services/Attachments/UnlinkAttachment.ts @@ -1,9 +1,12 @@ import { Inject, Service } from 'typedi'; +import bluebird from 'bluebird'; import HasTenancyService from '../Tenancy/TenancyService'; import { validateLinkModelEntryExists, validateLinkModelExists, } from './_utils'; +import { Knex } from 'knex'; +import { difference } from 'lodash'; @Service() export class UnlinkAttachment { @@ -11,7 +14,7 @@ export class UnlinkAttachment { private tenancy: HasTenancyService; /** - * + * Unlink the attachments from the model entry. * @param {number} tenantId * @param {string} filekey * @param {string} modelRef @@ -21,19 +24,105 @@ export class UnlinkAttachment { tenantId: number, filekey: string, modelRef: string, - modelId: number - ) { - const { DocumentLink, ...models } = this.tenancy.models(tenantId); + modelId: number, + trx?: Knex.Transaction + ): Promise { + const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId); const LinkModel = models[modelRef]; validateLinkModelExists(LinkModel); - const foundLinkModel = await LinkModel.query().findById(modelId); + const foundLinkModel = await LinkModel.query(trx).findById(modelId); validateLinkModelEntryExists(foundLinkModel); + const document = await Document.query().findOne('key', filekey); + // Delete the document link. - await DocumentLink.query() + await DocumentLink.query(trx) .where('modelRef', modelRef) .where('modelId', modelId) + .where('documentId', document.id) .delete(); } + + /** + * Bulk unlink the attachments from the model entry. + * @param {number} tenantId + * @param {string} fieldkey + * @param {string} modelRef + * @param {number} modelId + * @returns {Promise} + */ + async bulkUnlink( + tenantId: number, + filekeys: string[], + modelRef: string, + modelId: number, + trx?: Knex.Transaction + ): Promise { + await bluebird.map( + filekeys, + (fieldKey: string) => + this.unlink(tenantId, fieldKey, modelRef, modelId, trx), + { + concurrency: CONCURRENCY_ASYNC, + } + ); + } + + /** + * Unlink all the unpresented keys of the given model type and id. + * @param {number} tenantId + * @param {string[]} presentedKeys + * @param {string} modelRef + * @param {number} modelId + * @param {Knex.Transaction} trx + */ + async unlinkUnpresentedKeys( + tenantId: number, + presentedKeys: string[], + modelRef: string, + modelId: number, + trx?: Knex.Transaction + ): Promise { + const { DocumentLink } = this.tenancy.models(tenantId); + + const modelLinks = await DocumentLink.query(trx) + .where('modelRef', modelRef) + .where('modelId', modelId) + .withGraphFetched('document'); + + const modelLinkKeys = modelLinks.map((link) => link.document.key); + const unpresentedKeys = difference(modelLinkKeys, presentedKeys); + + await this.bulkUnlink(tenantId, unpresentedKeys, modelRef, modelId, trx); + } + + /** + * Unlink all attachments of the given model type and id. + * @param {number} tenantId + * @param {string} modelRef + * @param {number} modelId + * @param {Knex.Transaction} trx + * @returns {Promise} + */ + async unlinkAllModelKeys( + tenantId: number, + modelRef: string, + modelId: number, + trx?: Knex.Transaction + ): Promise { + const { DocumentLink } = this.tenancy.models(tenantId); + + // Get all the keys of the modelRef and modelId. + const modelLinks = await DocumentLink.query(trx) + .where('modelRef', modelRef) + .where('modelId', modelId) + .withGraphFetched('document'); + + const modelLinkKeys = modelLinks.map((link) => link.document.key); + + await this.bulkUnlink(tenantId, modelLinkKeys, modelRef, modelId, trx); + } } + +const CONCURRENCY_ASYNC = 10; diff --git a/packages/server/src/services/Attachments/ValidateAttachments.ts b/packages/server/src/services/Attachments/ValidateAttachments.ts new file mode 100644 index 000000000..885833f40 --- /dev/null +++ b/packages/server/src/services/Attachments/ValidateAttachments.ts @@ -0,0 +1,29 @@ +import { castArray, difference } from 'lodash'; +import HasTenancyService from '../Tenancy/TenancyService'; +import { ServiceError } from '@/exceptions'; +import { Inject, Service } from 'typedi'; + +@Service() +export class ValidateAttachments { + @Inject() + tenancy: HasTenancyService; + + /** + * Validates the given file keys existance. + * @param {number} tenantId + * @param {string|string[]} key + */ + async validate(tenantId: number, key: string | string[]) { + const { Document } = this.tenancy.models(tenantId); + + const keys = castArray(key); + const documents = await Document.query().whereIn('key', key); + const documentKeys = documents.map((document) => document.key); + + const notFoundKeys = difference(keys, documentKeys); + + if (notFoundKeys.length > 0) { + throw new ServiceError('DOCUMENT_KEYS_INVALID'); + } + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnBills.ts b/packages/server/src/services/Attachments/events/AttachmentsOnBills.ts new file mode 100644 index 000000000..162144a4d --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnBills.ts @@ -0,0 +1,149 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + IBIllEventDeletedPayload, + IBillCreatedPayload, + IBillCreatingPayload, + IBillEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnBills { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.bill.onCreating, + this.validateAttachmentsOnBillCreate.bind(this) + ); + bus.subscribe( + events.bill.onCreated, + this.handleAttachmentsOnBillCreated.bind(this) + ); + bus.subscribe( + events.bill.onEdited, + this.handleUnlinkUnpresentedKeysOnBillEdited.bind(this) + ); + bus.subscribe( + events.bill.onEdited, + this.handleLinkPresentedKeysOnBillEdited.bind(this) + ); + bus.subscribe( + events.bill.onDeleting, + this.handleUnlinkAttachmentsOnBillDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating bill. + * @param {ISaleInvoiceCreatingPaylaod} + * @returns {Promise} + */ + private async validateAttachmentsOnBillCreate({ + billDTO, + tenantId, + }: IBillCreatingPayload): Promise { + if (isEmpty(billDTO.attachments)) { + return; + } + const documentKeys = billDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created bill. + * @param {ISaleInvoiceCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnBillCreated({ + tenantId, + bill, + billDTO, + trx, + }: IBillCreatedPayload): Promise { + if (isEmpty(billDTO.attachments)) return; + + const keys = billDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'Bill', + bill.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited bill. + * @param {IBillEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnBillEdited({ + tenantId, + billDTO, + bill, + }: IBillEditedPayload) { + const keys = billDTO.attachments?.map((attachment) => attachment.key); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'Bill', + bill.id + ); + } + + /** + * Handles linking all the presented keys of the edited bill. + * @param {ISaleInvoiceEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnBillEdited({ + tenantId, + billDTO, + oldBill, + trx, + }: IBillEditedPayload) { + if (isEmpty(billDTO.attachments)) return; + + const keys = billDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'Bill', + oldBill.id, + trx + ); + } + + /** + * Unlink all attachments once the bill deleted. + * @param {ISaleInvoiceDeletedPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnBillDeleted({ + tenantId, + oldBill, + trx, + }: IBIllEventDeletedPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'Bill', + oldBill.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnCreditNote.ts b/packages/server/src/services/Attachments/events/AttachmentsOnCreditNote.ts new file mode 100644 index 000000000..ec4009d25 --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnCreditNote.ts @@ -0,0 +1,153 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + ICreditNoteCreatedPayload, + ICreditNoteCreatingPayload, + ICreditNoteDeletingPayload, + ICreditNoteEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnCreditNote { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.creditNote.onCreating, + this.validateAttachmentsOnCreditNoteCreate.bind(this) + ); + bus.subscribe( + events.creditNote.onCreated, + this.handleAttachmentsOnCreditNoteCreated.bind(this) + ); + bus.subscribe( + events.creditNote.onEdited, + this.handleUnlinkUnpresentedKeysOnCreditNoteEdited.bind(this) + ); + bus.subscribe( + events.creditNote.onEdited, + this.handleLinkPresentedKeysOnCreditNoteEdited.bind(this) + ); + bus.subscribe( + events.creditNote.onDeleting, + this.handleUnlinkAttachmentsOnCreditNoteDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating credit note. + * @param {ICreditNoteCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnCreditNoteCreate({ + creditNoteDTO, + tenantId, + }: ICreditNoteCreatingPayload): Promise { + if (isEmpty(creditNoteDTO.attachments)) { + return; + } + const documentKeys = creditNoteDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created credit note. + * @param {ICreditNoteCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnCreditNoteCreated({ + tenantId, + creditNote, + creditNoteDTO, + trx, + }: ICreditNoteCreatedPayload): Promise { + if (isEmpty(creditNoteDTO.attachments)) return; + + const keys = creditNoteDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'CreditNote', + creditNote.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited credit note. + * @param {ICreditNoteEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnCreditNoteEdited({ + tenantId, + creditNoteEditDTO, + oldCreditNote, + }: ICreditNoteEditedPayload) { + const keys = creditNoteEditDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'CreditNote', + oldCreditNote.id + ); + } + + /** + * Handles linking all the presented keys of the edited credit note. + * @param {ICreditNoteEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnCreditNoteEdited({ + tenantId, + creditNoteEditDTO, + oldCreditNote, + trx, + }: ICreditNoteEditedPayload) { + if (isEmpty(creditNoteEditDTO.attachments)) return; + + const keys = creditNoteEditDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'CreditNote', + oldCreditNote.id, + trx + ); + } + + /** + * Unlink all attachments once the credit note deleted. + * @param {ICreditNoteDeletingPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnCreditNoteDeleted({ + tenantId, + oldCreditNote, + trx, + }: ICreditNoteDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'CreditNote', + oldCreditNote.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnExpenses.ts b/packages/server/src/services/Attachments/events/AttachmentsOnExpenses.ts new file mode 100644 index 000000000..99415e76e --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnExpenses.ts @@ -0,0 +1,149 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + IExpenseCreatedPayload, + IExpenseCreatingPayload, + IExpenseDeletingPayload, + IExpenseEventEditPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnExpenses { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.expenses.onCreating, + this.validateAttachmentsOnExpenseCreate.bind(this) + ); + bus.subscribe( + events.expenses.onCreated, + this.handleAttachmentsOnExpenseCreated.bind(this) + ); + bus.subscribe( + events.expenses.onEdited, + this.handleUnlinkUnpresentedKeysOnExpenseEdited.bind(this) + ); + bus.subscribe( + events.expenses.onEdited, + this.handleLinkPresentedKeysOnExpenseEdited.bind(this) + ); + bus.subscribe( + events.expenses.onDeleting, + this.handleUnlinkAttachmentsOnExpenseDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating expense. + * @param {ISaleInvoiceCreatingPaylaod} + * @returns {Promise} + */ + private async validateAttachmentsOnExpenseCreate({ + expenseDTO, + tenantId, + }: IExpenseCreatingPayload): Promise { + if (isEmpty(expenseDTO.attachments)) { + return; + } + const documentKeys = expenseDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created expense. + * @param {ISaleInvoiceCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnExpenseCreated({ + tenantId, + expenseDTO, + expense, + trx, + }: IExpenseCreatedPayload): Promise { + if (isEmpty(expenseDTO.attachments)) return; + + const keys = expenseDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'Expense', + expense.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited expense. + * @param {ISaleInvoiceEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnExpenseEdited({ + tenantId, + expenseDTO, + expense, + }: IExpenseEventEditPayload) { + const keys = expenseDTO.attachments?.map((attachment) => attachment.key); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'Expense', + expense.id + ); + } + + /** + * Handles linking all the presented keys of the edited expense. + * @param {ISaleInvoiceEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnExpenseEdited({ + tenantId, + expenseDTO, + oldExpense, + trx, + }: IExpenseEventEditPayload) { + if (isEmpty(expenseDTO.attachments)) return; + + const keys = expenseDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'Expense', + oldExpense.id, + trx + ); + } + + /** + * Unlink all attachments once the expense deleted. + * @param {ISaleInvoiceDeletedPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnExpenseDeleted({ + tenantId, + oldExpense, + trx, + }: IExpenseDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'Expense', + oldExpense.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnManualJournals.ts b/packages/server/src/services/Attachments/events/AttachmentsOnManualJournals.ts new file mode 100644 index 000000000..468daf047 --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnManualJournals.ts @@ -0,0 +1,157 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + IManualJournalCreatingPayload, + IManualJournalEventCreatedPayload, + IManualJournalEventDeletedPayload, + IManualJournalEventEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnManualJournals { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.manualJournals.onCreating, + this.validateAttachmentsOnManualJournalCreate.bind(this) + ); + bus.subscribe( + events.manualJournals.onCreated, + this.handleAttachmentsOnManualJournalCreated.bind(this) + ); + bus.subscribe( + events.manualJournals.onEdited, + this.handleUnlinkUnpresentedKeysOnManualJournalEdited.bind(this) + ); + bus.subscribe( + events.manualJournals.onEdited, + this.handleLinkPresentedKeysOnManualJournalEdited.bind(this) + ); + bus.subscribe( + events.manualJournals.onDeleting, + this.handleUnlinkAttachmentsOnManualJournalDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating manual journal. + * @param {IManualJournalCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnManualJournalCreate({ + manualJournalDTO, + tenantId, + }: IManualJournalCreatingPayload): Promise { + if (isEmpty(manualJournalDTO.attachments)) { + return; + } + const documentKeys = manualJournalDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created manual journal. + * @param {IManualJournalEventCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnManualJournalCreated({ + tenantId, + manualJournalDTO, + manualJournal, + trx, + }: IManualJournalEventCreatedPayload): Promise { + if (isEmpty(manualJournalDTO.attachments)) return; + + const keys = manualJournalDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'ManualJournal', + manualJournal.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited manual journal. + * @param {ISaleInvoiceEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnManualJournalEdited({ + tenantId, + manualJournalDTO, + manualJournal, + }: IManualJournalEventEditedPayload) { + // if (isEmpty(saleInvoiceDTO.attachments)) return; + + const keys = manualJournalDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'SaleInvoice', + manualJournal.id + ); + } + + /** + * Handles linking all the presented keys of the edited manual journal. + * @param {ISaleInvoiceEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnManualJournalEdited({ + tenantId, + manualJournalDTO, + oldManualJournal, + trx, + }: IManualJournalEventEditedPayload) { + if (isEmpty(manualJournalDTO.attachments)) return; + + const keys = manualJournalDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'ManualJournal', + oldManualJournal.id, + trx + ); + } + + /** + * Unlink all attachments once the manual journal deleted. + * @param {ISaleInvoiceDeletedPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnManualJournalDeleted({ + tenantId, + oldManualJournal, + trx, + }: IManualJournalEventDeletedPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'SaleInvoice', + oldManualJournal.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnPaymentsMade.ts b/packages/server/src/services/Attachments/events/AttachmentsOnPaymentsMade.ts new file mode 100644 index 000000000..945115330 --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnPaymentsMade.ts @@ -0,0 +1,155 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + IBillPaymentCreatingPayload, + IBillPaymentDeletingPayload, + IBillPaymentEventCreatedPayload, + IBillPaymentEventEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnBillPayments{ + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.billPayment.onCreating, + this.validateAttachmentsOnBillPaymentCreate.bind(this) + ); + bus.subscribe( + events.billPayment.onCreated, + this.handleAttachmentsOnBillPaymentCreated.bind(this) + ); + bus.subscribe( + events.billPayment.onEdited, + this.handleUnlinkUnpresentedKeysOnBillPaymentEdited.bind(this) + ); + bus.subscribe( + events.billPayment.onEdited, + this.handleLinkPresentedKeysOnBillPaymentEdited.bind(this) + ); + bus.subscribe( + events.billPayment.onDeleting, + this.handleUnlinkAttachmentsOnBillPaymentDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating bill payment. + * @param {IBillPaymentCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnBillPaymentCreate({ + billPaymentDTO, + tenantId, + }: IBillPaymentCreatingPayload): Promise { + if (isEmpty(billPaymentDTO.attachments)) { + return; + } + const documentKeys = billPaymentDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created bill payment. + * @param {IBillPaymentEventCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnBillPaymentCreated({ + tenantId, + billPaymentDTO, + billPayment, + trx, + }: IBillPaymentEventCreatedPayload): Promise { + if (isEmpty(billPaymentDTO.attachments)) return; + + const keys = billPaymentDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'BillPayment', + billPayment.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited bill payment. + * @param {IBillPaymentEventEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnBillPaymentEdited({ + tenantId, + billPaymentDTO, + oldBillPayment, + }: IBillPaymentEventEditedPayload) { + const keys = billPaymentDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'BillPayment', + oldBillPayment.id + ); + } + + /** + * Handles linking all the presented keys of the edited bill payment. + * @param {IBillPaymentEventEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnBillPaymentEdited({ + tenantId, + billPaymentDTO, + oldBillPayment, + trx, + }: IBillPaymentEventEditedPayload) { + if (isEmpty(billPaymentDTO.attachments)) return; + + const keys = billPaymentDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'BillPayment', + oldBillPayment.id, + trx + ); + } + + /** + * Unlink all attachments once the bill payment deleted. + * @param {IBillPaymentDeletingPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnBillPaymentDeleted({ + tenantId, + oldBillPayment, + trx, + }: IBillPaymentDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'BillPayment', + oldBillPayment.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnPaymentsReceived.ts b/packages/server/src/services/Attachments/events/AttachmentsOnPaymentsReceived.ts new file mode 100644 index 000000000..eff3acd14 --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnPaymentsReceived.ts @@ -0,0 +1,155 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + IPaymentReceiveCreatedPayload, + IPaymentReceiveCreatingPayload, + IPaymentReceiveDeletingPayload, + IPaymentReceiveEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnPaymentsReceived { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.paymentReceive.onCreating, + this.validateAttachmentsOnPaymentCreate.bind(this) + ); + bus.subscribe( + events.paymentReceive.onCreated, + this.handleAttachmentsOnPaymentCreated.bind(this) + ); + bus.subscribe( + events.paymentReceive.onEdited, + this.handleUnlinkUnpresentedKeysOnPaymentEdited.bind(this) + ); + bus.subscribe( + events.paymentReceive.onEdited, + this.handleLinkPresentedKeysOnPaymentEdited.bind(this) + ); + bus.subscribe( + events.paymentReceive.onDeleting, + this.handleUnlinkAttachmentsOnPaymentDelete.bind(this) + ); + } + + /** + * Validates the attachment keys on creating payment. + * @param {IPaymentReceiveCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnPaymentCreate({ + paymentReceiveDTO, + tenantId, + }: IPaymentReceiveCreatingPayload): Promise { + if (isEmpty(paymentReceiveDTO.attachments)) { + return; + } + const documentKeys = paymentReceiveDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created payment. + * @param {IPaymentReceiveCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnPaymentCreated({ + tenantId, + paymentReceiveDTO, + paymentReceive, + trx, + }: IPaymentReceiveCreatedPayload): Promise { + if (isEmpty(paymentReceiveDTO.attachments)) return; + + const keys = paymentReceiveDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'PaymentReceive', + paymentReceive.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited payment. + * @param {IPaymentReceiveEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnPaymentEdited({ + tenantId, + paymentReceiveDTO, + oldPaymentReceive, + }: IPaymentReceiveEditedPayload) { + const keys = paymentReceiveDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'PaymentReceive', + oldPaymentReceive.id + ); + } + + /** + * Handles linking all the presented keys of the edited payment. + * @param {IPaymentReceiveEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnPaymentEdited({ + tenantId, + paymentReceiveDTO, + oldPaymentReceive, + trx, + }: IPaymentReceiveEditedPayload) { + if (isEmpty(paymentReceiveDTO.attachments)) return; + + const keys = paymentReceiveDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'PaymentReceive', + oldPaymentReceive.id, + trx + ); + } + + /** + * Unlink all attachments once the payment deleted. + * @param {ISaleInvoiceDeletedPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnPaymentDelete({ + tenantId, + oldPaymentReceive, + trx, + }: IPaymentReceiveDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'PaymentReceive', + oldPaymentReceive.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnSaleEstimates.ts b/packages/server/src/services/Attachments/events/AttachmentsOnSaleEstimates.ts new file mode 100644 index 000000000..d37827228 --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnSaleEstimates.ts @@ -0,0 +1,152 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + ISaleEstimateCreatedPayload, + ISaleEstimateCreatingPayload, + ISaleEstimateDeletingPayload, + ISaleEstimateEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnSaleEstimates { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.saleEstimate.onCreating, + this.validateAttachmentsOnSaleEstimateCreated.bind(this) + ); + bus.subscribe( + events.saleEstimate.onCreated, + this.handleAttachmentsOnSaleEstimateCreated.bind(this) + ); + bus.subscribe( + events.saleEstimate.onEdited, + this.handleUnlinkUnpresentedKeysOnSaleEstimateEdited.bind(this) + ); + bus.subscribe( + events.saleEstimate.onEdited, + this.handleLinkPresentedKeysOnSaleEstimateEdited.bind(this) + ); + bus.subscribe( + events.saleEstimate.onDeleting, + this.handleUnlinkAttachmentsOnSaleEstimateDelete.bind(this) + ); + } + + /** + * Validates the attachment keys on creating sale estimate. + * @param {ISaleEstimateCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnSaleEstimateCreated({ + estimateDTO, + tenantId, + }: ISaleEstimateCreatingPayload): Promise { + if (isEmpty(estimateDTO.attachments)) { + return; + } + const documentKeys = estimateDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created sale estimate. + * @param {ISaleEstimateCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnSaleEstimateCreated({ + tenantId, + saleEstimateDTO, + saleEstimate, + trx, + }: ISaleEstimateCreatedPayload): Promise { + if (isEmpty(saleEstimateDTO.attachments)) return; + + const keys = saleEstimateDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'SaleEstimate', + saleEstimate.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited sale estimate. + * @param {ISaleEstimateEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnSaleEstimateEdited({ + tenantId, + estimateDTO, + oldSaleEstimate, + }: ISaleEstimateEditedPayload) { + const keys = estimateDTO.attachments?.map((attachment) => attachment.key); + + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'SaleEstimate', + oldSaleEstimate.id + ); + } + + /** + * Handles linking all the presented keys of the edited sale estimate. + * @param {ISaleEstimateEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnSaleEstimateEdited({ + tenantId, + estimateDTO, + oldSaleEstimate, + trx, + }: ISaleEstimateEditedPayload) { + if (isEmpty(estimateDTO.attachments)) return; + + const keys = estimateDTO.attachments?.map((attachment) => attachment.key); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'SaleEstimate', + oldSaleEstimate.id, + trx + ); + } + + /** + * Unlink all attachments once the estimate deleted. + * @param {ISaleEstimateDeletingPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnSaleEstimateDelete({ + tenantId, + oldSaleEstimate, + trx, + }: ISaleEstimateDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'SaleEstimate', + oldSaleEstimate.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts b/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts new file mode 100644 index 000000000..2394af21e --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnSaleInvoice.ts @@ -0,0 +1,157 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + ISaleInvoiceCreatedPayload, + ISaleInvoiceCreatingPaylaod, + ISaleInvoiceDeletePayload, + ISaleInvoiceEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnSaleInvoiceCreated { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.saleInvoice.onCreating, + this.validateAttachmentsOnSaleInvoiceCreate.bind(this) + ); + bus.subscribe( + events.saleInvoice.onCreated, + this.handleAttachmentsOnSaleInvoiceCreated.bind(this) + ); + bus.subscribe( + events.saleInvoice.onEdited, + this.handleUnlinkUnpresentedKeysOnInvoiceEdited.bind(this) + ); + bus.subscribe( + events.saleInvoice.onEdited, + this.handleLinkPresentedKeysOnInvoiceEdited.bind(this) + ); + bus.subscribe( + events.saleInvoice.onDeleting, + this.handleUnlinkAttachmentsOnInvoiceDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating sale invoice. + * @param {ISaleInvoiceCreatingPaylaod} + * @returns {Promise} + */ + private async validateAttachmentsOnSaleInvoiceCreate({ + saleInvoiceDTO, + tenantId, + }: ISaleInvoiceCreatingPaylaod): Promise { + if (isEmpty(saleInvoiceDTO.attachments)) { + return; + } + const documentKeys = saleInvoiceDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created sale invoice. + * @param {ISaleInvoiceCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnSaleInvoiceCreated({ + tenantId, + saleInvoiceDTO, + saleInvoice, + trx, + }: ISaleInvoiceCreatedPayload): Promise { + if (isEmpty(saleInvoiceDTO.attachments)) return; + + const keys = saleInvoiceDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'SaleInvoice', + saleInvoice.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited sale invoice. + * @param {ISaleInvoiceEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnInvoiceEdited({ + tenantId, + saleInvoiceDTO, + saleInvoice, + }: ISaleInvoiceEditedPayload) { + // if (isEmpty(saleInvoiceDTO.attachments)) return; + + const keys = saleInvoiceDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'SaleInvoice', + saleInvoice.id + ); + } + + /** + * Handles linking all the presented keys of the edited sale invoice. + * @param {ISaleInvoiceEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnInvoiceEdited({ + tenantId, + saleInvoiceDTO, + oldSaleInvoice, + trx, + }: ISaleInvoiceEditedPayload) { + if (isEmpty(saleInvoiceDTO.attachments)) return; + + const keys = saleInvoiceDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'SaleInvoice', + oldSaleInvoice.id, + trx + ); + } + + /** + * Unlink all attachments once the invoice deleted. + * @param {ISaleInvoiceDeletedPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnInvoiceDeleted({ + tenantId, + saleInvoice, + trx, + }: ISaleInvoiceDeletePayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'SaleInvoice', + saleInvoice.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnSaleReceipts.ts b/packages/server/src/services/Attachments/events/AttachmentsOnSaleReceipts.ts new file mode 100644 index 000000000..795fc94fd --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnSaleReceipts.ts @@ -0,0 +1,155 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + ISaleReceiptCreatedPayload, + ISaleReceiptCreatingPayload, + ISaleReceiptDeletingPayload, + ISaleReceiptEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnSaleReceipt { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.saleReceipt.onCreating, + this.validateAttachmentsOnSaleInvoiceCreate.bind(this) + ); + bus.subscribe( + events.saleReceipt.onCreated, + this.handleAttachmentsOnSaleInvoiceCreated.bind(this) + ); + bus.subscribe( + events.saleReceipt.onEdited, + this.handleUnlinkUnpresentedKeysOnInvoiceEdited.bind(this) + ); + bus.subscribe( + events.saleReceipt.onEdited, + this.handleLinkPresentedKeysOnInvoiceEdited.bind(this) + ); + bus.subscribe( + events.saleReceipt.onDeleting, + this.handleUnlinkAttachmentsOnReceiptDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating sale receipt. + * @param {ISaleReceiptCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnSaleInvoiceCreate({ + saleReceiptDTO, + tenantId, + }: ISaleReceiptCreatingPayload): Promise { + if (isEmpty(saleReceiptDTO.attachments)) { + return; + } + const documentKeys = saleReceiptDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created sale receipt. + * @param {ISaleReceiptCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnSaleInvoiceCreated({ + tenantId, + saleReceiptDTO, + saleReceipt, + trx, + }: ISaleReceiptCreatedPayload): Promise { + if (isEmpty(saleReceiptDTO.attachments)) return; + + const keys = saleReceiptDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'SaleReceipt', + saleReceipt.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited sale receipt. + * @param {ISaleReceiptEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnInvoiceEdited({ + tenantId, + saleReceiptDTO, + saleReceipt, + }: ISaleReceiptEditedPayload) { + const keys = saleReceiptDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'SaleReceipt', + saleReceipt.id + ); + } + + /** + * Handles linking all the presented keys of the edited sale receipt. + * @param {ISaleReceiptEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnInvoiceEdited({ + tenantId, + saleReceiptDTO, + oldSaleReceipt, + trx, + }: ISaleReceiptEditedPayload) { + if (isEmpty(saleReceiptDTO.attachments)) return; + + const keys = saleReceiptDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'SaleReceipt', + oldSaleReceipt.id, + trx + ); + } + + /** + * Unlink all attachments once the receipt deleted. + * @param {ISaleReceiptDeletingPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnReceiptDeleted({ + tenantId, + oldSaleReceipt, + trx, + }: ISaleReceiptDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'SaleReceipt', + oldSaleReceipt.id, + trx + ); + } +} diff --git a/packages/server/src/services/Attachments/events/AttachmentsOnVendorCredits.ts b/packages/server/src/services/Attachments/events/AttachmentsOnVendorCredits.ts new file mode 100644 index 000000000..1295baa6b --- /dev/null +++ b/packages/server/src/services/Attachments/events/AttachmentsOnVendorCredits.ts @@ -0,0 +1,155 @@ +import { Inject, Service } from 'typedi'; +import { isEmpty } from 'lodash'; +import { + IVendorCreditCreatedPayload, + IVendorCreditCreatingPayload, + IVendorCreditDeletingPayload, + IVendorCreditEditedPayload, +} from '@/interfaces'; +import events from '@/subscribers/events'; +import { LinkAttachment } from '../LinkAttachment'; +import { ValidateAttachments } from '../ValidateAttachments'; +import { UnlinkAttachment } from '../UnlinkAttachment'; + +@Service() +export class AttachmentsOnVendorCredits { + @Inject() + private linkAttachmentService: LinkAttachment; + + @Inject() + private unlinkAttachmentService: UnlinkAttachment; + + @Inject() + private validateDocuments: ValidateAttachments; + + /** + * Constructor method. + */ + public attach(bus) { + bus.subscribe( + events.saleInvoice.onCreating, + this.validateAttachmentsOnVendorCreditCreate.bind(this) + ); + bus.subscribe( + events.saleInvoice.onCreated, + this.handleAttachmentsOnVendorCreditCreated.bind(this) + ); + bus.subscribe( + events.saleInvoice.onEdited, + this.handleUnlinkUnpresentedKeysOnVendorCreditEdited.bind(this) + ); + bus.subscribe( + events.saleInvoice.onEdited, + this.handleLinkPresentedKeysOnVendorCreditEdited.bind(this) + ); + bus.subscribe( + events.saleInvoice.onDeleting, + this.handleUnlinkAttachmentsOnVendorCreditDeleted.bind(this) + ); + } + + /** + * Validates the attachment keys on creating vendor credit. + * @param {IVendorCreditCreatingPayload} + * @returns {Promise} + */ + private async validateAttachmentsOnVendorCreditCreate({ + vendorCreditCreateDTO, + tenantId, + }: IVendorCreditCreatingPayload): Promise { + if (isEmpty(vendorCreditCreateDTO.attachments)) { + return; + } + const documentKeys = vendorCreditCreateDTO?.attachments?.map((a) => a.key); + + await this.validateDocuments.validate(tenantId, documentKeys); + } + + /** + * Handles linking the attachments of the created vendor credit. + * @param {IVendorCreditCreatedPayload} + * @returns {Promise} + */ + private async handleAttachmentsOnVendorCreditCreated({ + tenantId, + vendorCreditCreateDTO, + vendorCredit, + trx, + }: IVendorCreditCreatedPayload): Promise { + if (isEmpty(vendorCreditCreateDTO.attachments)) return; + + const keys = vendorCreditCreateDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'VendorCredit', + vendorCredit.id, + trx + ); + } + + /** + * Handles unlinking all the unpresented keys of the edited vendor credit. + * @param {IVendorCreditEditedPayload} + */ + private async handleUnlinkUnpresentedKeysOnVendorCreditEdited({ + tenantId, + vendorCreditDTO, + oldVendorCredit, + }: IVendorCreditEditedPayload) { + const keys = vendorCreditDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.unlinkAttachmentService.unlinkUnpresentedKeys( + tenantId, + keys, + 'VendorCredit', + oldVendorCredit.id + ); + } + + /** + * Handles linking all the presented keys of the edited vendor credit. + * @param {IVendorCreditEditedPayload} + * @returns {Promise} + */ + private async handleLinkPresentedKeysOnVendorCreditEdited({ + tenantId, + vendorCreditDTO, + oldVendorCredit, + trx, + }: IVendorCreditEditedPayload) { + if (isEmpty(vendorCreditDTO.attachments)) return; + + const keys = vendorCreditDTO.attachments?.map( + (attachment) => attachment.key + ); + await this.linkAttachmentService.bulkLink( + tenantId, + keys, + 'VendorCredit', + oldVendorCredit.id, + trx + ); + } + + /** + * Unlink all attachments once the vendor credit deleted. + * @param {IVendorCreditDeletingPayload} + * @returns {Promise} + */ + private async handleUnlinkAttachmentsOnVendorCreditDeleted({ + tenantId, + oldVendorCredit, + trx, + }: IVendorCreditDeletingPayload) { + await this.unlinkAttachmentService.unlinkAllModelKeys( + tenantId, + 'VendorCredit', + oldVendorCredit.id, + trx + ); + } +} diff --git a/packages/server/src/services/CreditNotes/CreditNotes.ts b/packages/server/src/services/CreditNotes/CreditNotes.ts index f5485ad08..6fedf3245 100644 --- a/packages/server/src/services/CreditNotes/CreditNotes.ts +++ b/packages/server/src/services/CreditNotes/CreditNotes.ts @@ -57,7 +57,7 @@ export default class BaseCreditNotes { autoNextNumber; const initialDTO = { - ...omit(creditNoteDTO, ['open']), + ...omit(creditNoteDTO, ['open', 'attachments']), creditNoteNumber, amount, currencyCode: customerCurrencyCode, diff --git a/packages/server/src/services/Expenses/CRUD/CreateExpense.ts b/packages/server/src/services/Expenses/CRUD/CreateExpense.ts index 8d002f9b0..a342daed7 100644 --- a/packages/server/src/services/Expenses/CRUD/CreateExpense.ts +++ b/packages/server/src/services/Expenses/CRUD/CreateExpense.ts @@ -123,6 +123,7 @@ export class CreateExpense { tenantId, expenseId: expense.id, authorizedUser, + expenseDTO, expense, trx, } as IExpenseCreatedPayload); diff --git a/packages/server/src/services/Expenses/CRUD/ExpenseDTOTransformer.ts b/packages/server/src/services/Expenses/CRUD/ExpenseDTOTransformer.ts index 9357df7c5..d53148e92 100644 --- a/packages/server/src/services/Expenses/CRUD/ExpenseDTOTransformer.ts +++ b/packages/server/src/services/Expenses/CRUD/ExpenseDTOTransformer.ts @@ -54,7 +54,7 @@ export class ExpenseDTOTransformer { const initialDTO = { categories: [], - ...omit(expenseDTO, ['publish']), + ...omit(expenseDTO, ['publish', 'attachments']), totalAmount, landedCostAmount, paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(), diff --git a/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts b/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts index 89f461934..92caf0959 100644 --- a/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts +++ b/packages/server/src/services/Expenses/CRUD/ExpenseTransformer.ts @@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer'; import { formatNumber } from 'utils'; import { IExpense } from '@/interfaces'; import { ExpenseCategoryTransformer } from './ExpenseCategoryTransformer'; +import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer'; export class ExpenseTransfromer extends Transformer { /** @@ -15,6 +16,7 @@ export class ExpenseTransfromer extends Transformer { 'formattedAllocatedCostAmount', 'formattedDate', 'categories', + 'attachments', ]; }; @@ -70,4 +72,13 @@ export class ExpenseTransfromer extends Transformer { currencyCode: expense.currencyCode, }); }; + + /** + * Retrieves the sale invoice attachments. + * @param {ISaleInvoice} invoice + * @returns + */ + protected attachments = (expense: IExpense) => { + return this.item(expense.attachments, new AttachmentTransformer()); + }; } diff --git a/packages/server/src/services/Expenses/CRUD/GetExpense.ts b/packages/server/src/services/Expenses/CRUD/GetExpense.ts index 2203d4e13..e8961ecca 100644 --- a/packages/server/src/services/Expenses/CRUD/GetExpense.ts +++ b/packages/server/src/services/Expenses/CRUD/GetExpense.ts @@ -29,6 +29,7 @@ export class GetExpense { .withGraphFetched('categories.expenseAccount') .withGraphFetched('paymentAccount') .withGraphFetched('branch') + .withGraphFetched('attachments') .throwIfNotFound(); // Transformes expense model to POJO. diff --git a/packages/server/src/services/ManualJournals/CreateManualJournal.ts b/packages/server/src/services/ManualJournals/CreateManualJournal.ts index 3212e1d1d..513f8d642 100644 --- a/packages/server/src/services/ManualJournals/CreateManualJournal.ts +++ b/packages/server/src/services/ManualJournals/CreateManualJournal.ts @@ -12,7 +12,7 @@ import { } from '@/interfaces'; import TenancyService from '@/services/Tenancy/TenancyService'; import events from '@/subscribers/events'; -import { Tenant, TenantMetadata } from '@/system/models'; +import { TenantMetadata } from '@/system/models'; import UnitOfWork from '@/services/UnitOfWork'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { CommandManualJournalValidators } from './CommandManualJournalValidators'; @@ -59,7 +59,7 @@ export class CreateManualJournalService { const journalNumber = manualJournalDTO.journalNumber || autoNextNumber; const initialDTO = { - ...omit(manualJournalDTO, ['publish']), + ...omit(manualJournalDTO, ['publish', 'attachments']), ...(manualJournalDTO.publish ? { publishedAt: moment().toMySqlDateTime() } : {}), @@ -173,6 +173,7 @@ export class CreateManualJournalService { tenantId, manualJournal, manualJournalId: manualJournal.id, + manualJournalDTO, trx, } as IManualJournalEventCreatedPayload); diff --git a/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts index 81eb3753c..057b330ed 100644 --- a/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/CreateBillPayment.ts @@ -118,6 +118,7 @@ export class CreateBillPayment { tenantId, billPayment, billPaymentId: billPayment.id, + billPaymentDTO, trx, } as IBillPaymentEventCreatedPayload); diff --git a/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts b/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts index de18853bb..78141b035 100644 --- a/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts +++ b/packages/server/src/services/Purchases/BillPayments/EditBillPayment.ts @@ -137,6 +137,7 @@ export class EditBillPayment { billPaymentId, billPayment, oldBillPayment, + billPaymentDTO, trx, } as IBillPaymentEventEditedPayload); diff --git a/packages/server/src/services/Purchases/Bills/BillDTOTransformer.ts b/packages/server/src/services/Purchases/Bills/BillDTOTransformer.ts index fed2c249e..50808340d 100644 --- a/packages/server/src/services/Purchases/Bills/BillDTOTransformer.ts +++ b/packages/server/src/services/Purchases/Bills/BillDTOTransformer.ts @@ -96,7 +96,7 @@ export class BillDTOTransformer { )(asyncEntries); const initialDTO = { - ...formatDateFields(omit(billDTO, ['open', 'entries']), [ + ...formatDateFields(omit(billDTO, ['open', 'entries', 'attachments']), [ 'billDate', 'dueDate', ]), diff --git a/packages/server/src/services/Purchases/Bills/CreateBill.ts b/packages/server/src/services/Purchases/Bills/CreateBill.ts index 98faea3c5..2bb317ff6 100644 --- a/packages/server/src/services/Purchases/Bills/CreateBill.ts +++ b/packages/server/src/services/Purchases/Bills/CreateBill.ts @@ -110,6 +110,7 @@ export class CreateBill { tenantId, bill, billId: bill.id, + billDTO, trx, } as IBillCreatedPayload); diff --git a/packages/server/src/services/Purchases/Bills/EditBill.ts b/packages/server/src/services/Purchases/Bills/EditBill.ts index 7b10bb7f1..4bb61f4f5 100644 --- a/packages/server/src/services/Purchases/Bills/EditBill.ts +++ b/packages/server/src/services/Purchases/Bills/EditBill.ts @@ -148,6 +148,7 @@ export class EditBill { billId, oldBill, bill, + billDTO, trx, } as IBillEditedPayload); diff --git a/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts b/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts index a0ff0f421..1ec313852 100644 --- a/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts +++ b/packages/server/src/services/Purchases/VendorCredits/EditVendorCredit.ts @@ -93,6 +93,7 @@ export default class EditVendorCredit extends BaseVendorCredit { oldVendorCredit, vendorCredit, vendorCreditId, + vendorCreditDTO, trx, } as IVendorCreditEditedPayload); diff --git a/packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts b/packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts index 5b2323a23..bbc73902c 100644 --- a/packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts +++ b/packages/server/src/services/Sales/Estimates/EditSaleEstimate.ts @@ -114,6 +114,7 @@ export class EditSaleEstimate { estimateId, saleEstimate, oldSaleEstimate, + estimateDTO, trx, } as ISaleEstimateEditedPayload); diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts b/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts index 967edcb63..df982c816 100644 --- a/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts +++ b/packages/server/src/services/Sales/Estimates/SaleEstimateDTOTransformer.ts @@ -58,10 +58,10 @@ export class SaleEstimateDTOTransformer { const initialDTO = { amount, - ...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [ - 'estimateDate', - 'expirationDate', - ]), + ...formatDateFields( + omit(estimateDTO, ['delivered', 'entries', 'attachments']), + ['estimateDate', 'expirationDate'] + ), currencyCode: paymentCustomer.currencyCode, exchangeRate: estimateDTO.exchangeRate || 1, ...(estimateNumber ? { estimateNumber } : {}), diff --git a/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts b/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts index e6f4c054e..4a4f5f6ea 100644 --- a/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts @@ -86,7 +86,12 @@ export class CommandSaleInvoiceDTOTransformer { const initialDTO = { ...formatDateFields( - omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']), + omit(saleInvoiceDTO, [ + 'delivered', + 'entries', + 'fromEstimateId', + 'attachments', + ]), ['invoiceDate', 'dueDate'] ), // Avoid rewrite the deliver date in edit mode when already published. diff --git a/packages/server/src/services/Sales/Invoices/GetSaleInvoice.ts b/packages/server/src/services/Sales/Invoices/GetSaleInvoice.ts index b57f86ed9..f788354e0 100644 --- a/packages/server/src/services/Sales/Invoices/GetSaleInvoice.ts +++ b/packages/server/src/services/Sales/Invoices/GetSaleInvoice.ts @@ -34,7 +34,8 @@ export class GetSaleInvoice { .withGraphFetched('entries.tax') .withGraphFetched('customer') .withGraphFetched('branch') - .withGraphFetched('taxes.taxRate'); + .withGraphFetched('taxes.taxRate') + .withGraphFetched('attachments'); // Validates the given sale invoice existance. this.validators.validateInvoiceExistance(saleInvoice); diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts b/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts index 878160b96..4d3b9d2c2 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts @@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer'; import { formatNumber } from 'utils'; import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer'; import { ItemEntryTransformer } from './ItemEntryTransformer'; +import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer'; export class SaleInvoiceTransformer extends Transformer { /** @@ -25,6 +26,7 @@ export class SaleInvoiceTransformer extends Transformer { 'totalLocalFormatted', 'taxes', 'entries', + 'attachments', ]; }; @@ -190,4 +192,13 @@ export class SaleInvoiceTransformer extends Transformer { currencyCode: invoice.currencyCode, }); }; + + /** + * Retrieves the sale invoice attachments. + * @param {ISaleInvoice} invoice + * @returns + */ + protected attachments = (invoice) => { + return this.item(invoice.attachments, new AttachmentTransformer()); + }; } diff --git a/packages/server/src/services/Sales/PaymentReceives/CreatePaymentReceive.ts b/packages/server/src/services/Sales/PaymentReceives/CreatePaymentReceive.ts index 4e0708cbf..7649177d3 100644 --- a/packages/server/src/services/Sales/PaymentReceives/CreatePaymentReceive.ts +++ b/packages/server/src/services/Sales/PaymentReceives/CreatePaymentReceive.ts @@ -110,6 +110,7 @@ export class CreatePaymentReceive { tenantId, paymentReceive, paymentReceiveId: paymentReceive.id, + paymentReceiveDTO, authorizedUser, trx, } as IPaymentReceiveCreatedPayload);