add server to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 11:57:50 +02:00
parent 28e309981b
commit 80b97b5fdc
1303 changed files with 137049 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import { Service, Inject } from 'typedi';
import Knex from 'knex';
import { IVendorCreditAppliedBill } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import Bluebird from 'bluebird';
@Service()
export default class ApplyVendorCreditSyncBills {
@Inject()
tenancy: HasTenancyService;
/**
* Increment bills credited amount.
* @param {number} tenantId
* @param {IVendorCreditAppliedBill[]} vendorCreditAppliedBills
* @param {Knex.Transaction} trx
*/
public incrementBillsCreditedAmount = async (
tenantId: number,
vendorCreditAppliedBills: IVendorCreditAppliedBill[],
trx?: Knex.Transaction
) => {
const { Bill } = this.tenancy.models(tenantId);
await Bluebird.each(
vendorCreditAppliedBills,
(vendorCreditAppliedBill: IVendorCreditAppliedBill) => {
return Bill.query(trx)
.where('id', vendorCreditAppliedBill.billId)
.increment('creditedAmount', vendorCreditAppliedBill.amount);
}
);
};
/**
* Decrement bill credited amount.
* @param {number} tenantId
* @param {IVendorCreditAppliedBill} vendorCreditAppliedBill
* @param {Knex.Transaction} trx
*/
public decrementBillCreditedAmount = async (
tenantId: number,
vendorCreditAppliedBill: IVendorCreditAppliedBill,
trx?: Knex.Transaction
) => {
const { Bill } = this.tenancy.models(tenantId);
await Bill.query(trx)
.findById(vendorCreditAppliedBill.billId)
.decrement('creditedAmount', vendorCreditAppliedBill.amount);
};
}

View File

@@ -0,0 +1,65 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import {
IVendorCreditApplyToBillDeletedPayload,
IVendorCreditApplyToBillsCreatedPayload,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import ApplyVendorCreditSyncBills from './ApplyVendorCreditSyncBills';
@Service()
export default class ApplyVendorCreditSyncBillsSubscriber {
@Inject()
tenancy: HasTenancyService;
@Inject()
syncBillsWithVendorCredit: ApplyVendorCreditSyncBills;
/**
* Attaches events with handlers.
*/
attach(bus) {
bus.subscribe(
events.vendorCredit.onApplyToInvoicesCreated,
this.incrementAppliedBillsOnceCreditCreated
);
bus.subscribe(
events.vendorCredit.onApplyToInvoicesDeleted,
this.decrementAppliedBillsOnceCreditDeleted
);
}
/**
* Increment credited amount of applied bills once the vendor credit
* transaction created.
* @param {IVendorCreditApplyToBillsCreatedPayload} paylaod -
*/
private incrementAppliedBillsOnceCreditCreated = async ({
tenantId,
vendorCreditAppliedBills,
trx,
}: IVendorCreditApplyToBillsCreatedPayload) => {
await this.syncBillsWithVendorCredit.incrementBillsCreditedAmount(
tenantId,
vendorCreditAppliedBills,
trx
);
};
/**
* Decrement credited amount of applied bills once the vendor credit
* transaction delted.
* @param {IVendorCreditApplyToBillDeletedPayload} payload
*/
private decrementAppliedBillsOnceCreditDeleted = async ({
oldCreditAppliedToBill,
tenantId,
trx,
}: IVendorCreditApplyToBillDeletedPayload) => {
await this.syncBillsWithVendorCredit.decrementBillCreditedAmount(
tenantId,
oldCreditAppliedToBill,
trx
);
};
}

View File

@@ -0,0 +1,48 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class ApplyVendorCreditSyncInvoiced {
@Inject()
tenancy: HasTenancyService;
/**
* Increment vendor credit invoiced amount.
* @param {number} tenantId
* @param {number} vendorCreditId
* @param {number} amount
* @param {Knex.Transaction} trx
*/
public incrementVendorCreditInvoicedAmount = async (
tenantId: number,
vendorCreditId: number,
amount: number,
trx?: Knex.Transaction
) => {
const { VendorCredit } = this.tenancy.models(tenantId);
await VendorCredit.query(trx)
.findById(vendorCreditId)
.increment('invoicedAmount', amount);
};
/**
* Decrement credit note invoiced amount.
* @param {number} tenantId
* @param {number} creditNoteId
* @param {number} invoicesAppliedAmount
*/
public decrementVendorCreditInvoicedAmount = async (
tenantId: number,
vendorCreditId: number,
amount: number,
trx?: Knex.Transaction
) => {
const { VendorCredit } = this.tenancy.models(tenantId);
await VendorCredit.query(trx)
.findById(vendorCreditId)
.decrement('invoicedAmount', amount);
};
}

View File

@@ -0,0 +1,70 @@
import { Service, Inject } from 'typedi';
import { sumBy } from 'lodash';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import ApplyVendorCreditSyncInvoiced from './ApplyVendorCreditSyncInvoiced';
import events from '@/subscribers/events';
import {
IVendorCreditApplyToBillDeletedPayload,
IVendorCreditApplyToBillsCreatedPayload,
} from '@/interfaces';
@Service()
export default class ApplyVendorCreditSyncInvoicedSubscriber {
@Inject()
tenancy: HasTenancyService;
@Inject()
syncCreditWithInvoiced: ApplyVendorCreditSyncInvoiced;
/**
* Attaches events with handlers.
*/
attach(bus) {
bus.subscribe(
events.vendorCredit.onApplyToInvoicesCreated,
this.incrementBillInvoicedOnceCreditApplied
);
bus.subscribe(
events.vendorCredit.onApplyToInvoicesDeleted,
this.decrementBillInvoicedOnceCreditApplyDeleted
);
}
/**
* Increment vendor credit invoiced amount once the apply transaction created.
* @param {IVendorCreditApplyToBillsCreatedPayload} payload -
*/
private incrementBillInvoicedOnceCreditApplied = async ({
vendorCredit,
tenantId,
vendorCreditAppliedBills,
trx,
}: IVendorCreditApplyToBillsCreatedPayload) => {
const amount = sumBy(vendorCreditAppliedBills, 'amount');
await this.syncCreditWithInvoiced.incrementVendorCreditInvoicedAmount(
tenantId,
vendorCredit.id,
amount,
trx
);
};
/**
* Decrement vendor credit invoiced amount once the apply transaction deleted.
* @param {IVendorCreditApplyToBillDeletedPayload} payload -
*/
private decrementBillInvoicedOnceCreditApplyDeleted = async ({
tenantId,
vendorCredit,
oldCreditAppliedToBill,
trx,
}: IVendorCreditApplyToBillDeletedPayload) => {
await this.syncCreditWithInvoiced.decrementVendorCreditInvoicedAmount(
tenantId,
oldCreditAppliedToBill.vendorCreditId,
oldCreditAppliedToBill.amount,
trx
);
};
}

View File

@@ -0,0 +1,127 @@
import { Service, Inject } from 'typedi';
import Knex from 'knex';
import { sumBy } from 'lodash';
import {
IVendorCredit,
IVendorCreditApplyToBillsCreatedPayload,
IVendorCreditApplyToInvoicesDTO,
IVendorCreditApplyToInvoicesModel,
IBill,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import PaymentReceiveService from '@/services/Sales/PaymentReceives/PaymentsReceives';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import VendorCredit from '../BaseVendorCredit';
import BillPaymentsService from '@/services/Purchases/BillPayments/BillPayments';
import { ServiceError } from '@/exceptions';
import { ERRORS } from '../constants';
@Service()
export default class ApplyVendorCreditToBills extends VendorCredit {
@Inject('PaymentReceives')
paymentReceive: PaymentReceiveService;
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
@Inject()
billPayment: BillPaymentsService;
/**
* Apply credit note to the given invoices.
* @param {number} tenantId
* @param {number} creditNoteId
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
*/
public applyVendorCreditToBills = async (
tenantId: number,
vendorCreditId: number,
applyCreditToBillsDTO: IVendorCreditApplyToInvoicesDTO
): Promise<void> => {
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
// Retrieves the vendor credit or throw not found service error.
const vendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
vendorCreditId
);
// Transfomes credit apply to bills DTO to model object.
const vendorCreditAppliedModel = this.transformApplyDTOToModel(
applyCreditToBillsDTO,
vendorCredit
);
// Validate bills entries existance.
const appliedBills = await this.billPayment.validateBillsExistance(
tenantId,
vendorCreditAppliedModel.entries,
vendorCredit.vendorId
);
// Validate bills has remaining amount to apply.
this.validateBillsRemainingAmount(
appliedBills,
vendorCreditAppliedModel.amount
);
// Validate vendor credit remaining credit amount.
this.validateCreditRemainingAmount(
vendorCredit,
vendorCreditAppliedModel.amount
);
// Saves vendor credit applied to bills under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Inserts vendor credit applied to bills graph to the storage layer.
const vendorCreditAppliedBills =
await VendorCreditAppliedBill.query().insertGraph(
vendorCreditAppliedModel.entries
);
// Triggers `IVendorCreditApplyToBillsCreatedPayload` event.
await this.eventPublisher.emitAsync(
events.vendorCredit.onApplyToInvoicesCreated,
{
trx,
tenantId,
vendorCredit,
vendorCreditAppliedBills,
} as IVendorCreditApplyToBillsCreatedPayload
);
});
};
/**
* Transformes apply DTO to model.
* @param {IApplyCreditToInvoicesDTO} applyDTO
* @param {ICreditNote} creditNote
* @returns {IVendorCreditApplyToInvoicesModel}
*/
private transformApplyDTOToModel = (
applyDTO: IVendorCreditApplyToInvoicesDTO,
vendorCredit: IVendorCredit
): IVendorCreditApplyToInvoicesModel => {
const entries = applyDTO.entries.map((entry) => ({
billId: entry.billId,
amount: entry.amount,
vendorCreditId: vendorCredit.id,
}));
const amount = sumBy(applyDTO.entries, 'amount');
return {
amount,
entries,
};
};
/**
* Validate bills remaining amount.
* @param {IBill[]} bills
* @param {number} amount
*/
private validateBillsRemainingAmount = (bills: IBill[], amount: number) => {
const invalidBills = bills.filter((bill) => bill.dueAmount < amount);
if (invalidBills.length > 0) {
throw new ServiceError(ERRORS.BILLS_HAS_NO_REMAINING_AMOUNT);
}
};
}

