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.
*/
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;

View File

@@ -148,6 +148,9 @@ export default class ManualJournalsController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.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('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(),
];
}

View File

@@ -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(),
];
}

View File

@@ -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(),
];
}

View File

@@ -236,6 +236,9 @@ export default class PaymentReceivesController extends BaseController {
.optional({ nullable: true })
.isNumeric()
.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.*.invoice_id').exists().isNumeric().toInt(),
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('terms_conditions').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,
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)),

View File

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

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 { 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;
}

View File

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

View File

@@ -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 {}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 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<void>}
*/
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<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 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<void> {
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<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;
const initialDTO = {
...omit(creditNoteDTO, ['open']),
...omit(creditNoteDTO, ['open', 'attachments']),
creditNoteNumber,
amount,
currencyCode: customerCurrencyCode,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 } : {}),

View File

@@ -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.

View File

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

View File

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

View File

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