feat: wip attach attachments to resource models

This commit is contained in:
Ahmed Bouhuolia
2024-05-26 21:59:39 +02:00
parent 15dbc4137c
commit 2244cc6116
64 changed files with 2052 additions and 54 deletions

View File

@@ -84,7 +84,7 @@ export class ExpensesController extends BaseController {
/** /**
* Expense DTO schema. * Expense DTO schema.
*/ */
get expenseDTOSchema() { private get expenseDTOSchema() {
return [ return [
check('reference_no') check('reference_no')
.optional({ nullable: true }) .optional({ nullable: true })
@@ -130,6 +130,9 @@ export class ExpensesController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isInt({ max: DATATYPES_LENGTH.INT_10 }) .isInt({ max: DATATYPES_LENGTH.INT_10 })
.toInt(), .toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }
@@ -183,6 +186,9 @@ export class ExpensesController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isInt({ max: DATATYPES_LENGTH.INT_10 }) .isInt({ max: DATATYPES_LENGTH.INT_10 })
.toInt(), .toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }
@@ -269,7 +275,7 @@ export class ExpensesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @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 { tenantId, user } = req;
const { id: expenseId } = req.params; const { id: expenseId } = req.params;
@@ -291,7 +297,11 @@ export class ExpensesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @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 { tenantId, user } = req;
const { id: expenseId } = req.params; const { id: expenseId } = req.params;
@@ -313,7 +323,11 @@ export class ExpensesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
async getExpensesList(req: Request, res: Response, next: NextFunction) { private async getExpensesList(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const filter = { const filter = {
sortOrder: 'desc', sortOrder: 'desc',
@@ -343,7 +357,7 @@ export class ExpensesController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
async getExpense(req: Request, res: Response, next: NextFunction) { private async getExpense(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const { id: expenseId } = req.params; const { id: expenseId } = req.params;

View File

@@ -148,6 +148,9 @@ export default class ManualJournalsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }

View File

@@ -118,7 +118,6 @@ export default class BillsController extends BaseController {
check('is_inclusive_tax').default(false).isBoolean().toBoolean(), check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
check('entries').isArray({ min: 1 }), check('entries').isArray({ min: 1 }),
check('entries.*.index').exists().isNumeric().toInt(), check('entries.*.index').exists().isNumeric().toInt(),
check('entries.*.item_id').exists().isNumeric().toInt(), check('entries.*.item_id').exists().isNumeric().toInt(),
check('entries.*.rate').exists().isNumeric().toFloat(), check('entries.*.rate').exists().isNumeric().toFloat(),
@@ -148,6 +147,9 @@ export default class BillsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }
@@ -190,6 +192,9 @@ export default class BillsController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isBoolean() .isBoolean()
.toBoolean(), .toBoolean(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }

View File

@@ -122,6 +122,9 @@ export default class BillsPayments extends BaseController {
check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.bill_id').exists().isNumeric().toInt(), check('entries.*.bill_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(), check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }

View File

@@ -186,6 +186,9 @@ export default class VendorCreditController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }
@@ -228,6 +231,9 @@ export default class VendorCreditController extends BaseController {
.optional({ nullable: true }) .optional({ nullable: true })
.isNumeric() .isNumeric()
.toInt(), .toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }

View File

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

View File

@@ -164,6 +164,9 @@ export default class PaymentReceivesController extends BaseController {
check('entries.*.index').optional().isNumeric().toInt(), check('entries.*.index').optional().isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(), check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toFloat(), check('entries.*.payment_amount').exists().isNumeric().toFloat(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }

View File

@@ -184,6 +184,9 @@ export default class SalesEstimatesController extends BaseController {
check('note').optional().trim().escape(), check('note').optional().trim().escape(),
check('terms_conditions').optional().trim().escape(), check('terms_conditions').optional().trim().escape(),
check('send_to_email').optional().trim().escape(), check('send_to_email').optional().trim().escape(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
]; ];
} }

View File

@@ -36,6 +36,8 @@ export default class SaleInvoicesController extends BaseController {
[ [
...this.saleInvoiceValidationSchema, ...this.saleInvoiceValidationSchema,
check('from_estimate_id').optional().isNumeric().toInt(), check('from_estimate_id').optional().isNumeric().toInt(),
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
], ],
this.validationResult, this.validationResult,
asyncMiddleware(this.newSaleInvoice.bind(this)), asyncMiddleware(this.newSaleInvoice.bind(this)),
@@ -98,6 +100,8 @@ export default class SaleInvoicesController extends BaseController {
[ [
...this.saleInvoiceValidationSchema, ...this.saleInvoiceValidationSchema,
...this.specificSaleInvoiceValidation, ...this.specificSaleInvoiceValidation,
check('attachments').isArray().optional(),
check('attachments.*.key').exists().isString(),
], ],
this.validationResult, this.validationResult,
asyncMiddleware(this.editSaleInvoice.bind(this)), asyncMiddleware(this.editSaleInvoice.bind(this)),

View File

@@ -4,6 +4,7 @@ exports.up = function (knex) {
table.string('model_ref').notNullable(); table.string('model_ref').notNullable();
table.string('model_id').notNullable(); table.string('model_id').notNullable();
table.integer('document_id').unsigned(); table.integer('document_id').unsigned();
table.datetime('expires_at').nullable();
table.timestamps(); table.timestamps();
}); });
}; };

View File

@@ -0,0 +1,3 @@
export interface AttachmentLinkDTO {
key: string;
}

View File

@@ -2,6 +2,7 @@ import { Knex } from 'knex';
import { IDynamicListFilterDTO } from './DynamicFilter'; import { IDynamicListFilterDTO } from './DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { IBillLandedCost } from './LandedCost'; import { IBillLandedCost } from './LandedCost';
import { AttachmentLinkDTO } from './Attachments';
export interface IBillDTO { export interface IBillDTO {
vendorId: number; vendorId: number;
@@ -20,6 +21,7 @@ export interface IBillDTO {
warehouseId?: number; warehouseId?: number;
projectId?: number; projectId?: number;
isInclusiveTax?: boolean; isInclusiveTax?: boolean;
attachments?: AttachmentLinkDTO[];
} }
export interface IBillEditDTO { export interface IBillEditDTO {
@@ -38,6 +40,7 @@ export interface IBillEditDTO {
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
projectId?: number; projectId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface IBill { export interface IBill {
@@ -105,6 +108,7 @@ export interface IBillsService {
export interface IBillCreatedPayload { export interface IBillCreatedPayload {
tenantId: number; tenantId: number;
bill: IBill; bill: IBill;
billDTO: IBillDTO;
billId: number; billId: number;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
@@ -126,6 +130,7 @@ export interface IBillEditedPayload {
billId: number; billId: number;
oldBill: IBill; oldBill: IBill;
bill: IBill; bill: IBill;
billDTO: IBillDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -1,5 +1,6 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IBill } from './Bill'; import { IBill } from './Bill';
import { AttachmentLinkDTO } from './Attachments';
export interface IBillPaymentEntry { export interface IBillPaymentEntry {
id?: number; id?: number;
@@ -45,6 +46,7 @@ export interface IBillPaymentDTO {
reference: string; reference: string;
entries: IBillPaymentEntryDTO[]; entries: IBillPaymentEntryDTO[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface IBillReceivePageEntry { export interface IBillReceivePageEntry {
@@ -66,6 +68,7 @@ export interface IBillPaymentsService {
export interface IBillPaymentEventCreatedPayload { export interface IBillPaymentEventCreatedPayload {
tenantId: number; tenantId: number;
billPayment: IBillPayment; billPayment: IBillPayment;
billPaymentDTO: IBillPaymentDTO;
billPaymentId: number; billPaymentId: number;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
@@ -87,6 +90,7 @@ export interface IBillPaymentEventEditedPayload {
billPaymentId: number; billPaymentId: number;
billPayment: IBillPayment; billPayment: IBillPayment;
oldBillPayment: IBillPayment; oldBillPayment: IBillPayment;
billPaymentDTO: IBillPaymentDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -1,6 +1,7 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IDynamicListFilter, IItemEntry, IVendorCredit } from '@/interfaces'; import { IDynamicListFilter, IItemEntry, IVendorCredit } from '@/interfaces';
import { ILedgerEntry } from './Ledger'; import { ILedgerEntry } from './Ledger';
import { AttachmentLinkDTO } from './Attachments';
export interface ICreditNoteEntryNewDTO { export interface ICreditNoteEntryNewDTO {
index: number; index: number;
@@ -21,6 +22,7 @@ export interface ICreditNoteNewDTO {
entries: ICreditNoteEntryNewDTO[]; entries: ICreditNoteEntryNewDTO[];
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
attachments?: AttachmentLinkDTO[]
} }
export interface ICreditNoteEditDTO { export interface ICreditNoteEditDTO {
@@ -33,6 +35,7 @@ export interface ICreditNoteEditDTO {
entries: ICreditNoteEntryNewDTO[]; entries: ICreditNoteEntryNewDTO[];
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
attachments?: AttachmentLinkDTO[]
} }
export interface ICreditNoteEntry extends IItemEntry {} export interface ICreditNoteEntry extends IItemEntry {}

View File

@@ -2,6 +2,7 @@ import { Knex } from 'knex';
import { ISystemUser } from './User'; import { ISystemUser } from './User';
import { IFilterRole } from './DynamicFilter'; import { IFilterRole } from './DynamicFilter';
import { IAccount } from './Account'; import { IAccount } from './Account';
import { AttachmentLinkDTO } from './Attachments';
export interface IPaginationMeta { export interface IPaginationMeta {
total: number; total: number;
@@ -81,6 +82,7 @@ export interface IExpenseCommonDTO {
categories: IExpenseCategoryDTO[]; categories: IExpenseCategoryDTO[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface IExpenseCreateDTO extends IExpenseCommonDTO {} export interface IExpenseCreateDTO extends IExpenseCommonDTO {}
@@ -152,6 +154,7 @@ export interface IExpenseCreatedPayload {
expenseId: number; expenseId: number;
authorizedUser: ISystemUser; authorizedUser: ISystemUser;
expense: IExpense; expense: IExpense;
expenseDTO: IExpenseCreateDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -2,6 +2,7 @@ import { Knex } from 'knex';
import { IDynamicListFilterDTO } from './DynamicFilter'; import { IDynamicListFilterDTO } from './DynamicFilter';
import { ISystemUser } from './User'; import { ISystemUser } from './User';
import { IAccount } from './Account'; import { IAccount } from './Account';
import { AttachmentLinkDTO } from './Attachments';
export interface IManualJournal { export interface IManualJournal {
id?: number; id?: number;
@@ -56,6 +57,7 @@ export interface IManualJournalDTO {
publish?: boolean; publish?: boolean;
branchId?: number; branchId?: number;
entries: IManualJournalEntryDTO[]; entries: IManualJournalEntryDTO[];
attachments?: AttachmentLinkDTO[];
} }
export interface IManualJournalsFilter extends IDynamicListFilterDTO { export interface IManualJournalsFilter extends IDynamicListFilterDTO {
@@ -142,6 +144,7 @@ export interface IManualJournalEventEditedPayload {
tenantId: number; tenantId: number;
manualJournal: IManualJournal; manualJournal: IManualJournal;
oldManualJournal: IManualJournal; oldManualJournal: IManualJournal;
manualJournalDTO: IManualJournalDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
export interface IManualJournalEditingPayload { export interface IManualJournalEditingPayload {
@@ -161,6 +164,7 @@ export interface IManualJournalEventCreatedPayload {
tenantId: number; tenantId: number;
manualJournal: IManualJournal; manualJournal: IManualJournal;
manualJournalId: number; manualJournalId: number;
manualJournalDTO: IManualJournalDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -6,6 +6,7 @@ import {
} from '@/interfaces'; } from '@/interfaces';
import { ILedgerEntry } from './Ledger'; import { ILedgerEntry } from './Ledger';
import { ISaleInvoice } from './SaleInvoice'; import { ISaleInvoice } from './SaleInvoice';
import { AttachmentLinkDTO } from './Attachments';
export interface IPaymentReceive { export interface IPaymentReceive {
id?: number; id?: number;
@@ -37,6 +38,7 @@ export interface IPaymentReceiveCreateDTO {
entries: IPaymentReceiveEntryDTO[]; entries: IPaymentReceiveEntryDTO[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface IPaymentReceiveEditDTO { export interface IPaymentReceiveEditDTO {
@@ -50,6 +52,7 @@ export interface IPaymentReceiveEditDTO {
statement: string; statement: string;
entries: IPaymentReceiveEntryDTO[]; entries: IPaymentReceiveEntryDTO[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface IPaymentReceiveEntry { export interface IPaymentReceiveEntry {
@@ -114,6 +117,7 @@ export interface IPaymentReceiveCreatedPayload {
paymentReceive: IPaymentReceive; paymentReceive: IPaymentReceive;
paymentReceiveId: number; paymentReceiveId: number;
authorizedUser: ISystemUser; authorizedUser: ISystemUser;
paymentReceiveDTO: IPaymentReceiveCreateDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
@@ -122,6 +126,7 @@ export interface IPaymentReceiveEditedPayload {
paymentReceiveId: number; paymentReceiveId: number;
paymentReceive: IPaymentReceive; paymentReceive: IPaymentReceive;
oldPaymentReceive: IPaymentReceive; oldPaymentReceive: IPaymentReceive;
paymentReceiveDTO: IPaymentReceiveEditDTO;
authorizedUser: ISystemUser; authorizedUser: ISystemUser;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -2,6 +2,7 @@ import { Knex } from 'knex';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter'; import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
import { AttachmentLinkDTO } from './Attachments';
export interface ISaleEstimate { export interface ISaleEstimate {
id?: number; id?: number;
@@ -38,6 +39,7 @@ export interface ISaleEstimateDTO {
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface ISalesEstimatesFilter extends IDynamicListFilterDTO { export interface ISalesEstimatesFilter extends IDynamicListFilterDTO {
@@ -70,6 +72,7 @@ export interface ISaleEstimateEditedPayload {
estimateId: number; estimateId: number;
saleEstimate: ISaleEstimate; saleEstimate: ISaleEstimate;
oldSaleEstimate: ISaleEstimate; oldSaleEstimate: ISaleEstimate;
estimateDTO: ISaleEstimateDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -3,6 +3,7 @@ import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
import { IDynamicListFilter } from '@/interfaces/DynamicFilter'; import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry'; import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { AttachmentLinkDTO } from './Attachments';
export interface ISaleInvoice { export interface ISaleInvoice {
id: number; id: number;
@@ -64,6 +65,8 @@ export interface ISaleInvoiceDTO {
branchId?: number | null; branchId?: number | null;
isInclusiveTax?: boolean; isInclusiveTax?: boolean;
attachments?: AttachmentLinkDTO[];
} }
export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO { export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO {

View File

@@ -1,6 +1,7 @@
import { Knex } from 'knex'; import { Knex } from 'knex';
import { IItemEntry } from './ItemEntry'; import { IItemEntry } from './ItemEntry';
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable'; import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
import { AttachmentLinkDTO } from './Attachments';
export interface ISaleReceipt { export interface ISaleReceipt {
id?: number; id?: number;
@@ -43,6 +44,7 @@ export interface ISaleReceiptDTO {
closed: boolean; closed: boolean;
entries: any[]; entries: any[];
branchId?: number; branchId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface ISalesReceiptsService { export interface ISalesReceiptsService {
@@ -85,6 +87,7 @@ export interface ISaleReceiptCreatedPayload {
tenantId: number; tenantId: number;
saleReceipt: ISaleReceipt; saleReceipt: ISaleReceipt;
saleReceiptId: number; saleReceiptId: number;
saleReceiptDTO: ISaleReceiptDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }
@@ -93,6 +96,7 @@ export interface ISaleReceiptEditedPayload {
oldSaleReceipt: number; oldSaleReceipt: number;
saleReceipt: ISaleReceipt; saleReceipt: ISaleReceipt;
saleReceiptId: number; saleReceiptId: number;
saleReceiptDTO: ISaleReceiptDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -1,5 +1,6 @@
import { IDynamicListFilter, IItemEntry, IItemEntryDTO } from '@/interfaces'; import { IDynamicListFilter, IItemEntry, IItemEntryDTO } from '@/interfaces';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { AttachmentLinkDTO } from './Attachments';
export enum VendorCreditAction { export enum VendorCreditAction {
Create = 'Create', Create = 'Create',
@@ -61,6 +62,7 @@ export interface IVendorCreditDTO {
branchId?: number; branchId?: number;
warehouseId?: number; warehouseId?: number;
attachments?: AttachmentLinkDTO[];
} }
export interface IVendorCreditCreateDTO extends IVendorCreditDTO {} export interface IVendorCreditCreateDTO extends IVendorCreditDTO {}
@@ -118,6 +120,7 @@ export interface IVendorCreditEditedPayload {
oldVendorCredit: IVendorCredit; oldVendorCredit: IVendorCredit;
vendorCredit: IVendorCredit; vendorCredit: IVendorCredit;
vendorCreditId: number; vendorCreditId: number;
vendorCreditDTO: IVendorCreditEditDTO;
trx: Knex.Transaction; trx: Knex.Transaction;
} }

View File

@@ -92,7 +92,16 @@ import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/sub
import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete'; import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete';
import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity'; import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity';
import { SendVerfiyMailOnSignUp } from '@/services/Authentication/events/SendVerfiyMailOnSignUp'; 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 () => { export default () => {
return new EventPublisher(); return new EventPublisher();
@@ -224,6 +233,18 @@ export const susbcribers = () => {
PreventDeleteTransactionOnDelete, PreventDeleteTransactionOnDelete,
SubscribeFreeOnSignupCommunity, SubscribeFreeOnSignupCommunity,
SendVerfiyMailOnSignUp SendVerfiyMailOnSignUp,
// Attachments
AttachmentsOnSaleInvoiceCreated,
AttachmentsOnSaleEstimates,
AttachmentsOnSaleReceipt,
AttachmentsOnPaymentsReceived,
AttachmentsOnCreditNote,
AttachmentsOnVendorCredits,
AttachmentsOnBills,
AttachmentsOnBillPayments,
AttachmentsOnManualJournals,
AttachmentsOnExpenses,
]; ];
}; };

View File

@@ -403,6 +403,7 @@ export default class Bill extends mixin(TenantModel, [
const BillLandedCost = require('models/BillLandedCost'); const BillLandedCost = require('models/BillLandedCost');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const TaxRateTransaction = require('models/TaxRateTransaction'); const TaxRateTransaction = require('models/TaxRateTransaction');
const Document = require('models/Document');
return { return {
vendor: { vendor: {
@@ -465,6 +466,25 @@ export default class Bill extends mixin(TenantModel, [
builder.where('reference_type', 'Bill'); 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');
},
},
}; };
} }

View File

@@ -56,6 +56,7 @@ export default class BillPayment extends mixin(TenantModel, [
const Vendor = require('models/Vendor'); const Vendor = require('models/Vendor');
const Account = require('models/Account'); const Account = require('models/Account');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document');
return { return {
entries: { entries: {
@@ -114,6 +115,25 @@ export default class BillPayment extends mixin(TenantModel, [
to: 'branches.id', 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');
},
},
}; };
} }

View File

@@ -174,6 +174,7 @@ export default class CreditNote extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Customer = require('models/Customer'); const Customer = require('models/Customer');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document');
return { return {
/** /**
@@ -233,6 +234,25 @@ export default class CreditNote extends mixin(TenantModel, [
to: 'branches.id', 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');
},
},
}; };
} }

View File

@@ -180,7 +180,7 @@ export default class Expense extends mixin(TenantModel, [
static get relationMappings() { static get relationMappings() {
const Account = require('models/Account'); const Account = require('models/Account');
const ExpenseCategory = require('models/ExpenseCategory'); const ExpenseCategory = require('models/ExpenseCategory');
const Media = require('models/Media'); const Document = require('models/Document');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
return { return {
@@ -217,21 +217,21 @@ export default class Expense extends mixin(TenantModel, [
}, },
/** /**
* * Expense transaction may has many attached attachments.
*/ */
media: { attachments: {
relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
modelClass: Media.default, modelClass: Document.default,
join: { join: {
from: 'expenses_transactions.id', from: 'expenses_transactions.id',
through: { through: {
from: 'media_links.model_id', from: 'document_links.modelId',
to: 'media_links.media_id', to: 'document_links.documentId',
}, },
to: 'media.id', to: 'documents.id',
}, },
filter(query) { filter(query) {
query.where('model_name', 'Expense'); query.where('model_ref', 'Expense');
}, },
}, },
}; };

View File

@@ -94,9 +94,9 @@ export default class ManualJournal extends mixin(TenantModel, [
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const Media = require('models/Media');
const AccountTransaction = require('models/AccountTransaction'); const AccountTransaction = require('models/AccountTransaction');
const ManualJournalEntry = require('models/ManualJournalEntry'); const ManualJournalEntry = require('models/ManualJournalEntry');
const ManualJournal = require('models/ManualJournal');
return { return {
entries: { entries: {
@@ -121,19 +121,23 @@ export default class ManualJournal extends mixin(TenantModel, [
query.where('referenceType', 'Journal'); query.where('referenceType', 'Journal');
}, },
}, },
media: {
/**
* Manual journal may has many attached attachments.
*/
attachments: {
relation: Model.ManyToManyRelation, relation: Model.ManyToManyRelation,
modelClass: Media.default, modelClass: ManualJournal.default,
join: { join: {
from: 'manual_journals.id', from: 'manual_journals.id',
through: { through: {
from: 'media_links.model_id', from: 'document_links.modelId',
to: 'media_links.media_id', to: 'document_links.documentId',
}, },
to: 'media.id', to: 'documents.id',
}, },
filter(query) { filter(query) {
query.where('model_name', 'ManualJournal'); query.where('model_ref', 'ManualJournal');
}, },
}, },
}; };

View File

@@ -56,6 +56,7 @@ export default class PaymentReceive extends mixin(TenantModel, [
const Customer = require('models/Customer'); const Customer = require('models/Customer');
const Account = require('models/Account'); const Account = require('models/Account');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document');
return { return {
customer: { customer: {
@@ -111,6 +112,25 @@ export default class PaymentReceive extends mixin(TenantModel, [
to: 'branches.id', 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');
},
},
}; };
} }

View File

@@ -182,6 +182,7 @@ export default class SaleEstimate extends mixin(TenantModel, [
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Customer = require('models/Customer'); const Customer = require('models/Customer');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document');
return { return {
customer: { customer: {
@@ -219,6 +220,25 @@ export default class SaleEstimate extends mixin(TenantModel, [
to: 'branches.id', 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');
},
},
}; };
} }

View File

@@ -410,7 +410,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Account = require('models/Account'); const Account = require('models/Account');
const TaxRateTransaction = require('models/TaxRateTransaction'); const TaxRateTransaction = require('models/TaxRateTransaction');
const DocumentLink = require('models/DocumentLink'); const Document = require('models/Document');
return { 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: { attachments: {
relation: Model.HasManyRelation, relation: Model.ManyToManyRelation,
modelClass: DocumentLink.default, modelClass: Document.default,
join: { join: {
from: 'sales_invoices.id', from: 'sales_invoices.id',
to: 'document_links.modelId', through: {
from: 'document_links.modelId',
to: 'document_links.documentId',
},
to: 'documents.id',
}, },
filter: (builder) => { filter(query) {
builder.where('modelRef', 'SaleInvoice'); query.where('model_ref', 'Expense');
}, },
}, },
}; };

View File

@@ -108,6 +108,7 @@ export default class SaleReceipt extends mixin(TenantModel, [
const AccountTransaction = require('models/AccountTransaction'); const AccountTransaction = require('models/AccountTransaction');
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document');
return { return {
customer: { customer: {
@@ -167,6 +168,25 @@ export default class SaleReceipt extends mixin(TenantModel, [
to: 'branches.id', 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');
},
},
}; };
} }

View File

@@ -177,6 +177,7 @@ export default class VendorCredit extends mixin(TenantModel, [
const Vendor = require('models/Vendor'); const Vendor = require('models/Vendor');
const ItemEntry = require('models/ItemEntry'); const ItemEntry = require('models/ItemEntry');
const Branch = require('models/Branch'); const Branch = require('models/Branch');
const Document = require('models/Document');
return { return {
vendor: { vendor: {
@@ -215,6 +216,25 @@ export default class VendorCredit extends mixin(TenantModel, [
to: 'branches.id', 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');
},
},
}; };
} }

View File

@@ -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'];
};
}

View File

@@ -1,4 +1,6 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import bluebird from 'bluebird';
import { Knex } from 'knex';
import { import {
validateLinkModelEntryExists, validateLinkModelEntryExists,
validateLinkModelExists, validateLinkModelExists,
@@ -12,38 +14,68 @@ export class LinkAttachment {
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
/** /**
* * Links the given file key to the given model type and id.
* @param {number} tenantId * @param {number} tenantId
* @param {string} filekey * @param {string} filekey
* @param {string} modelRef * @param {string} modelRef
* @param {number} modelId * @param {number} modelId
* @returns {Promise<void>}
*/ */
async link( async link(
tenantId: number, tenantId: number,
filekey: string, filekey: string,
modelRef: string, modelRef: string,
modelId: number modelId: number,
trx?: Knex.Transaction
) { ) {
const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId); const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId);
const LinkModel = models[modelRef]; const LinkModel = models[modelRef];
validateLinkModelExists(LinkModel); validateLinkModelExists(LinkModel);
const foundLinkModel = await LinkModel.query().findById(modelId); const foundLinkModel = await LinkModel.query(trx).findById(modelId);
validateLinkModelEntryExists(foundLinkModel); validateLinkModelEntryExists(foundLinkModel);
const foundLinks = await DocumentLink.query() const foundLinks = await DocumentLink.query(trx)
.where('modelRef', modelRef) .where('modelRef', modelRef)
.where('modelId', modelId); .where('modelId', modelId);
if (foundLinks.length > 0) { 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, modelRef,
modelId, modelId,
documentId: foundFile.id, 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<void>}
*/
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;

View File

@@ -1,9 +1,12 @@
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import bluebird from 'bluebird';
import HasTenancyService from '../Tenancy/TenancyService'; import HasTenancyService from '../Tenancy/TenancyService';
import { import {
validateLinkModelEntryExists, validateLinkModelEntryExists,
validateLinkModelExists, validateLinkModelExists,
} from './_utils'; } from './_utils';
import { Knex } from 'knex';
import { difference } from 'lodash';
@Service() @Service()
export class UnlinkAttachment { export class UnlinkAttachment {
@@ -11,7 +14,7 @@ export class UnlinkAttachment {
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
/** /**
* * Unlink the attachments from the model entry.
* @param {number} tenantId * @param {number} tenantId
* @param {string} filekey * @param {string} filekey
* @param {string} modelRef * @param {string} modelRef
@@ -21,19 +24,105 @@ export class UnlinkAttachment {
tenantId: number, tenantId: number,
filekey: string, filekey: string,
modelRef: string, modelRef: string,
modelId: number modelId: number,
) { trx?: Knex.Transaction
const { DocumentLink, ...models } = this.tenancy.models(tenantId); ): Promise<void> {
const { DocumentLink, Document, ...models } = this.tenancy.models(tenantId);
const LinkModel = models[modelRef]; const LinkModel = models[modelRef];
validateLinkModelExists(LinkModel); validateLinkModelExists(LinkModel);
const foundLinkModel = await LinkModel.query().findById(modelId); const foundLinkModel = await LinkModel.query(trx).findById(modelId);
validateLinkModelEntryExists(foundLinkModel); validateLinkModelEntryExists(foundLinkModel);
const document = await Document.query().findOne('key', filekey);
// Delete the document link. // Delete the document link.
await DocumentLink.query() await DocumentLink.query(trx)
.where('modelRef', modelRef) .where('modelRef', modelRef)
.where('modelId', modelId) .where('modelId', modelId)
.where('documentId', document.id)
.delete(); .delete();
} }
/**
* Bulk unlink the attachments from the model entry.
* @param {number} tenantId
* @param {string} fieldkey
* @param {string} modelRef
* @param {number} modelId
* @returns {Promise<void>}
*/
async bulkUnlink(
tenantId: number,
filekeys: string[],
modelRef: string,
modelId: number,
trx?: Knex.Transaction
): Promise<void> {
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<void> {
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<void>}
*/
async unlinkAllModelKeys(
tenantId: number,
modelRef: string,
modelId: number,
trx?: Knex.Transaction
): Promise<void> {
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;

View File

@@ -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');
}
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnBillCreate({
billDTO,
tenantId,
}: IBillCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnBillCreated({
tenantId,
bill,
billDTO,
trx,
}: IBillCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnBillDeleted({
tenantId,
oldBill,
trx,
}: IBIllEventDeletedPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'Bill',
oldBill.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnCreditNoteCreate({
creditNoteDTO,
tenantId,
}: ICreditNoteCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnCreditNoteCreated({
tenantId,
creditNote,
creditNoteDTO,
trx,
}: ICreditNoteCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnCreditNoteDeleted({
tenantId,
oldCreditNote,
trx,
}: ICreditNoteDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'CreditNote',
oldCreditNote.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnExpenseCreate({
expenseDTO,
tenantId,
}: IExpenseCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnExpenseCreated({
tenantId,
expenseDTO,
expense,
trx,
}: IExpenseCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnExpenseDeleted({
tenantId,
oldExpense,
trx,
}: IExpenseDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'Expense',
oldExpense.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnManualJournalCreate({
manualJournalDTO,
tenantId,
}: IManualJournalCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnManualJournalCreated({
tenantId,
manualJournalDTO,
manualJournal,
trx,
}: IManualJournalEventCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnManualJournalDeleted({
tenantId,
oldManualJournal,
trx,
}: IManualJournalEventDeletedPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleInvoice',
oldManualJournal.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnBillPaymentCreate({
billPaymentDTO,
tenantId,
}: IBillPaymentCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnBillPaymentCreated({
tenantId,
billPaymentDTO,
billPayment,
trx,
}: IBillPaymentEventCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnBillPaymentDeleted({
tenantId,
oldBillPayment,
trx,
}: IBillPaymentDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'BillPayment',
oldBillPayment.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnPaymentCreate({
paymentReceiveDTO,
tenantId,
}: IPaymentReceiveCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnPaymentCreated({
tenantId,
paymentReceiveDTO,
paymentReceive,
trx,
}: IPaymentReceiveCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnPaymentDelete({
tenantId,
oldPaymentReceive,
trx,
}: IPaymentReceiveDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'PaymentReceive',
oldPaymentReceive.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnSaleEstimateCreated({
estimateDTO,
tenantId,
}: ISaleEstimateCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnSaleEstimateCreated({
tenantId,
saleEstimateDTO,
saleEstimate,
trx,
}: ISaleEstimateCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnSaleEstimateDelete({
tenantId,
oldSaleEstimate,
trx,
}: ISaleEstimateDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleEstimate',
oldSaleEstimate.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnSaleInvoiceCreate({
saleInvoiceDTO,
tenantId,
}: ISaleInvoiceCreatingPaylaod): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnSaleInvoiceCreated({
tenantId,
saleInvoiceDTO,
saleInvoice,
trx,
}: ISaleInvoiceCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnInvoiceDeleted({
tenantId,
saleInvoice,
trx,
}: ISaleInvoiceDeletePayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleInvoice',
saleInvoice.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnSaleInvoiceCreate({
saleReceiptDTO,
tenantId,
}: ISaleReceiptCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnSaleInvoiceCreated({
tenantId,
saleReceiptDTO,
saleReceipt,
trx,
}: ISaleReceiptCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnReceiptDeleted({
tenantId,
oldSaleReceipt,
trx,
}: ISaleReceiptDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'SaleReceipt',
oldSaleReceipt.id,
trx
);
}
}

View File

@@ -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<void>}
*/
private async validateAttachmentsOnVendorCreditCreate({
vendorCreditCreateDTO,
tenantId,
}: IVendorCreditCreatingPayload): Promise<void> {
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<void>}
*/
private async handleAttachmentsOnVendorCreditCreated({
tenantId,
vendorCreditCreateDTO,
vendorCredit,
trx,
}: IVendorCreditCreatedPayload): Promise<void> {
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<void>}
*/
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<void>}
*/
private async handleUnlinkAttachmentsOnVendorCreditDeleted({
tenantId,
oldVendorCredit,
trx,
}: IVendorCreditDeletingPayload) {
await this.unlinkAttachmentService.unlinkAllModelKeys(
tenantId,
'VendorCredit',
oldVendorCredit.id,
trx
);
}
}

View File

@@ -57,7 +57,7 @@ export default class BaseCreditNotes {
autoNextNumber; autoNextNumber;
const initialDTO = { const initialDTO = {
...omit(creditNoteDTO, ['open']), ...omit(creditNoteDTO, ['open', 'attachments']),
creditNoteNumber, creditNoteNumber,
amount, amount,
currencyCode: customerCurrencyCode, currencyCode: customerCurrencyCode,

View File

@@ -123,6 +123,7 @@ export class CreateExpense {
tenantId, tenantId,
expenseId: expense.id, expenseId: expense.id,
authorizedUser, authorizedUser,
expenseDTO,
expense, expense,
trx, trx,
} as IExpenseCreatedPayload); } as IExpenseCreatedPayload);

View File

@@ -54,7 +54,7 @@ export class ExpenseDTOTransformer {
const initialDTO = { const initialDTO = {
categories: [], categories: [],
...omit(expenseDTO, ['publish']), ...omit(expenseDTO, ['publish', 'attachments']),
totalAmount, totalAmount,
landedCostAmount, landedCostAmount,
paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(), paymentDate: moment(expenseDTO.paymentDate).toMySqlDateTime(),

View File

@@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils'; import { formatNumber } from 'utils';
import { IExpense } from '@/interfaces'; import { IExpense } from '@/interfaces';
import { ExpenseCategoryTransformer } from './ExpenseCategoryTransformer'; import { ExpenseCategoryTransformer } from './ExpenseCategoryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
export class ExpenseTransfromer extends Transformer { export class ExpenseTransfromer extends Transformer {
/** /**
@@ -15,6 +16,7 @@ export class ExpenseTransfromer extends Transformer {
'formattedAllocatedCostAmount', 'formattedAllocatedCostAmount',
'formattedDate', 'formattedDate',
'categories', 'categories',
'attachments',
]; ];
}; };
@@ -70,4 +72,13 @@ export class ExpenseTransfromer extends Transformer {
currencyCode: expense.currencyCode, currencyCode: expense.currencyCode,
}); });
}; };
/**
* Retrieves the sale invoice attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (expense: IExpense) => {
return this.item(expense.attachments, new AttachmentTransformer());
};
} }

View File

@@ -29,6 +29,7 @@ export class GetExpense {
.withGraphFetched('categories.expenseAccount') .withGraphFetched('categories.expenseAccount')
.withGraphFetched('paymentAccount') .withGraphFetched('paymentAccount')
.withGraphFetched('branch') .withGraphFetched('branch')
.withGraphFetched('attachments')
.throwIfNotFound(); .throwIfNotFound();
// Transformes expense model to POJO. // Transformes expense model to POJO.

View File

@@ -12,7 +12,7 @@ import {
} from '@/interfaces'; } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService'; import TenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { Tenant, TenantMetadata } from '@/system/models'; import { TenantMetadata } from '@/system/models';
import UnitOfWork from '@/services/UnitOfWork'; import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { CommandManualJournalValidators } from './CommandManualJournalValidators'; import { CommandManualJournalValidators } from './CommandManualJournalValidators';
@@ -59,7 +59,7 @@ export class CreateManualJournalService {
const journalNumber = manualJournalDTO.journalNumber || autoNextNumber; const journalNumber = manualJournalDTO.journalNumber || autoNextNumber;
const initialDTO = { const initialDTO = {
...omit(manualJournalDTO, ['publish']), ...omit(manualJournalDTO, ['publish', 'attachments']),
...(manualJournalDTO.publish ...(manualJournalDTO.publish
? { publishedAt: moment().toMySqlDateTime() } ? { publishedAt: moment().toMySqlDateTime() }
: {}), : {}),
@@ -173,6 +173,7 @@ export class CreateManualJournalService {
tenantId, tenantId,
manualJournal, manualJournal,
manualJournalId: manualJournal.id, manualJournalId: manualJournal.id,
manualJournalDTO,
trx, trx,
} as IManualJournalEventCreatedPayload); } as IManualJournalEventCreatedPayload);

View File

@@ -118,6 +118,7 @@ export class CreateBillPayment {
tenantId, tenantId,
billPayment, billPayment,
billPaymentId: billPayment.id, billPaymentId: billPayment.id,
billPaymentDTO,
trx, trx,
} as IBillPaymentEventCreatedPayload); } as IBillPaymentEventCreatedPayload);

View File

@@ -137,6 +137,7 @@ export class EditBillPayment {
billPaymentId, billPaymentId,
billPayment, billPayment,
oldBillPayment, oldBillPayment,
billPaymentDTO,
trx, trx,
} as IBillPaymentEventEditedPayload); } as IBillPaymentEventEditedPayload);

View File

@@ -96,7 +96,7 @@ export class BillDTOTransformer {
)(asyncEntries); )(asyncEntries);
const initialDTO = { const initialDTO = {
...formatDateFields(omit(billDTO, ['open', 'entries']), [ ...formatDateFields(omit(billDTO, ['open', 'entries', 'attachments']), [
'billDate', 'billDate',
'dueDate', 'dueDate',
]), ]),

View File

@@ -110,6 +110,7 @@ export class CreateBill {
tenantId, tenantId,
bill, bill,
billId: bill.id, billId: bill.id,
billDTO,
trx, trx,
} as IBillCreatedPayload); } as IBillCreatedPayload);

View File

@@ -148,6 +148,7 @@ export class EditBill {
billId, billId,
oldBill, oldBill,
bill, bill,
billDTO,
trx, trx,
} as IBillEditedPayload); } as IBillEditedPayload);

View File

@@ -93,6 +93,7 @@ export default class EditVendorCredit extends BaseVendorCredit {
oldVendorCredit, oldVendorCredit,
vendorCredit, vendorCredit,
vendorCreditId, vendorCreditId,
vendorCreditDTO,
trx, trx,
} as IVendorCreditEditedPayload); } as IVendorCreditEditedPayload);

View File

@@ -114,6 +114,7 @@ export class EditSaleEstimate {
estimateId, estimateId,
saleEstimate, saleEstimate,
oldSaleEstimate, oldSaleEstimate,
estimateDTO,
trx, trx,
} as ISaleEstimateEditedPayload); } as ISaleEstimateEditedPayload);

View File

@@ -58,10 +58,10 @@ export class SaleEstimateDTOTransformer {
const initialDTO = { const initialDTO = {
amount, amount,
...formatDateFields(omit(estimateDTO, ['delivered', 'entries']), [ ...formatDateFields(
'estimateDate', omit(estimateDTO, ['delivered', 'entries', 'attachments']),
'expirationDate', ['estimateDate', 'expirationDate']
]), ),
currencyCode: paymentCustomer.currencyCode, currencyCode: paymentCustomer.currencyCode,
exchangeRate: estimateDTO.exchangeRate || 1, exchangeRate: estimateDTO.exchangeRate || 1,
...(estimateNumber ? { estimateNumber } : {}), ...(estimateNumber ? { estimateNumber } : {}),

View File

@@ -86,7 +86,12 @@ export class CommandSaleInvoiceDTOTransformer {
const initialDTO = { const initialDTO = {
...formatDateFields( ...formatDateFields(
omit(saleInvoiceDTO, ['delivered', 'entries', 'fromEstimateId']), omit(saleInvoiceDTO, [
'delivered',
'entries',
'fromEstimateId',
'attachments',
]),
['invoiceDate', 'dueDate'] ['invoiceDate', 'dueDate']
), ),
// Avoid rewrite the deliver date in edit mode when already published. // Avoid rewrite the deliver date in edit mode when already published.

View File

@@ -34,7 +34,8 @@ export class GetSaleInvoice {
.withGraphFetched('entries.tax') .withGraphFetched('entries.tax')
.withGraphFetched('customer') .withGraphFetched('customer')
.withGraphFetched('branch') .withGraphFetched('branch')
.withGraphFetched('taxes.taxRate'); .withGraphFetched('taxes.taxRate')
.withGraphFetched('attachments');
// Validates the given sale invoice existance. // Validates the given sale invoice existance.
this.validators.validateInvoiceExistance(saleInvoice); this.validators.validateInvoiceExistance(saleInvoice);

View File

@@ -2,6 +2,7 @@ import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils'; import { formatNumber } from 'utils';
import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer'; import { SaleInvoiceTaxEntryTransformer } from './SaleInvoiceTaxEntryTransformer';
import { ItemEntryTransformer } from './ItemEntryTransformer'; import { ItemEntryTransformer } from './ItemEntryTransformer';
import { AttachmentTransformer } from '@/services/Attachments/AttachmentTransformer';
export class SaleInvoiceTransformer extends Transformer { export class SaleInvoiceTransformer extends Transformer {
/** /**
@@ -25,6 +26,7 @@ export class SaleInvoiceTransformer extends Transformer {
'totalLocalFormatted', 'totalLocalFormatted',
'taxes', 'taxes',
'entries', 'entries',
'attachments',
]; ];
}; };
@@ -190,4 +192,13 @@ export class SaleInvoiceTransformer extends Transformer {
currencyCode: invoice.currencyCode, currencyCode: invoice.currencyCode,
}); });
}; };
/**
* Retrieves the sale invoice attachments.
* @param {ISaleInvoice} invoice
* @returns
*/
protected attachments = (invoice) => {
return this.item(invoice.attachments, new AttachmentTransformer());
};
} }

View File

@@ -110,6 +110,7 @@ export class CreatePaymentReceive {
tenantId, tenantId,
paymentReceive, paymentReceive,
paymentReceiveId: paymentReceive.id, paymentReceiveId: paymentReceive.id,
paymentReceiveDTO,
authorizedUser, authorizedUser,
trx, trx,
} as IPaymentReceiveCreatedPayload); } as IPaymentReceiveCreatedPayload);