View File

@@ -0,0 +1,61 @@
import { ServiceError } from '@/exceptions';
import { IVendorCreditApplyToBillDeletedPayload } from '@/interfaces';
import Knex from 'knex';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import { Service, Inject } from 'typedi';
import BaseVendorCredit from '../BaseVendorCredit';
import { ERRORS } from '../constants';
@Service()
export default class DeleteApplyVendorCreditToBill extends BaseVendorCredit {
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
/**
* Delete apply vendor credit to bill transaction.
* @param {number} tenantId
* @param {number} appliedCreditToBillId
* @returns {Promise<void>}
*/
public deleteApplyVendorCreditToBills = async (
tenantId: number,
appliedCreditToBillId: number
) => {
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
const oldCreditAppliedToBill =
await VendorCreditAppliedBill.query().findById(appliedCreditToBillId);
if (!oldCreditAppliedToBill) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND);
}
// Retrieve the vendor credit or throw not found service error.
const vendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
oldCreditAppliedToBill.vendorCreditId
);
// Deletes vendor credit apply under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Delete vendor credit applied to bill transaction.
await VendorCreditAppliedBill.query(trx)
.findById(appliedCreditToBillId)
.delete();
// Triggers `onVendorCreditApplyToInvoiceDeleted` event.
await this.eventPublisher.emitAsync(
events.vendorCredit.onApplyToInvoicesDeleted,
{
tenantId,
vendorCredit,
oldCreditAppliedToBill,
trx,
} as IVendorCreditApplyToBillDeletedPayload
);
});
};
}

View File

@@ -0,0 +1,36 @@
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { Service, Inject } from 'typedi';
import BaseVendorCredit from '../BaseVendorCredit';
import { VendorCreditAppliedBillTransformer } from './VendorCreditAppliedBillTransformer';
@Service()
export default class GetAppliedBillsToVendorCredit extends BaseVendorCredit {
@Inject()
private transformer: TransformerInjectable;
/**
*
* @param {number} tenantId
* @param {number} vendorCreditId
* @returns
*/
public getAppliedBills = async (tenantId: number, vendorCreditId: number) => {
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
const vendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
vendorCreditId
);
const appliedToBills = await VendorCreditAppliedBill.query()
.where('vendorCreditId', vendorCreditId)
.withGraphFetched('bill')
.withGraphFetched('vendorCredit');
// Transformes the models to POJO.
return this.transformer.transform(
tenantId,
appliedToBills,
new VendorCreditAppliedBillTransformer()
);
};
}

View File

@@ -0,0 +1,41 @@
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { Service, Inject } from 'typedi';
import BaseVendorCredit from '../BaseVendorCredit';
import { VendorCreditToApplyBillTransformer } from './VendorCreditToApplyBillTransformer';
@Service()
export default class GetVendorCreditToApplyBills extends BaseVendorCredit {
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve bills that valid apply to the given vendor credit.
* @param {number} tenantId
* @param {number} vendorCreditId
* @returns
*/
public getCreditToApplyBills = async (
tenantId: number,
vendorCreditId: number
) => {
const { Bill } = this.tenancy.models(tenantId);
// Retrieve vendor credit or throw not found service error.
const vendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
vendorCreditId
);
// Retrieive open bills associated to the given vendor.
const openBills = await Bill.query()
.where('vendor_id', vendorCredit.vendorId)
.modify('dueBills')
.modify('published');
// Transformes the bills to POJO.
return this.transformer.transform(
tenantId,
openBills,
new VendorCreditToApplyBillTransformer()
);
};
}

View File

@@ -0,0 +1,62 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class VendorCreditAppliedBillTransformer extends Transformer {
/**
* Includeded attributes.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'vendorCreditNumber',
'vendorCreditDate',
'billNumber',
'billReferenceNo',
'formattedVendorCreditDate',
'formattedBillDate',
];
};
/**
* Exclude attributes.
* @returns {string[]}
*/
public excludeAttributes = (): string[] => {
return ['bill', 'vendorCredit'];
};
protected formattedAmount = (item) => {
return formatNumber(item.amount, {
currencyCode: item.vendorCredit.currencyCode,
});
};
protected vendorCreditNumber = (item) => {
return item.vendorCredit.vendorCreditNumber;
};
protected vendorCreditDate = (item) => {
return item.vendorCredit.vendorCreditDate;
};
protected formattedVendorCreditDate = (item) => {
return this.formatDate(item.vendorCredit.vendorCreditDate);
};
protected billNumber = (item) => {
return item.bill.billNo;
};
protected billReferenceNo = (item) => {
return item.bill.referenceNo;
};
protected BillDate = (item) => {
return item.bill.billDate;
};
protected formattedBillDate = (item) => {
return this.formatDate(item.bill.billDate);
};
}

View File

@@ -0,0 +1,70 @@
import { IBill } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class VendorCreditToApplyBillTransformer extends Transformer {
/**
* Include these attributes to sale invoice object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'formattedBillDate',
'formattedDueDate',
'formattedAmount',
'formattedDueAmount',
'formattedPaymentAmount',
];
};
/**
* Retrieve formatted bill date.
* @param {IBill} bill
* @returns {String}
*/
protected formattedBillDate = (bill: IBill): string => {
return this.formatDate(bill.billDate);
};
/**
* Retrieve formatted due date.
* @param {IBill} bill
* @returns {string}
*/
protected formattedDueDate = (bill: IBill): string => {
return this.formatDate(bill.dueDate);
};
/**
* Retrieve formatted bill amount.
* @param {IBill} bill
* @returns {string}
*/
protected formattedAmount = (bill: IBill): string => {
return formatNumber(bill.amount, {
currencyCode: bill.currencyCode,
});
};
/**
* Retrieve formatted bill due amount.
* @param {IBill} bill
* @returns {string}
*/
protected formattedDueAmount = (bill: IBill): string => {
return formatNumber(bill.dueAmount, {
currencyCode: bill.currencyCode,
});
};
/**
* Retrieve formatted payment amount.
* @param {IBill} bill
* @returns {string}
*/
protected formattedPaymentAmount = (bill: IBill): string => {
return formatNumber(bill.paymentAmount, {
currencyCode: bill.currencyCode,
});
};
}

View File

@@ -0,0 +1,139 @@
import { Inject, Service } from 'typedi';
import moment from 'moment';
import { omit } from 'lodash';
import * as R from 'ramda';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from './constants';
import { ServiceError } from '@/exceptions';
import {
IVendorCredit,
IVendorCreditCreateDTO,
IVendorCreditEditDTO,
} from '@/interfaces';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import AutoIncrementOrdersService from '@/services/Sales/AutoIncrementOrdersService';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
@Service()
export default class BaseVendorCredit {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private itemsEntriesService: ItemsEntriesService;
@Inject()
private autoIncrementOrdersService: AutoIncrementOrdersService;
@Inject()
private branchDTOTransform: BranchTransactionDTOTransform;
@Inject()
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
/**
* Transformes the credit/edit vendor credit DTO to model.
* @param {number} tenantId -
* @param {IVendorCreditCreateDTO | IVendorCreditEditDTO} vendorCreditDTO
* @param {string} vendorCurrencyCode -
* @param {IVendorCredit} oldVendorCredit -
* @returns {IVendorCredit}
*/
public transformCreateEditDTOToModel = (
tenantId: number,
vendorCreditDTO: IVendorCreditCreateDTO | IVendorCreditEditDTO,
vendorCurrencyCode: string,
oldVendorCredit?: IVendorCredit
): IVendorCredit => {
// Calculates the total amount of items entries.
const amount = this.itemsEntriesService.getTotalItemsEntries(
vendorCreditDTO.entries
);
const entries = vendorCreditDTO.entries.map((entry) => ({
...entry,
referenceType: 'VendorCredit',
}));
// Retreive the next vendor credit number.
const autoNextNumber = this.getNextCreditNumber(tenantId);
// Detarmines the credit note number.
const vendorCreditNumber =
vendorCreditDTO.vendorCreditNumber ||
oldVendorCredit?.vendorCreditNumber ||
autoNextNumber;
const initialDTO = {
...omit(vendorCreditDTO, ['open']),
amount,
currencyCode: vendorCurrencyCode,
exchangeRate: vendorCreditDTO.exchangeRate || 1,
vendorCreditNumber,
entries,
...(vendorCreditDTO.open &&
!oldVendorCredit?.openedAt && {
openedAt: moment().toMySqlDateTime(),
}),
};
return R.compose(
this.branchDTOTransform.transformDTO<IVendorCredit>(tenantId),
this.warehouseDTOTransform.transformDTO<IVendorCredit>(tenantId)
)(initialDTO);
};
/**
* Retrieve the vendor credit or throw not found service error.
* @param {number} tenantId
* @param {number} vendorCreditId
*/
public getVendorCreditOrThrowError = async (
tenantId: number,
vendorCreditId: number
): Promise<IVendorCredit> => {
const { VendorCredit } = this.tenancy.models(tenantId);
const vendorCredit = await VendorCredit.query().findById(vendorCreditId);
if (!vendorCredit) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_NOT_FOUND);
}
return vendorCredit;
};
/**
* Retrieve the next unique credit number.
* @param {number} tenantId - Tenant id.
* @return {string}
*/
private getNextCreditNumber = (tenantId: number): string => {
return this.autoIncrementOrdersService.getNextTransactionNumber(
tenantId,
'vendor_credit'
);
};
/**
* Increment the vendor credit serial next number.
* @param {number} tenantId -
*/
public incrementSerialNumber = (tenantId: number) => {
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
tenantId,
'vendor_credit'
);
};
/**
* Validate the credit note remaining amount.
* @param {ICreditNote} creditNote
* @param {number} amount
*/
public validateCreditRemainingAmount = (
vendorCredit: IVendorCredit,
amount: number
) => {
if (vendorCredit.creditsRemaining < amount) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT);
}
};
}

View File

@@ -0,0 +1,85 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import {
IVendorCreditCreatedPayload,
IVendorCreditCreateDTO,
IVendorCreditCreatingPayload,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import BaseVendorCredit from './BaseVendorCredit';
@Service()
export default class CreateVendorCredit extends BaseVendorCredit {
@Inject()
private uow: UnitOfWork;
@Inject()
private itemsEntriesService: ItemsEntriesService;
@Inject()
private tenancy: HasTenancyService;
@Inject()
private eventPublisher: EventPublisher;
/**
* Creates a new vendor credit.
* @param {number} tenantId -
* @param {IVendorCreditCreateDTO} vendorCreditCreateDTO -
*/
public newVendorCredit = async (
tenantId: number,
vendorCreditCreateDTO: IVendorCreditCreateDTO
) => {
const { VendorCredit, Vendor } = this.tenancy.models(tenantId);
// Triggers `onVendorCreditCreate` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onCreate, {
tenantId,
vendorCreditCreateDTO,
});
// Retrieve the given vendor or throw not found service error.
const vendor = await Vendor.query()
.findById(vendorCreditCreateDTO.vendorId)
.throwIfNotFound();
// Validate items should be sellable items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
vendorCreditCreateDTO.entries
);
// Transformes the credit DTO to storage layer.
const vendorCreditModel = this.transformCreateEditDTOToModel(
tenantId,
vendorCreditCreateDTO,
vendor.currencyCode
);
// Saves the vendor credit transactions under UOW envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onVendorCreditCreating` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onCreating, {
tenantId,
vendorCreditCreateDTO,
trx,
} as IVendorCreditCreatingPayload);
// Saves the vendor credit graph.
const vendorCredit = await VendorCredit.query(trx).upsertGraphAndFetch({
...vendorCreditModel,
});
// Triggers `onVendorCreditCreated` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onCreated, {
tenantId,
vendorCredit,
vendorCreditCreateDTO,
trx,
} as IVendorCreditCreatedPayload);
return vendorCredit;
});
};
}

View File

@@ -0,0 +1,57 @@
import { Inject, Service } from 'typedi';
import { ServiceError } from '@/exceptions';
import TenancyService from '@/services/Tenancy/TenancyService';
import events from '@/subscribers/events';
import { IVendorEventDeletingPayload } from '@/interfaces';
const ERRORS = {
VENDOR_HAS_TRANSACTIONS: 'VENDOR_HAS_TRANSACTIONS',
};
@Service()
export default class DeleteVendorAssociatedVendorCredit {
@Inject()
tenancy: TenancyService;
/**
* Attaches events with handlers.
* @param bus
*/
public attach = (bus) => {
bus.subscribe(
events.vendors.onDeleting,
this.validateVendorHasNoCreditsTransactionsOnceDeleting
);
};
/**
* Validate vendor has no assocaited credit transaction once the vendor deleting.
* @param {IVendorEventDeletingPayload} payload -
*/
public validateVendorHasNoCreditsTransactionsOnceDeleting = async ({
tenantId,
vendorId,
}: IVendorEventDeletingPayload) => {
await this.validateVendorHasNoCreditsTransactions(tenantId, vendorId);
};
/**
* Validate the given vendor has no associated vendor credit transactions.
* @param {number} tenantId
* @param {number} vendorId
*/
public validateVendorHasNoCreditsTransactions = async (
tenantId: number,
vendorId: number
): Promise<void> => {
const { VendorCredit } = this.tenancy.models(tenantId);
const associatedVendors = await VendorCredit.query().where(
'vendorId',
vendorId
);
if (associatedVendors.length > 0) {
throw new ServiceError(ERRORS.VENDOR_HAS_TRANSACTIONS);
}
};
}

View File

@@ -0,0 +1,119 @@
import { Service, Inject } from 'typedi';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import BaseVendorCredit from './BaseVendorCredit';
import { Knex } from 'knex';
import events from '@/subscribers/events';
import {
IVendorCreditDeletedPayload,
IVendorCreditDeletingPayload,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
import { ERRORS } from './constants';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class DeleteVendorCredit extends BaseVendorCredit {
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
@Inject()
tenancy: HasTenancyService;
/**
* Deletes the given vendor credit.
* @param {number} tenantId - Tenant id.
* @param {number} vendorCreditId - Vendor credit id.
*/
public deleteVendorCredit = async (
tenantId: number,
vendorCreditId: number
) => {
const { VendorCredit, ItemEntry } = this.tenancy.models(tenantId);
// Retrieve the old vendor credit.
const oldVendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
vendorCreditId
);
// Validates vendor credit has no associate refund transactions.
await this.validateVendorCreditHasNoRefundTransactions(
tenantId,
vendorCreditId
);
// Validates vendor credit has no associated applied to bills transactions.
await this.validateVendorCreditHasNoApplyBillsTransactions(
tenantId,
vendorCreditId
);
// Deletes the vendor credit transactions under UOW envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
// Triggers `onVendorCreditEditing` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onDeleting, {
tenantId,
oldVendorCredit,
trx,
} as IVendorCreditDeletingPayload);
// Deletes the associated credit note entries.
await ItemEntry.query(trx)
.where('reference_id', vendorCreditId)
.where('reference_type', 'VendorCredit')
.delete();
// Deletes the credit note transaction.
await VendorCredit.query(trx).findById(vendorCreditId).delete();
// Triggers `onVendorCreditDeleted` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onDeleted, {
tenantId,
vendorCreditId,
oldVendorCredit,
trx,
} as IVendorCreditDeletedPayload);
});
};
/**
* Validates vendor credit has no refund transactions.
* @param {number} tenantId
* @param {number} vendorCreditId
*/
private validateVendorCreditHasNoRefundTransactions = async (
tenantId: number,
vendorCreditId: number
): Promise<void> => {
const { RefundVendorCredit } = this.tenancy.models(tenantId);
const refundCredits = await RefundVendorCredit.query().where(
'vendorCreditId',
vendorCreditId
);
if (refundCredits.length > 0) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS);
}
};
/**
* Validate vendor credit has no applied transactions to bills.
* @param {number} tenantId
* @param {number} vendorCreditId
*/
private validateVendorCreditHasNoApplyBillsTransactions = async (
tenantId: number,
vendorCreditId: number
): Promise<void> => {
const { VendorCreditAppliedBill } = this.tenancy.models(tenantId);
const appliedTransactions = await VendorCreditAppliedBill.query().where(
'vendorCreditId',
vendorCreditId
);
if (appliedTransactions.length > 0) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_APPLIED_BILLS);
}
};
}

View File

@@ -0,0 +1,98 @@
import { Service, Inject } from 'typedi';
import {
IVendorCreditEditDTO,
IVendorCreditEditedPayload,
IVendorCreditEditingPayload,
} from '@/interfaces';
import BaseVendorCredit from './BaseVendorCredit';
import UnitOfWork from '@/services/UnitOfWork';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
import events from '@/subscribers/events';
@Service()
export default class EditVendorCredit extends BaseVendorCredit {
@Inject()
private eventPublisher: EventPublisher;
@Inject()
private uow: UnitOfWork;
@Inject()
private itemsEntriesService: ItemsEntriesService;
/**
* Deletes the given vendor credit.
* @param {number} tenantId - Tenant id.
* @param {number} vendorCreditId - Vendor credit id.
*/
public editVendorCredit = async (
tenantId: number,
vendorCreditId: number,
vendorCreditDTO: IVendorCreditEditDTO
) => {
const { VendorCredit } = this.tenancy.models(tenantId);
// Retrieve the vendor credit or throw not found service error.
const oldVendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
vendorCreditId
);
// Validate customer existance.
const vendor = await Contact.query()
.modify('vendor')
.findById(vendorCreditDTO.vendorId)
.throwIfNotFound();
// Validate items ids existance.
await this.itemsEntriesService.validateItemsIdsExistance(
tenantId,
vendorCreditDTO.entries
);
// Validate non-sellable entries items.
await this.itemsEntriesService.validateNonSellableEntriesItems(
tenantId,
vendorCreditDTO.entries
);
// Validate the items entries existance.
await this.itemsEntriesService.validateEntriesIdsExistance(
tenantId,
vendorCreditId,
'VendorCredit',
vendorCreditDTO.entries
);
// Transformes edit DTO to model storage layer.
const vendorCreditModel = this.transformCreateEditDTOToModel(
tenantId,
vendorCreditDTO,
vendor.currencyCode,
oldVendorCredit
);
// Edits the vendor credit graph under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx) => {
// Triggers `onVendorCreditEditing` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onEditing, {
tenantId,
oldVendorCredit,
vendorCreditDTO,
trx,
} as IVendorCreditEditingPayload);
// Saves the vendor credit graph to the storage.
const vendorCredit = await VendorCredit.query(trx).upsertGraphAndFetch({
id: vendorCreditId,
...vendorCreditModel,
});
// Triggers `onVendorCreditEdited event.
await this.eventPublisher.emitAsync(events.vendorCredit.onEdited, {
tenantId,
oldVendorCredit,
vendorCredit,
vendorCreditId,
trx,
} as IVendorCreditEditedPayload);
return vendorCredit;
});
};
}

View File

@@ -0,0 +1,40 @@
import { ServiceError } from '@/exceptions';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { VendorCreditTransformer } from './VendorCreditTransformer';
import { Inject, Service } from 'typedi';
import { ERRORS } from './constants';
@Service()
export default class GetVendorCredit {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve the given vendor credit.
* @param {number} tenantId - Tenant id.
* @param {number} vendorCreditId - Vendor credit id.
*/
public getVendorCredit = async (tenantId: number, vendorCreditId: number) => {
const { VendorCredit } = this.tenancy.models(tenantId);
// Retrieve the vendor credit model graph.
const vendorCredit = await VendorCredit.query()
.findById(vendorCreditId)
.withGraphFetched('entries.item')
.withGraphFetched('vendor')
.withGraphFetched('branch');
if (!vendorCredit) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_NOT_FOUND);
}
return this.transformer.transform(
tenantId,
vendorCredit,
new VendorCreditTransformer()
);
};
}

View File

@@ -0,0 +1,66 @@
import * as R from 'ramda';
import { Service, Inject } from 'typedi';
import BaseVendorCredit from './BaseVendorCredit';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { IVendorCreditsQueryDTO } from '@/interfaces';
import { VendorCreditTransformer } from './VendorCreditTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export default class ListVendorCredits extends BaseVendorCredit {
@Inject()
private dynamicListService: DynamicListingService;
@Inject()
private transformer: TransformerInjectable;
/**
* Parses the sale invoice list filter DTO.
* @param {IVendorCreditsQueryDTO} filterDTO
* @returns
*/
private parseListFilterDTO = (filterDTO: IVendorCreditsQueryDTO) => {
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
};
/**
* Retrieve the vendor credits list.
* @param {number} tenantId - Tenant id.
* @param {IVendorCreditsQueryDTO} vendorCreditQuery -
*/
public getVendorCredits = async (
tenantId: number,
vendorCreditQuery: IVendorCreditsQueryDTO
) => {
const { VendorCredit } = this.tenancy.models(tenantId);
// Parses stringified filter roles.
const filter = this.parseListFilterDTO(vendorCreditQuery);
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
tenantId,
VendorCredit,
filter
);
const { results, pagination } = await VendorCredit.query()
.onBuild((builder) => {
builder.withGraphFetched('entries');
builder.withGraphFetched('vendor');
dynamicFilter.buildQuery()(builder);
})
.pagination(filter.page - 1, filter.pageSize);
// Transformes the vendor credits models to POJO.
const vendorCredits = await this.transformer.transform(
tenantId,
results,
new VendorCreditTransformer()
);
return {
vendorCredits,
pagination,
filterMeta: dynamicFilter.getResponseMeta(),
};
};
}

View File

@@ -0,0 +1,90 @@
import { ServiceError } from '@/exceptions';
import {
IVendorCredit,
IVendorCreditOpenedPayload,
IVendorCreditOpeningPayload,
IVendorCreditOpenPayload,
} from '@/interfaces';
import Knex from 'knex';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import { Inject, Service } from 'typedi';
import BaseVendorCredit from './BaseVendorCredit';
import { ERRORS } from './constants';
@Service()
export default class OpenVendorCredit extends BaseVendorCredit {
@Inject()
eventPublisher: EventPublisher;
@Inject()
uow: UnitOfWork;
/**
* Opens the given credit note.
* @param {number} tenantId -
* @param {ICreditNoteEditDTO} creditNoteEditDTO -
* @returns {Promise<ICreditNote>}
*/
public openVendorCredit = async (
tenantId: number,
vendorCreditId: number
): Promise<IVendorCredit> => {
const { VendorCredit } = this.tenancy.models(tenantId);
// Retrieve the vendor credit or throw not found service error.
const oldVendorCredit = await this.getVendorCreditOrThrowError(
tenantId,
vendorCreditId
);
// Throw service error if the credit note is already open.
this.throwErrorIfAlreadyOpen(oldVendorCredit);
// Triggers `onVendorCreditOpen` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onOpen, {
tenantId,
vendorCreditId,
oldVendorCredit,
} as IVendorCreditOpenPayload);
// Sales the credit note transactions with associated entries.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
const eventPayload = {
tenantId,
vendorCreditId,
oldVendorCredit,
trx,
} as IVendorCreditOpeningPayload;
// Triggers `onCreditNoteOpening` event.
await this.eventPublisher.emitAsync(
events.creditNote.onOpening,
eventPayload as IVendorCreditOpeningPayload
);
// Saves the vendor credit graph to the storage.
const vendorCredit = await VendorCredit.query(trx)
.findById(vendorCreditId)
.update({
openedAt: new Date(),
});
// Triggers `onVendorCreditOpened` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onOpened, {
...eventPayload,
vendorCredit,
} as IVendorCreditOpenedPayload);
return vendorCredit;
});
};
/**
* Throw error if the vendor credit is already open.
* @param {IVendorCredit} vendorCredit
*/
public throwErrorIfAlreadyOpen = (vendorCredit: IVendorCredit) => {
if (vendorCredit.openedAt) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_ALREADY_OPENED);
}
};
}

View File

@@ -0,0 +1,128 @@
import { Service, Inject } from 'typedi';
import { Knex } from 'knex';
import * as R from 'ramda';
import {
IRefundVendorCredit,
IRefundVendorCreditCreatedPayload,
IRefundVendorCreditCreatingPayload,
IRefundVendorCreditDTO,
IVendorCredit,
IVendorCreditCreatePayload,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import RefundVendorCredit from './RefundVendorCredit';
import events from '@/subscribers/events';
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
@Service()
export default class CreateRefundVendorCredit extends RefundVendorCredit {
@Inject()
tenancy: HasTenancyService;
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
@Inject()
private branchDTOTransform: BranchTransactionDTOTransform;
/**
* Creates a refund vendor credit.
* @param {number} tenantId
* @param {number} vendorCreditId
* @param {IRefundVendorCreditDTO} refundVendorCreditDTO
* @returns {Promise<IRefundVendorCredit>}
*/
public createRefund = async (
tenantId: number,
vendorCreditId: number,
refundVendorCreditDTO: IRefundVendorCreditDTO
): Promise<IRefundVendorCredit> => {
const { RefundVendorCredit, Account, VendorCredit } =
this.tenancy.models(tenantId);
// Retrieve the vendor credit or throw not found service error.
const vendorCredit = await VendorCredit.query()
.findById(vendorCreditId)
.throwIfNotFound();
// Retrieve the deposit account or throw not found service error.
const depositAccount = await Account.query()
.findById(refundVendorCreditDTO.depositAccountId)
.throwIfNotFound();
// Validate vendor credit has remaining credit.
this.validateVendorCreditRemainingCredit(
vendorCredit,
refundVendorCreditDTO.amount
);
// Validate refund deposit account type.
this.validateRefundDepositAccountType(depositAccount);
// Triggers `onVendorCreditRefundCreate` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onRefundCreate, {
tenantId,
vendorCreditId,
refundVendorCreditDTO,
} as IVendorCreditCreatePayload);
const refundCreditObj = this.transformDTOToModel(
tenantId,
vendorCredit,
refundVendorCreditDTO
);
// Saves refund vendor credit with associated transactions.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
const eventPayload = {
vendorCredit,
trx,
tenantId,
refundVendorCreditDTO,
} as IRefundVendorCreditCreatingPayload;
// Triggers `onVendorCreditRefundCreating` event.
await this.eventPublisher.emitAsync(
events.vendorCredit.onRefundCreating,
eventPayload as IRefundVendorCreditCreatingPayload
);
// Inserts refund vendor credit to the storage layer.
const refundVendorCredit =
await RefundVendorCredit.query().insertAndFetch({
...refundCreditObj,
});
// Triggers `onVendorCreditCreated` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onRefundCreated, {
...eventPayload,
refundVendorCredit,
} as IRefundVendorCreditCreatedPayload);
return refundVendorCredit;
});
};
/**
* Transformes the refund DTO to refund vendor credit model.
* @param {IVendorCredit} vendorCredit -
* @param {IRefundVendorCreditDTO} vendorCreditDTO
* @returns {IRefundVendorCredit}
*/
public transformDTOToModel = (
tenantId: number,
vendorCredit: IVendorCredit,
vendorCreditDTO: IRefundVendorCreditDTO
) => {
const initialDTO = {
vendorCreditId: vendorCredit.id,
...vendorCreditDTO,
currencyCode: vendorCredit.currencyCode,
exchangeRate: vendorCreditDTO.exchangeRate || 1,
};
return R.compose(this.branchDTOTransform.transformDTO(tenantId))(
initialDTO
);
};
}

View File

@@ -0,0 +1,73 @@
import { Knex } from 'knex';
import {
IRefundVendorCreditDeletedPayload,
IRefundVendorCreditDeletePayload,
IRefundVendorCreditDeletingPayload,
} from '@/interfaces';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import UnitOfWork from '@/services/UnitOfWork';
import events from '@/subscribers/events';
import { Inject, Service } from 'typedi';
import RefundVendorCredit from './RefundVendorCredit';
@Service()
export default class DeleteRefundVendorCredit extends RefundVendorCredit {
@Inject()
tenancy: HasTenancyService;
@Inject()
uow: UnitOfWork;
@Inject()
eventPublisher: EventPublisher;
/**
* Retrieve the credit note graph.
* @param {number} tenantId - Tenant id.
* @param {number} creditNoteId - Credit note id.
* @returns {Promise<void>}
*/
public deleteRefundVendorCreditRefund = async (
tenantId: number,
refundCreditId: number
): Promise<void> => {
const { RefundVendorCredit } = this.tenancy.models(tenantId);
// Retrieve the old credit note or throw not found service error.
const oldRefundCredit = await this.getRefundVendorCreditOrThrowError(
tenantId,
refundCreditId
);
// Triggers `onVendorCreditRefundDelete` event.
await this.eventPublisher.emitAsync(events.vendorCredit.onRefundDelete, {
refundCreditId,
oldRefundCredit,
tenantId,
} as IRefundVendorCreditDeletePayload);
// Deletes the refund vendor credit under unit-of-work envirement.
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
const eventPayload = {
trx,
refundCreditId,
oldRefundCredit,
tenantId,
} as IRefundVendorCreditDeletingPayload;
// Triggers `onVendorCreditRefundDeleting` event.
await this.eventPublisher.emitAsync(
events.vendorCredit.onRefundDeleting,
eventPayload
);
// Deletes the refund vendor credit graph from the storage.
await RefundVendorCredit.query(trx).findById(refundCreditId).delete();
// Triggers `onVendorCreditRefundDeleted` event.
await this.eventPublisher.emitAsync(
events.vendorCredit.onRefundDeleted,
eventPayload as IRefundVendorCreditDeletedPayload
);
});
};
}

View File

@@ -0,0 +1,43 @@
import { Service, Inject } from 'typedi';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { RefundVendorCreditTransformer } from './RefundVendorCreditTransformer';
import RefundVendorCredit from './RefundVendorCredit';
import { IRefundVendorCredit } from '@/interfaces';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
@Service()
export default class GetRefundVendorCredit extends RefundVendorCredit {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve refund vendor credit transaction.
* @param {number} tenantId
* @param {number} refundId
* @returns {Promise<IRefundVendorCredit>}
*/
public getRefundCreditTransaction = async (
tenantId: number,
refundId: number
): Promise<IRefundVendorCredit> => {
const { RefundVendorCredit } = this.tenancy.models(tenantId);
await this.getRefundVendorCreditOrThrowError(tenantId, refundId);
// Retrieve refund transactions associated to the given vendor credit.
const refundVendorTransactions = await RefundVendorCredit.query()
.findById(refundId)
.withGraphFetched('vendorCredit')
.withGraphFetched('depositAccount');
// Transformes refund vendor credit models to POJO objects.
return this.transformer.transform(
tenantId,
refundVendorTransactions,
new RefundVendorCreditTransformer()
);
};
}

View File

@@ -0,0 +1,41 @@
import { IRefundVendorCreditPOJO } from '@/interfaces';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import RefundVendorCredit from './RefundVendorCredit';
import { RefundVendorCreditTransformer } from './RefundVendorCreditTransformer';
@Service()
export default class ListVendorCreditRefunds extends RefundVendorCredit {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieve the credit note graph.
* @param {number} tenantId
* @param {number} creditNoteId
* @returns {Promise<IRefundCreditNotePOJO[]>}
*/
public getVendorCreditRefunds = async (
tenantId: number,
vendorCreditId: number
): Promise<IRefundVendorCreditPOJO[]> => {
const { RefundVendorCredit } = this.tenancy.models(tenantId);
// Retrieve refund transactions associated to the given vendor credit.
const refundVendorTransactions = await RefundVendorCredit.query()
.where('vendorCreditId', vendorCreditId)
.withGraphFetched('vendorCredit')
.withGraphFetched('depositAccount');
// Transformes refund vendor credit models to POJO objects.
return this.transformer.transform(
tenantId,
refundVendorTransactions,
new RefundVendorCreditTransformer()
);
};
}

View File

@@ -0,0 +1,47 @@
import Knex from 'knex';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
@Service()
export default class RefundSyncCreditRefundedAmount {
@Inject()
tenancy: HasTenancyService;
/**
* Increment vendor credit refunded amount.
* @param {number} tenantId - Tenant id.
* @param {number} amount - Amount.
* @param {Knex.Transaction} trx - Knex transaction.
*/
public incrementCreditRefundedAmount = async (
tenantId: number,
vendorCreditId: number,
amount: number,
trx?: Knex.Transaction
): Promise<void> => {
const { VendorCredit } = this.tenancy.models(tenantId);
await VendorCredit.query(trx)
.findById(vendorCreditId)
.increment('refundedAmount', amount);
};
/**
* Decrement vendor credit refunded amount.
* @param {number} tenantId
* @param {number} amount
* @param {Knex.Transaction} trx
*/
public decrementCreditNoteRefundAmount = async (
tenantId: number,
vendorCreditId: number,
amount: number,
trx?: Knex.Transaction
): Promise<void> => {
const { VendorCredit } = this.tenancy.models(tenantId);
await VendorCredit.query(trx)
.findById(vendorCreditId)
.decrement('refundedAmount', amount);
};
}

View File

@@ -0,0 +1,48 @@
import Knex from 'knex';
import { IRefundVendorCredit } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
@Service()
export default class RefundSyncVendorCreditBalance {
@Inject()
tenancy: HasTenancyService;
/**
* Increment vendor credit refunded amount.
* @param {number} tenantId -
* @param {IRefundVendorCredit} refundCreditNote -
* @param {Knex.Transaction} trx -
*/
public incrementVendorCreditRefundAmount = async (
tenantId: number,
refundVendorCredit: IRefundVendorCredit,
trx?: Knex.Transaction
): Promise<void> => {
const { VendorCredit } = this.tenancy.models(tenantId);
await VendorCredit.query(trx).increment(
'refundedAmount',
refundVendorCredit.amount
);
};
/**
* Decrement vendor credit refunded amount.
* @param {number} tenantId
* @param {IRefundVendorCredit} refundCreditNote
* @param {Knex.Transaction} trx
*/
public decrementVendorCreditRefundAmount = async (
tenantId: number,
refundVendorCredit: IRefundVendorCredit,
trx?: Knex.Transaction
): Promise<void> => {
const { VendorCredit } = this.tenancy.models(tenantId);
await VendorCredit.query(trx).decrement(
'refundedAmount',
refundVendorCredit.amount
);
};
}

View File

@@ -0,0 +1,62 @@
import { Service, Inject } from 'typedi';
import {
IRefundVendorCreditCreatedPayload,
IRefundVendorCreditDeletedPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import RefundSyncCreditRefundedAmount from './RefundSyncCreditRefundedAmount';
@Service()
export default class RefundSyncVendorCreditBalanceSubscriber {
@Inject()
refundSyncCreditRefunded: RefundSyncCreditRefundedAmount;
/**
* Attaches events with handlers.
*/
public attach = (bus) => {
bus.subscribe(
events.vendorCredit.onRefundCreated,
this.incrementRefundedAmountOnceRefundCreated
);
bus.subscribe(
events.vendorCredit.onRefundDeleted,
this.decrementRefundedAmountOnceRefundDeleted
);
};
/**
* Increment refunded vendor credit amount once refund transaction created.
* @param {IRefundVendorCreditCreatedPayload} payload -
*/
private incrementRefundedAmountOnceRefundCreated = async ({
refundVendorCredit,
vendorCredit,
tenantId,
trx,
}: IRefundVendorCreditCreatedPayload) => {
await this.refundSyncCreditRefunded.incrementCreditRefundedAmount(
tenantId,
refundVendorCredit.vendorCreditId,
refundVendorCredit.amount,
trx
);
};
/**
* Decrement refunded vendor credit amount once refund transaction deleted.
* @param {IRefundVendorCreditDeletedPayload} payload -
*/
private decrementRefundedAmountOnceRefundDeleted = async ({
trx,
oldRefundCredit,
tenantId,
}: IRefundVendorCreditDeletedPayload) => {
await this.refundSyncCreditRefunded.decrementCreditNoteRefundAmount(
tenantId,
oldRefundCredit.vendorCreditId,
oldRefundCredit.amount,
trx
);
};
}

View File

@@ -0,0 +1,55 @@
import { ServiceError } from '@/exceptions';
import { IAccount, IVendorCredit } from '@/interfaces';
import { Service, Inject } from 'typedi';
import BaseVendorCredit from '../BaseVendorCredit';
import { ERRORS } from './constants';
@Service()
export default class RefundVendorCredit extends BaseVendorCredit {
/**
* Retrieve the vendor credit refund or throw not found service error.
* @param {number} tenantId
* @param {number} vendorCreditId
* @returns
*/
public getRefundVendorCreditOrThrowError = async (
tenantId: number,
refundVendorCreditId: number
) => {
const { RefundVendorCredit } = this.tenancy.models(tenantId);
const refundCredit = await RefundVendorCredit.query().findById(
refundVendorCreditId
);
if (!refundCredit) {
throw new ServiceError(ERRORS.REFUND_VENDOR_CREDIT_NOT_FOUND);
}
return refundCredit;
};
/**
* Validate the deposit refund account type.
* @param {IAccount} account
*/
public validateRefundDepositAccountType = (account: IAccount): void => {
const supportedTypes = ['bank', 'cash', 'fixed-asset'];
if (supportedTypes.indexOf(account.accountType) === -1) {
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_INVALID_TYPE);
}
};
/**
* Validate vendor credit has remaining credits.
* @param {IVendorCredit} vendorCredit
* @param {number} amount
*/
public validateVendorCreditRemainingCredit = (
vendorCredit: IVendorCredit,
amount: number
) => {
if (vendorCredit.creditsRemaining < amount) {
throw new ServiceError(ERRORS.VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING);
}
};
}

View File

@@ -0,0 +1,159 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import { AccountNormal, ILedgerEntry } from '@/interfaces';
import { IRefundVendorCredit } from '@/interfaces';
import JournalPosterService from '@/services/Sales/JournalPosterService';
import LedgerRepository from '@/services/Ledger/LedgerRepository';
import HasTenancyService from '@/services/Tenancy/TenancyService';
@Service()
export default class RefundVendorCreditGLEntries {
@Inject()
private journalService: JournalPosterService;
@Inject()
private ledgerRepository: LedgerRepository;
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieves the refund credit common GL entry.
* @param {IRefundVendorCredit} refundCredit
*/
private getRefundCreditGLCommonEntry = (
refundCredit: IRefundVendorCredit
) => {
return {
exchangeRate: refundCredit.exchangeRate,
currencyCode: refundCredit.currencyCode,
transactionType: 'RefundVendorCredit',
transactionId: refundCredit.id,
date: refundCredit.date,
userId: refundCredit.userId,
referenceNumber: refundCredit.referenceNo,
createdAt: refundCredit.createdAt,
indexGroup: 10,
credit: 0,
debit: 0,
note: refundCredit.description,
branchId: refundCredit.branchId,
};
};
/**
* Retrieves the refund credit payable GL entry.
* @param {IRefundVendorCredit} refundCredit
* @param {number} APAccountId
* @returns {ILedgerEntry}
*/
private getRefundCreditGLPayableEntry = (
refundCredit: IRefundVendorCredit,
APAccountId: number
): ILedgerEntry => {
const commonEntry = this.getRefundCreditGLCommonEntry(refundCredit);
return {
...commonEntry,
credit: refundCredit.amount,
accountId: APAccountId,
contactId: refundCredit.vendorCredit.vendorId,
index: 1,
accountNormal: AccountNormal.CREDIT,
};
};
/**
* Retrieves the refund credit deposit GL entry.
* @param {IRefundVendorCredit} refundCredit
* @returns {ILedgerEntry}
*/
private getRefundCreditGLDepositEntry = (
refundCredit: IRefundVendorCredit
): ILedgerEntry => {
const commonEntry = this.getRefundCreditGLCommonEntry(refundCredit);
return {
...commonEntry,
debit: refundCredit.amount,
accountId: refundCredit.depositAccountId,
index: 2,
accountNormal: AccountNormal.DEBIT,
};
};
/**
* Retrieve refund vendor credit GL entries.
* @param {IRefundVendorCredit} refundCredit
* @param {number} APAccountId
* @returns {ILedgerEntry[]}
*/
public getRefundCreditGLEntries = (
refundCredit: IRefundVendorCredit,
APAccountId: number
): ILedgerEntry[] => {
const payableEntry = this.getRefundCreditGLPayableEntry(
refundCredit,
APAccountId
);
const depositEntry = this.getRefundCreditGLDepositEntry(refundCredit);
return [payableEntry, depositEntry];
};
/**
* Saves refund credit note GL entries.
* @param {number} tenantId
* @param {IRefundVendorCredit} refundCredit -
* @param {Knex.Transaction} trx -
* @return {Promise<void>}
*/
public saveRefundCreditGLEntries = async (
tenantId: number,
refundCreditId: number,
trx?: Knex.Transaction
): Promise<void> => {
const { Account, RefundVendorCredit } = this.tenancy.models(tenantId);
// Retireve refund with associated vendor credit entity.
const refundCredit = await RefundVendorCredit.query()
.findById(refundCreditId)
.withGraphFetched('vendorCredit');
const payableAccount = await Account.query().findOne(
'slug',
'accounts-payable'
);
// Generates the GL entries of the given refund credit.
const entries = this.getRefundCreditGLEntries(
refundCredit,
payableAccount.id
);
// Saves the ledegr to the storage.
await this.ledgerRepository.saveLedgerEntries(tenantId, entries, trx);
};
/**
* Reverts refund credit note GL entries.
* @param {number} tenantId
* @param {number} refundCreditId
* @param {Knex.Transaction} trx
* @return {Promise<void>}
*/
public revertRefundCreditGLEntries = async (
tenantId: number,
refundCreditId: number,
trx?: Knex.Transaction
) => {
await this.journalService.revertJournalTransactions(
tenantId,
refundCreditId,
'RefundVendorCredit',
trx
);
};
}

View File

@@ -0,0 +1,59 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import RefundVendorCreditGLEntries from './RefundVendorCreditGLEntries';
import {
IRefundCreditNoteDeletedPayload,
IRefundVendorCreditCreatedPayload,
} from '@/interfaces';
@Service()
export default class RefundVendorCreditGLEntriesSubscriber {
@Inject()
refundVendorGLEntries: RefundVendorCreditGLEntries;
/**
* Attaches events with handlers.
*/
attach(bus) {
bus.subscribe(
events.vendorCredit.onRefundCreated,
this.writeRefundVendorCreditGLEntriesOnceCreated
);
bus.subscribe(
events.vendorCredit.onRefundDeleted,
this.revertRefundVendorCreditOnceDeleted
);
}
/**
* Writes refund vendor credit GL entries once the transaction created.
* @param {IRefundCreditNoteCreatedPayload} payload -
*/
private writeRefundVendorCreditGLEntriesOnceCreated = async ({
tenantId,
trx,
refundVendorCredit,
}: IRefundVendorCreditCreatedPayload) => {
await this.refundVendorGLEntries.saveRefundCreditGLEntries(
tenantId,
refundVendorCredit.id,
trx
);
};
/**
* Reverts refund vendor credit GL entries once the transaction deleted.
* @param {IRefundCreditNoteDeletedPayload} payload -
*/
private revertRefundVendorCreditOnceDeleted = async ({
tenantId,
trx,
refundCreditId,
}: IRefundCreditNoteDeletedPayload) => {
await this.refundVendorGLEntries.revertRefundCreditGLEntries(
tenantId,
refundCreditId,
trx
);
};
}

View File

@@ -0,0 +1,30 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class RefundVendorCreditTransformer extends Transformer {
/**
* Includeded attributes.
* @returns {string[]}
*/
public includeAttributes = (): string[] => {
return ['formttedAmount', 'formattedDate'];
};
/**
* Formatted amount.
* @returns {string}
*/
protected formttedAmount = (item) => {
return formatNumber(item.amount, {
currencyCode: item.currencyCode,
});
};
/**
* Formatted date.
* @returns {string}
*/
protected formattedDate = (item) => {
return this.formatDate(item.date);
};
}

View File

@@ -0,0 +1,5 @@
export const ERRORS = {
REFUND_VENDOR_CREDIT_NOT_FOUND: 'REFUND_VENDOR_CREDIT_NOT_FOUND',
DEPOSIT_ACCOUNT_INVALID_TYPE: 'DEPOSIT_ACCOUNT_INVALID_TYPE',
VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING: 'VENDOR_CREDIT_HAS_NO_CREDITS_REMAINING'
}

View File

@@ -0,0 +1,27 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import BaseVendorCredit from './BaseVendorCredit';
import { IVendorCreditCreatedPayload } from '@/interfaces';
@Service()
export default class VendorCreditAutoSerialSubscriber {
@Inject()
vendorCreditService: BaseVendorCredit;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(events.vendorCredit.onCreated, this.autoIncrementOnceCreated);
}
/**
* Auto serial increment once the vendor credit created.
* @param {IVendorCreditCreatedPayload} payload
*/
private autoIncrementOnceCreated = ({
tenantId,
}: IVendorCreditCreatedPayload) => {
this.vendorCreditService.incrementSerialNumber(tenantId);
};
}

View File

@@ -0,0 +1,189 @@
import { Inject, Service } from 'typedi';
import { Knex } from 'knex';
import * as R from 'ramda';
import {
IVendorCredit,
ILedgerEntry,
AccountNormal,
IItemEntry,
} from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
import Ledger from '@/services/Accounting/Ledger';
@Service()
export default class VendorCreditGLEntries {
@Inject()
private ledgerStorage: LedgerStorageService;
@Inject()
private tenancy: HasTenancyService;
/**
* Retrieve the vendor credit GL common entry.
* @param {IVendorCredit} vendorCredit
* @returns {}
*/
public getVendorCreditGLCommonEntry = (vendorCredit: IVendorCredit) => {
return {
date: vendorCredit.vendorCreditDate,
currencyCode: vendorCredit.currencyCode,
exchangeRate: vendorCredit.exchangeRate,
transactionId: vendorCredit.id,
transactionType: 'VendorCredit',
transactionNumber: vendorCredit.vendorCreditNumber,
referenceNumber: vendorCredit.referenceNo,
credit: 0,
debit: 0,
branchId: vendorCredit.branchId,
};
};
/**
* Retrieves the vendor credit payable GL entry.
* @param {IVendorCredit} vendorCredit
* @param {number} APAccountId
* @returns {ILedgerEntry}
*/
public getVendorCreditPayableGLEntry = (
vendorCredit: IVendorCredit,
APAccountId: number
): ILedgerEntry => {
const commonEntity = this.getVendorCreditGLCommonEntry(vendorCredit);
return {
...commonEntity,
debit: vendorCredit.localAmount,
accountId: APAccountId,
contactId: vendorCredit.vendorId,
accountNormal: AccountNormal.CREDIT,
index: 1,
};
};
/**
* Retrieves the vendor credit item GL entry.
* @param {IVendorCredit} vendorCredit
* @param {IItemEntry} entry
* @returns {ILedgerEntry}
*/
public getVendorCreditGLItemEntry = R.curry(
(
vendorCredit: IVendorCredit,
entry: IItemEntry,
index: number
): ILedgerEntry => {
const commonEntity = this.getVendorCreditGLCommonEntry(vendorCredit);
const localAmount = entry.amount * vendorCredit.exchangeRate;
return {
...commonEntity,
credit: localAmount,
index: index + 2,
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountId:
'inventory' === entry.item.type
? entry.item.inventoryAccountId
: entry.costAccountId || entry.item.costAccountId,
accountNormal: AccountNormal.DEBIT,
};
}
);
/**
* Retrieve the vendor credit GL entries.
* @param {IVendorCredit} vendorCredit -
* @param {number} receivableAccount -
* @return {ILedgerEntry[]}
*/
public getVendorCreditGLEntries = (
vendorCredit: IVendorCredit,
payableAccountId: number
): ILedgerEntry[] => {
const payableEntry = this.getVendorCreditPayableGLEntry(
vendorCredit,
payableAccountId
);
const getItemEntry = this.getVendorCreditGLItemEntry(vendorCredit);
const itemsEntries = vendorCredit.entries.map(getItemEntry);
return [payableEntry, ...itemsEntries];
};
/**
* Reverts the vendor credit associated GL entries.
* @param {number} tenantId
* @param {number} vendorCreditId
* @param {Knex.Transaction} trx
*/
public revertVendorCreditGLEntries = async (
tenantId: number,
vendorCreditId: number,
trx?: Knex.Transaction
): Promise<void> => {
await this.ledgerStorage.deleteByReference(
tenantId,
vendorCreditId,
'VendorCredit',
trx
);
};
/**
* Creates vendor credit associated GL entries.
* @param {number} tenantId
* @param {number} vendorCreditId
* @param {Knex.Transaction} trx
*/
public writeVendorCreditGLEntries = async (
tenantId: number,
vendorCreditId: number,
trx?: Knex.Transaction
) => {
const { accountRepository } = this.tenancy.repositories(tenantId);
const { VendorCredit } = this.tenancy.models(tenantId);
// Vendor credit with entries items.
const vendorCredit = await VendorCredit.query(trx)
.findById(vendorCreditId)
.withGraphFetched('entries.item');
// Retrieve the payable account (A/P) account.
const APAccount = await accountRepository.findOrCreateAccountsPayable(
vendorCredit.currencyCode,
{},
trx
);
// Saves the vendor credit GL entries.
const ledgerEntries = this.getVendorCreditGLEntries(
vendorCredit,
APAccount.id
);
const ledger = new Ledger(ledgerEntries);
// Commits the ledger entries to the storage.
await this.ledgerStorage.commit(tenantId, ledger, trx);
};
/**
* Edits vendor credit associated GL entries.
* @param {number} tenantId
* @param {number} vendorCreditId
* @param {Knex.Transaction} trx
*/
public rewriteVendorCreditGLEntries = async (
tenantId: number,
vendorCreditId: number,
trx?: Knex.Transaction
) => {
// Reverts the GL entries.
await this.revertVendorCreditGLEntries(tenantId, vendorCreditId, trx);
// Re-write the GL entries.
await this.writeVendorCreditGLEntries(tenantId, vendorCreditId, trx);
};
}

View File

@@ -0,0 +1,110 @@
import { Service, Inject } from 'typedi';
import events from '@/subscribers/events';
import {
IVendorCreditCreatedPayload,
IVendorCreditDeletedPayload,
IVendorCreditEditedPayload,
IVendorCreditOpenedPayload,
} from '@/interfaces';
import VendorCreditGLEntries from './VendorCreditGLEntries';
@Service()
export default class VendorCreditGlEntriesSubscriber {
@Inject()
private vendorCreditGLEntries: VendorCreditGLEntries;
/***
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.vendorCredit.onCreated,
this.writeGLEntriesOnceVendorCreditCreated
);
bus.subscribe(
events.vendorCredit.onOpened,
this.writeGLEntgriesOnceVendorCreditOpened
);
bus.subscribe(
events.vendorCredit.onEdited,
this.editGLEntriesOnceVendorCreditEdited
);
bus.subscribe(
events.vendorCredit.onDeleted,
this.revertGLEntriesOnceDeleted
);
}
/**
* Writes GL entries of vendor credit once the transaction created.
* @param {IVendorCreditCreatedPayload} payload -
*/
private writeGLEntriesOnceVendorCreditCreated = async ({
tenantId,
vendorCredit,
trx,
}: IVendorCreditCreatedPayload): Promise<void> => {
// Can't continue if the vendor credit is not open yet.
if (!vendorCredit.isPublished) return;
await this.vendorCreditGLEntries.writeVendorCreditGLEntries(
tenantId,
vendorCredit.id,
trx
);
};
/**
* Writes Gl entries of vendor credit once the transaction opened.
* @param {IVendorCreditOpenedPayload} payload -
*/
private writeGLEntgriesOnceVendorCreditOpened = async ({
tenantId,
vendorCreditId,
trx,
}: IVendorCreditOpenedPayload) => {
await this.vendorCreditGLEntries.writeVendorCreditGLEntries(
tenantId,
vendorCreditId,
trx
);
};
/**
* Edits assocaited GL entries once vendor credit edited.
* @param {IVendorCreditEditedPayload} payload
*/
private editGLEntriesOnceVendorCreditEdited = async ({
tenantId,
vendorCreditId,
vendorCredit,
trx,
}: IVendorCreditEditedPayload) => {
// Can't continue if the vendor credit is not open yet.
if (!vendorCredit.isPublished) return;
await this.vendorCreditGLEntries.rewriteVendorCreditGLEntries(
tenantId,
vendorCreditId,
trx
);
};
/**
* Reverts the GL entries once vendor credit deleted.
* @param {IVendorCreditDeletedPayload} payload -
*/
private revertGLEntriesOnceDeleted = async ({
vendorCreditId,
tenantId,
oldVendorCredit,
}: IVendorCreditDeletedPayload): Promise<void> => {
// Can't continue of the vendor credit is not open yet.
if (!oldVendorCredit.isPublished) return;
await this.vendorCreditGLEntries.revertVendorCreditGLEntries(
tenantId,
vendorCreditId
);
};
}

View File

@@ -0,0 +1,92 @@
import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import { IVendorCredit } from '@/interfaces';
import InventoryService from '@/services/Inventory/Inventory';
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
@Service()
export default class VendorCreditInventoryTransactions {
@Inject()
inventoryService: InventoryService;
@Inject()
itemsEntriesService: ItemsEntriesService;
/**
* Creates vendor credit associated inventory transactions.
* @param {number} tenantId
* @param {IVnedorCredit} vendorCredit
* @param {Knex.Transaction} trx
*/
public createInventoryTransactions = async (
tenantId: number,
vendorCredit: IVendorCredit,
trx: Knex.Transaction
): Promise<void> => {
// Loads the inventory items entries of the given sale invoice.
const inventoryEntries =
await this.itemsEntriesService.filterInventoryEntries(
tenantId,
vendorCredit.entries
);
const transaction = {
transactionId: vendorCredit.id,
transactionType: 'VendorCredit',
transactionNumber: vendorCredit.vendorCreditNumber,
exchangeRate: vendorCredit.exchangeRate,
date: vendorCredit.vendorCreditDate,
direction: 'OUT',
entries: inventoryEntries,
warehouseId: vendorCredit.warehouseId,
createdAt: vendorCredit.createdAt,
};
// Writes inventory tranactions.
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
tenantId,
transaction,
false,
trx
);
};
/**
* Edits vendor credit assocaited inventory transactions.
* @param {number} tenantId
* @param {number} creditNoteId
* @param {ICreditNote} creditNote
* @param {Knex.Transactions} trx
*/
public editInventoryTransactions = async (
tenantId: number,
vendorCreditId: number,
vendorCredit: IVendorCredit,
trx?: Knex.Transaction
): Promise<void> => {
// Deletes inventory transactions.
await this.deleteInventoryTransactions(tenantId, vendorCreditId, trx);
// Re-write inventory transactions.
await this.createInventoryTransactions(tenantId, vendorCredit, trx);
};
/**
* Deletes credit note associated inventory transactions.
* @param {number} tenantId - Tenant id.
* @param {number} creditNoteId - Credit note id.
* @param {Knex.Transaction} trx -
*/
public deleteInventoryTransactions = async (
tenantId: number,
vendorCreditId: number,
trx?: Knex.Transaction
): Promise<void> => {
// Deletes the inventory transactions by the given reference id and type.
await this.inventoryService.deleteInventoryTransactions(
tenantId,
vendorCreditId,
'VendorCredit',
trx
);
};
}

View File

@@ -0,0 +1,83 @@
import { Inject, Service } from 'typedi';
import events from '@/subscribers/events';
import {
IVendorCreditCreatedPayload,
IVendorCreditDeletedPayload,
IVendorCreditEditedPayload,
} from '@/interfaces';
import VendorCreditInventoryTransactions from './VendorCreditInventoryTransactions';
@Service()
export default class VendorCreditInventoryTransactionsSubscriber {
@Inject()
inventoryTransactions: VendorCreditInventoryTransactions;
/**
* Attaches events with handlers.
* @param bus
*/
attach(bus) {
bus.subscribe(
events.vendorCredit.onCreated,
this.writeInventoryTransactionsOnceCreated
);
bus.subscribe(
events.vendorCredit.onEdited,
this.rewriteInventroyTransactionsOnceEdited
);
bus.subscribe(
events.vendorCredit.onDeleted,
this.revertInventoryTransactionsOnceDeleted
);
}
/**
* Writes inventory transactions once vendor created created.
* @param {IVendorCreditCreatedPayload} payload -
*/
private writeInventoryTransactionsOnceCreated = async ({
tenantId,
vendorCredit,
trx,
}: IVendorCreditCreatedPayload) => {
await this.inventoryTransactions.createInventoryTransactions(
tenantId,
vendorCredit,
trx
);
};
/**
* Rewrites inventory transactions once vendor credit edited.
* @param {IVendorCreditEditedPayload} payload -
*/
private rewriteInventroyTransactionsOnceEdited = async ({
tenantId,
vendorCreditId,
vendorCredit,
trx,
}: IVendorCreditEditedPayload) => {
await this.inventoryTransactions.editInventoryTransactions(
tenantId,
vendorCreditId,
vendorCredit,
trx
);
};
/**
* Reverts inventory transactions once vendor credit deleted.
* @param {IVendorCreditDeletedPayload} payload -
*/
private revertInventoryTransactionsOnceDeleted = async ({
tenantId,
vendorCreditId,
trx,
}: IVendorCreditDeletedPayload) => {
await this.inventoryTransactions.deleteInventoryTransactions(
tenantId,
vendorCreditId,
trx
);
};
}

View File

@@ -0,0 +1,47 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
export class VendorCreditTransformer extends Transformer {
/**
* Include these attributes to vendor credit object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'formattedVendorCreditDate',
'formattedAmount',
'formattedCreditsRemaining',
];
};
/**
* Retrieve formatted vendor credit date.
* @param {IVendorCredit} credit
* @returns {String}
*/
protected formattedVendorCreditDate = (vendorCredit): string => {
return this.formatDate(vendorCredit.vendorCreditDate);
};
/**
* Retrieve formatted vendor credit amount.
* @param {IVendorCredit} credit
* @returns {string}
*/
protected formattedAmount = (vendorCredit): string => {
return formatNumber(vendorCredit.amount, {
currencyCode: vendorCredit.currencyCode,
});
};
/**
* Retrieve formatted credits remaining.
* @param {IVendorCredit} credit
* @returns {string}
*/
protected formattedCreditsRemaining = (credit) => {
return formatNumber(credit.creditsRemaining, {
currencyCode: credit.currencyCode,
});
};
}

View File

@@ -0,0 +1,64 @@
export const ERRORS = {
VENDOR_CREDIT_NOT_FOUND: 'VENDOR_CREDIT_NOT_FOUND',
VENDOR_CREDIT_ALREADY_OPENED: 'VENDOR_CREDIT_ALREADY_OPENED',
VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT: 'VENDOR_CREDIT_HAS_NO_REMAINING_AMOUNT',
VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND: 'VENDOR_CREDIT_APPLY_TO_BILLS_NOT_FOUND',
BILLS_HAS_NO_REMAINING_AMOUNT: 'BILLS_HAS_NO_REMAINING_AMOUNT',
VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS: 'VENDOR_CREDIT_HAS_REFUND_TRANSACTIONS',
VENDOR_CREDIT_HAS_APPLIED_BILLS: 'VENDOR_CREDIT_HAS_APPLIED_BILLS'
};
export const DEFAULT_VIEW_COLUMNS = [];
export const DEFAULT_VIEWS = [
{
name: 'vendor_credit.view.draft',
slug: 'draft',
rolesLogicExpression: '1',
roles: [
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' },
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'vendor_credit.view.published',
slug: 'published',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'published',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'vendor_credit.view.open',
slug: 'open',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'open',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
{
name: 'vendor_credit.view.closed',
slug: 'closed',
rolesLogicExpression: '1',
roles: [
{
index: 1,
fieldKey: 'status',
comparator: 'equals',
value: 'closed',
},
],
columns: DEFAULT_VIEW_COLUMNS,
},
];