mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 04:10:32 +00:00
add server to monorepo.
This commit is contained in:
93
packages/server/src/services/CreditNotes/CreateCreditNote.ts
Normal file
93
packages/server/src/services/CreditNotes/CreateCreditNote.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import {
|
||||
ICreditNoteCreatedPayload,
|
||||
ICreditNoteCreatingPayload,
|
||||
ICreditNoteNewDTO,
|
||||
ISystemUser,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
|
||||
@Service()
|
||||
export default class CreateCreditNote extends BaseCreditNotes {
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Creates a new credit note.
|
||||
* @param creditNoteDTO
|
||||
*/
|
||||
public newCreditNote = async (
|
||||
tenantId: number,
|
||||
creditNoteDTO: ICreditNoteNewDTO,
|
||||
authorizedUser: ISystemUser
|
||||
) => {
|
||||
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Triggers `onCreditNoteCreate` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onCreate, {
|
||||
tenantId,
|
||||
creditNoteDTO,
|
||||
});
|
||||
// Validate customer existance.
|
||||
const customer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(creditNoteDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
creditNoteDTO.entries
|
||||
);
|
||||
// Validate items should be sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
creditNoteDTO.entries
|
||||
);
|
||||
// Transformes the given DTO to storage layer data.
|
||||
const creditNoteModel = this.transformCreateEditDTOToModel(
|
||||
tenantId,
|
||||
creditNoteDTO,
|
||||
customer.currencyCode
|
||||
);
|
||||
// Creates a new credit card transactions under unit-of-work envirement.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onCreditNoteCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onCreating, {
|
||||
tenantId,
|
||||
creditNoteDTO,
|
||||
trx,
|
||||
} as ICreditNoteCreatingPayload);
|
||||
|
||||
// Upsert the credit note graph.
|
||||
const creditNote = await CreditNote.query(trx).upsertGraph({
|
||||
...creditNoteModel,
|
||||
});
|
||||
// Triggers `onCreditNoteCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onCreated, {
|
||||
tenantId,
|
||||
creditNoteDTO,
|
||||
creditNote,
|
||||
creditNoteId: creditNote.id,
|
||||
trx,
|
||||
} as ICreditNoteCreatedPayload);
|
||||
|
||||
return creditNote;
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
ICreditNote,
|
||||
ICreditNoteRefundDTO,
|
||||
IRefundCreditNote,
|
||||
IRefundCreditNoteCreatedPayload,
|
||||
IRefundCreditNoteCreatingPayload,
|
||||
} 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 RefundCreditNote from './RefundCreditNote';
|
||||
|
||||
@Service()
|
||||
export default class CreateRefundCreditNote extends RefundCreditNote {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve the credit note graph.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<IRefundCreditNote>}
|
||||
*/
|
||||
public createCreditNoteRefund = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
newCreditNoteDTO: ICreditNoteRefundDTO
|
||||
): Promise<IRefundCreditNote> => {
|
||||
const { RefundCreditNote, Account } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the credit note or throw not found service error.
|
||||
const creditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Retrieve the withdrawal account or throw not found service error.
|
||||
const fromAccount = await Account.query()
|
||||
.findById(newCreditNoteDTO.fromAccountId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate the credit note remaining amount.
|
||||
this.validateCreditRemainingAmount(creditNote, newCreditNoteDTO.amount);
|
||||
|
||||
// Validate the refund withdrawal account type.
|
||||
this.validateRefundWithdrawwalAccountType(fromAccount);
|
||||
|
||||
// Creates a refund credit note transaction.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onCreditNoteRefundCreating` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onRefundCreating, {
|
||||
trx,
|
||||
creditNote,
|
||||
tenantId,
|
||||
newCreditNoteDTO,
|
||||
} as IRefundCreditNoteCreatingPayload);
|
||||
|
||||
// Stores the refund credit note graph to the storage layer.
|
||||
const refundCreditNote = await RefundCreditNote.query(trx).insertAndFetch(
|
||||
{
|
||||
...this.transformDTOToModel(creditNote, newCreditNoteDTO),
|
||||
}
|
||||
);
|
||||
// Triggers `onCreditNoteRefundCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onRefundCreated, {
|
||||
trx,
|
||||
refundCreditNote,
|
||||
creditNote,
|
||||
tenantId,
|
||||
} as IRefundCreditNoteCreatedPayload);
|
||||
|
||||
return refundCreditNote;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes the refund credit note DTO to model.
|
||||
* @param {number} creditNoteId
|
||||
* @param {ICreditNoteRefundDTO} creditNoteDTO
|
||||
* @returns {ICreditNote}
|
||||
*/
|
||||
private transformDTOToModel = (
|
||||
creditNote: ICreditNote,
|
||||
creditNoteDTO: ICreditNoteRefundDTO
|
||||
): IRefundCreditNote => {
|
||||
return {
|
||||
creditNoteId: creditNote.id,
|
||||
currencyCode: creditNote.currencyCode,
|
||||
...creditNoteDTO,
|
||||
exchangeRate: creditNoteDTO.exchangeRate || 1,
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class CreditNoteAppliedInvoiceTransformer extends Transformer {
|
||||
/**
|
||||
* Includeded attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formttedAmount',
|
||||
'creditNoteNumber',
|
||||
'creditNoteDate',
|
||||
'invoiceNumber',
|
||||
'invoiceReferenceNo',
|
||||
'formattedCreditNoteDate',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Exclude attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['saleInvoice', 'creditNote'];
|
||||
};
|
||||
|
||||
formttedAmount = (item) => {
|
||||
return formatNumber(item.amount, {
|
||||
currencyCode: item.creditNote.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
creditNoteNumber = (item) => {
|
||||
return item.creditNote.creditNoteNumber;
|
||||
};
|
||||
|
||||
creditNoteDate = (item) => {
|
||||
return item.creditNote.creditNoteDate;
|
||||
};
|
||||
|
||||
invoiceNumber = (item) => {
|
||||
return item.saleInvoice.invoiceNo;
|
||||
};
|
||||
|
||||
invoiceReferenceNo = (item) => {
|
||||
return item.saleInvoice.referenceNo;
|
||||
};
|
||||
|
||||
formattedCreditNoteDate = (item) => {
|
||||
return this.formatDate(item.creditNote.creditNoteDate);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import Knex from 'knex';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Service, Inject } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteApplySyncCredit {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Increment credit note invoiced amount.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @param {number} invoicesAppliedAmount
|
||||
*/
|
||||
public incrementCreditNoteInvoicedAmount = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
invoicesAppliedAmount: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
await CreditNote.query(trx)
|
||||
.findById(creditNoteId)
|
||||
.increment('invoicesAmount', invoicesAppliedAmount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrement credit note invoiced amount.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @param {number} invoicesAppliedAmount
|
||||
*/
|
||||
public decrementCreditNoteInvoicedAmount = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
invoicesAppliedAmount: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
await CreditNote.query(trx)
|
||||
.findById(creditNoteId)
|
||||
.decrement('invoicesAmount', invoicesAppliedAmount);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { sumBy } from 'lodash';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IApplyCreditToInvoicesCreatedPayload,
|
||||
IApplyCreditToInvoicesDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import CreditNoteApplySyncCredit from './CreditNoteApplySyncCredit';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteApplySyncCreditSubscriber {
|
||||
@Inject()
|
||||
syncInvoicedAmountWithCredit: CreditNoteApplySyncCredit;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bus
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.creditNote.onApplyToInvoicesCreated,
|
||||
this.incrementCreditedAmountOnceApplyToInvoicesCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onApplyToInvoicesDeleted,
|
||||
this.decrementCreditedAmountOnceApplyToInvoicesDeleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment credited amount of credit note transaction once the transaction created.
|
||||
* @param {IApplyCreditToInvoicesCreatedPayload} payload -
|
||||
*/
|
||||
private incrementCreditedAmountOnceApplyToInvoicesCreated = async ({
|
||||
trx,
|
||||
creditNote,
|
||||
tenantId,
|
||||
creditNoteAppliedInvoices,
|
||||
}: IApplyCreditToInvoicesCreatedPayload) => {
|
||||
const totalCredited = sumBy(creditNoteAppliedInvoices, 'amount');
|
||||
|
||||
await this.syncInvoicedAmountWithCredit.incrementCreditNoteInvoicedAmount(
|
||||
tenantId,
|
||||
creditNote.id,
|
||||
totalCredited,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrement credited amount of credit note transaction once the transaction deleted.
|
||||
* @param {IApplyCreditToInvoicesDeletedPayload} payload -
|
||||
*/
|
||||
private decrementCreditedAmountOnceApplyToInvoicesDeleted = async ({
|
||||
tenantId,
|
||||
creditNote,
|
||||
creditNoteAppliedToInvoice,
|
||||
trx,
|
||||
}: IApplyCreditToInvoicesDeletedPayload) => {
|
||||
await this.syncInvoicedAmountWithCredit.decrementCreditNoteInvoicedAmount(
|
||||
tenantId,
|
||||
creditNote.id,
|
||||
creditNoteAppliedToInvoice.amount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Knex from 'knex';
|
||||
import Bluebird from 'bluebird';
|
||||
import { ICreditNoteAppliedToInvoice } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteApplySyncInvoicesCreditedAmount {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Increment invoices credited amount.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICreditNoteAppliedToInvoice[]} creditNoteAppliedInvoices -
|
||||
* @param {Knex.Transaction} trx -
|
||||
*/
|
||||
public incrementInvoicesCreditedAmount = async (
|
||||
tenantId,
|
||||
creditNoteAppliedInvoices: ICreditNoteAppliedToInvoice[],
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
await Bluebird.each(
|
||||
creditNoteAppliedInvoices,
|
||||
(creditNoteAppliedInvoice: ICreditNoteAppliedToInvoice) => {
|
||||
return SaleInvoice.query(trx)
|
||||
.where('id', creditNoteAppliedInvoice.invoiceId)
|
||||
.increment('creditedAmount', creditNoteAppliedInvoice.amount);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param tenantId
|
||||
* @param invoicesIds
|
||||
* @param amount
|
||||
*/
|
||||
public decrementInvoiceCreditedAmount = async (
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
amount: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
await SaleInvoice.query(trx)
|
||||
.findById(invoiceId)
|
||||
.decrement('creditedAmount', amount);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
IApplyCreditToInvoicesCreatedPayload,
|
||||
IApplyCreditToInvoicesDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import CreditNoteApplySyncInvoicesCreditedAmount from './CreditNoteApplySyncInvoices';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteApplySyncInvoicesCreditedAmountSubscriber {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
syncInvoicesWithCreditNote: CreditNoteApplySyncInvoicesCreditedAmount;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.creditNote.onApplyToInvoicesCreated,
|
||||
this.incrementAppliedInvoicesOnceCreditCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onApplyToInvoicesDeleted,
|
||||
this.decrementAppliedInvoicesOnceCreditDeleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment invoices credited amount once the credit note apply to invoices transaction
|
||||
* @param {IApplyCreditToInvoicesCreatedPayload} payload -
|
||||
*/
|
||||
private incrementAppliedInvoicesOnceCreditCreated = async ({
|
||||
trx,
|
||||
tenantId,
|
||||
creditNoteAppliedInvoices,
|
||||
}: IApplyCreditToInvoicesCreatedPayload) => {
|
||||
await this.syncInvoicesWithCreditNote.incrementInvoicesCreditedAmount(
|
||||
tenantId,
|
||||
creditNoteAppliedInvoices,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {IApplyCreditToInvoicesDeletedPayload} payload -
|
||||
*/
|
||||
private decrementAppliedInvoicesOnceCreditDeleted = async ({
|
||||
trx,
|
||||
creditNoteAppliedToInvoice,
|
||||
tenantId,
|
||||
}: IApplyCreditToInvoicesDeletedPayload) => {
|
||||
// Decrement invoice credited amount.
|
||||
await this.syncInvoicesWithCreditNote.decrementInvoiceCreditedAmount(
|
||||
tenantId,
|
||||
creditNoteAppliedToInvoice.invoiceId,
|
||||
creditNoteAppliedToInvoice.amount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import Knex from 'knex';
|
||||
import { sumBy } from 'lodash';
|
||||
import {
|
||||
ICreditNote,
|
||||
ICreditNoteAppliedToInvoice,
|
||||
ICreditNoteAppliedToInvoiceModel,
|
||||
ISaleInvoice,
|
||||
} 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 BaseCreditNotes from './CreditNotes';
|
||||
import {
|
||||
IApplyCreditToInvoicesDTO,
|
||||
IApplyCreditToInvoicesCreatedPayload,
|
||||
} from '@/interfaces';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||
@Inject('PaymentReceives')
|
||||
paymentReceive: PaymentReceiveService;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Apply credit note to the given invoices.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
|
||||
*/
|
||||
public applyCreditNoteToInvoices = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
applyCreditToInvoicesDTO: IApplyCreditToInvoicesDTO
|
||||
): Promise<ICreditNoteAppliedToInvoice[]> => {
|
||||
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Saves the credit note or throw not found service error.
|
||||
const creditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Retrieve the applied invoices that associated to the credit note customer.
|
||||
const appliedInvoicesEntries =
|
||||
await this.paymentReceive.validateInvoicesIDsExistance(
|
||||
tenantId,
|
||||
creditNote.customerId,
|
||||
applyCreditToInvoicesDTO.entries
|
||||
);
|
||||
// Transformes apply DTO to model.
|
||||
const creditNoteAppliedModel = this.transformApplyDTOToModel(
|
||||
applyCreditToInvoicesDTO,
|
||||
creditNote
|
||||
);
|
||||
// Validate invoices has remaining amount to apply.
|
||||
this.validateInvoicesRemainingAmount(
|
||||
appliedInvoicesEntries,
|
||||
creditNoteAppliedModel.amount
|
||||
);
|
||||
// Validate the credit note remaining amount.
|
||||
this.validateCreditRemainingAmount(
|
||||
creditNote,
|
||||
creditNoteAppliedModel.amount
|
||||
);
|
||||
// Creates credit note apply to invoice transaction.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Saves the credit note apply to invoice graph to the storage layer.
|
||||
const creditNoteAppliedInvoices =
|
||||
await CreditNoteAppliedInvoice.query().insertGraph(
|
||||
creditNoteAppliedModel.entries
|
||||
);
|
||||
// Triggers `onCreditNoteApplyToInvoiceCreated` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.creditNote.onApplyToInvoicesCreated,
|
||||
{
|
||||
tenantId,
|
||||
creditNote,
|
||||
creditNoteAppliedInvoices,
|
||||
trx,
|
||||
} as IApplyCreditToInvoicesCreatedPayload
|
||||
);
|
||||
return creditNoteAppliedInvoices;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transformes apply DTO to model.
|
||||
* @param {IApplyCreditToInvoicesDTO} applyDTO
|
||||
* @param {ICreditNote} creditNote
|
||||
* @returns
|
||||
*/
|
||||
private transformApplyDTOToModel = (
|
||||
applyDTO: IApplyCreditToInvoicesDTO,
|
||||
creditNote: ICreditNote
|
||||
): ICreditNoteAppliedToInvoiceModel => {
|
||||
const entries = applyDTO.entries.map((entry) => ({
|
||||
invoiceId: entry.invoiceId,
|
||||
amount: entry.amount,
|
||||
creditNoteId: creditNote.id,
|
||||
}));
|
||||
return {
|
||||
amount: sumBy(entries, 'amount'),
|
||||
entries,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the invoice remaining amount.
|
||||
* @param {ISaleInvoice[]} invoices
|
||||
* @param {number} amount
|
||||
*/
|
||||
private validateInvoicesRemainingAmount = (
|
||||
invoices: ISaleInvoice[],
|
||||
amount: number
|
||||
) => {
|
||||
const invalidInvoices = invoices.filter(
|
||||
(invoice) => invoice.dueAmount < amount
|
||||
);
|
||||
if (invalidInvoices.length > 0) {
|
||||
throw new ServiceError(ERRORS.INVOICES_HAS_NO_REMAINING_AMOUNT);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { ICreditNoteCreatedPayload } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteAutoSerialSubscriber {
|
||||
@Inject()
|
||||
creditNotesService: BaseCreditNotes;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.creditNote.onCreated,
|
||||
this.autoSerialIncrementOnceCreated
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto serial increment once credit note created.
|
||||
* @param {ICreditNoteCreatedPayload} payload -
|
||||
*/
|
||||
private autoSerialIncrementOnceCreated = async ({
|
||||
tenantId,
|
||||
}: ICreditNoteCreatedPayload) => {
|
||||
await this.creditNotesService.incrementSerialNumber(tenantId);
|
||||
};
|
||||
}
|
||||
226
packages/server/src/services/CreditNotes/CreditNoteGLEntries.ts
Normal file
226
packages/server/src/services/CreditNotes/CreditNoteGLEntries.ts
Normal file
@@ -0,0 +1,226 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
AccountNormal,
|
||||
IItemEntry,
|
||||
ILedgerEntry,
|
||||
ICreditNote,
|
||||
ILedger,
|
||||
ICreditNoteGLCommonEntry,
|
||||
} from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteGLEntries {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private ledgerStorage: LedgerStorageService;
|
||||
|
||||
/**
|
||||
* Retrieves the credit note GL.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} receivableAccount
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
private getCreditNoteGLedger = (
|
||||
creditNote: ICreditNote,
|
||||
receivableAccount: number
|
||||
): Ledger => {
|
||||
const ledgerEntries = this.getCreditNoteGLEntries(
|
||||
creditNote,
|
||||
receivableAccount
|
||||
);
|
||||
return new Ledger(ledgerEntries);
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves credit note GL entries.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICreditNote} creditNote - Credit note model.
|
||||
* @param {number} payableAccount - Payable account id.
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public saveCreditNoteGLEntries = async (
|
||||
tenantId: number,
|
||||
creditNote: ICreditNote,
|
||||
payableAccount: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const ledger = this.getCreditNoteGLedger(creditNote, payableAccount);
|
||||
|
||||
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the credit note associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} vendorCreditId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public revertVendorCreditGLEntries = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
'CreditNote',
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes vendor credit associated GL entries.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @param {Knex.Transaction} trx - Knex transactions.
|
||||
*/
|
||||
public createVendorCreditGLEntries = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve the credit note with associated entries and items.
|
||||
const creditNoteWithItems = await CreditNote.query(trx)
|
||||
.findById(creditNoteId)
|
||||
.withGraphFetched('entries.item');
|
||||
|
||||
// Retreive the the `accounts receivable` account based on the given currency.
|
||||
const ARAccount = await accountRepository.findOrCreateAccountReceivable(
|
||||
creditNoteWithItems.currencyCode
|
||||
);
|
||||
// Saves the credit note GL entries.
|
||||
await this.saveCreditNoteGLEntries(
|
||||
tenantId,
|
||||
creditNoteWithItems,
|
||||
ARAccount.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits vendor credit associated GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public editVendorCreditGLEntries = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
// Reverts vendor credit GL entries.
|
||||
await this.revertVendorCreditGLEntries(tenantId, creditNoteId, trx);
|
||||
|
||||
// Creates vendor credit Gl entries.
|
||||
await this.createVendorCreditGLEntries(tenantId, creditNoteId, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the credit note common entry.
|
||||
* @param {ICreditNote} creditNote -
|
||||
* @returns {ICreditNoteGLCommonEntry}
|
||||
*/
|
||||
private getCreditNoteCommonEntry = (
|
||||
creditNote: ICreditNote
|
||||
): ICreditNoteGLCommonEntry => {
|
||||
return {
|
||||
date: creditNote.creditNoteDate,
|
||||
userId: creditNote.userId,
|
||||
currencyCode: creditNote.currencyCode,
|
||||
exchangeRate: creditNote.exchangeRate,
|
||||
|
||||
transactionType: 'CreditNote',
|
||||
transactionId: creditNote.id,
|
||||
|
||||
transactionNumber: creditNote.creditNoteNumber,
|
||||
referenceNumber: creditNote.referenceNo,
|
||||
|
||||
createdAt: creditNote.createdAt,
|
||||
indexGroup: 10,
|
||||
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
|
||||
branchId: creditNote.branchId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the creidt note A/R entry.
|
||||
* @param {ICreditNote} creditNote -
|
||||
* @param {number} ARAccountId -
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getCreditNoteAREntry = (
|
||||
creditNote: ICreditNote,
|
||||
ARAccountId: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCreditNoteCommonEntry(creditNote);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: creditNote.localAmount,
|
||||
accountId: ARAccountId,
|
||||
contactId: creditNote.customerId,
|
||||
index: 1,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the credit note item entry.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {IItemEntry} entry
|
||||
* @param {number} index
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getCreditNoteItemEntry = R.curry(
|
||||
(
|
||||
creditNote: ICreditNote,
|
||||
entry: IItemEntry,
|
||||
index: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCreditNoteCommonEntry(creditNote);
|
||||
const localAmount = entry.amount * creditNote.exchangeRate;
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: localAmount,
|
||||
accountId: entry.sellAccountId || entry.item.sellAccountId,
|
||||
note: entry.description,
|
||||
index: index + 2,
|
||||
itemId: entry.itemId,
|
||||
itemQuantity: entry.quantity,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieve the credit note GL entries.
|
||||
* @param {ICreditNote} creditNote - Credit note.
|
||||
* @param {IAccount} receivableAccount - Receviable account.
|
||||
* @returns {ILedgerEntry[]} - Ledger entries.
|
||||
*/
|
||||
public getCreditNoteGLEntries = (
|
||||
creditNote: ICreditNote,
|
||||
ARAccountId: number
|
||||
): ILedgerEntry[] => {
|
||||
const AREntry = this.getCreditNoteAREntry(creditNote, ARAccountId);
|
||||
|
||||
const getItemEntry = this.getCreditNoteItemEntry(creditNote);
|
||||
const itemsEntries = creditNote.entries.map(getItemEntry);
|
||||
|
||||
return [AREntry, ...itemsEntries];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import {
|
||||
ICreditNoteCreatedPayload,
|
||||
ICreditNoteDeletedPayload,
|
||||
ICreditNoteEditedPayload,
|
||||
ICreditNoteOpenedPayload,
|
||||
IRefundCreditNoteOpenedPayload,
|
||||
} from '@/interfaces';
|
||||
import CreditNoteGLEntries from './CreditNoteGLEntries';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteGLEntriesSubscriber {
|
||||
@Inject()
|
||||
creditNoteGLEntries: CreditNoteGLEntries;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.creditNote.onCreated,
|
||||
this.writeGlEntriesOnceCreditNoteCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onOpened,
|
||||
this.writeGLEntriesOnceCreditNoteOpened
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onEdited,
|
||||
this.editVendorCreditGLEntriesOnceEdited
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onDeleted,
|
||||
this.revertGLEntriesOnceCreditNoteDeleted
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the GL entries once the credit note transaction created or open.
|
||||
* @private
|
||||
* @param {ICreditNoteCreatedPayload|ICreditNoteOpenedPayload} payload -
|
||||
*/
|
||||
private writeGlEntriesOnceCreditNoteCreated = async ({
|
||||
tenantId,
|
||||
creditNote,
|
||||
creditNoteId,
|
||||
trx,
|
||||
}: ICreditNoteCreatedPayload | ICreditNoteOpenedPayload) => {
|
||||
// Can't continue if the credit note is not published yet.
|
||||
if (!creditNote.isPublished) return;
|
||||
|
||||
await this.creditNoteGLEntries.createVendorCreditGLEntries(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes the GL entries once the vendor credit transaction opened.
|
||||
* @param {ICreditNoteOpenedPayload} payload
|
||||
*/
|
||||
private writeGLEntriesOnceCreditNoteOpened = async ({
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
trx,
|
||||
}: ICreditNoteOpenedPayload) => {
|
||||
await this.creditNoteGLEntries.createVendorCreditGLEntries(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts GL entries once credit note deleted.
|
||||
*/
|
||||
private revertGLEntriesOnceCreditNoteDeleted = async ({
|
||||
tenantId,
|
||||
oldCreditNote,
|
||||
creditNoteId,
|
||||
trx,
|
||||
}: ICreditNoteDeletedPayload) => {
|
||||
// Can't continue if the credit note is not published yet.
|
||||
if (!oldCreditNote.isPublished) return;
|
||||
|
||||
await this.creditNoteGLEntries.revertVendorCreditGLEntries(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits vendor credit associated GL entries once the transaction edited.
|
||||
* @param {ICreditNoteEditedPayload} payload -
|
||||
*/
|
||||
private editVendorCreditGLEntriesOnceEdited = async ({
|
||||
tenantId,
|
||||
creditNote,
|
||||
creditNoteId,
|
||||
trx,
|
||||
}: ICreditNoteEditedPayload) => {
|
||||
// Can't continue if the credit note is not published yet.
|
||||
if (!creditNote.isPublished) return;
|
||||
|
||||
await this.creditNoteGLEntries.editVendorCreditGLEntries(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import CreditNoteInventoryTransactions from './CreditNotesInventoryTransactions';
|
||||
import {
|
||||
ICreditNoteCreatedPayload,
|
||||
ICreditNoteDeletedPayload,
|
||||
ICreditNoteEditedPayload,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteInventoryTransactionsSubscriber {
|
||||
@Inject()
|
||||
inventoryTransactions: CreditNoteInventoryTransactions;
|
||||
|
||||
/**
|
||||
* Attaches events with publisher.
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.creditNote.onCreated,
|
||||
this.writeInventoryTranscationsOnceCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onEdited,
|
||||
this.rewriteInventoryTransactionsOnceEdited
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onDeleted,
|
||||
this.revertInventoryTransactionsOnceDeleted
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onOpened,
|
||||
this.writeInventoryTranscationsOnceCreated
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes inventory transactions once credit note created.
|
||||
* @param {ICreditNoteCreatedPayload} payload -
|
||||
*/
|
||||
public writeInventoryTranscationsOnceCreated = async ({
|
||||
tenantId,
|
||||
creditNote,
|
||||
trx,
|
||||
}: ICreditNoteCreatedPayload) => {
|
||||
// Can't continue if the credit note is open yet.
|
||||
if (!creditNote.isOpen) {
|
||||
return;
|
||||
}
|
||||
await this.inventoryTransactions.createInventoryTransactions(
|
||||
tenantId,
|
||||
creditNote,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites inventory transactions once credit note edited.
|
||||
* @param {ICreditNoteEditedPayload} payload -
|
||||
*/
|
||||
public rewriteInventoryTransactionsOnceEdited = async ({
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
creditNote,
|
||||
trx,
|
||||
}: ICreditNoteEditedPayload) => {
|
||||
// Can't continue if the credit note is open yet.
|
||||
if (!creditNote.isOpen) {
|
||||
return;
|
||||
}
|
||||
await this.inventoryTransactions.editInventoryTransactions(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
creditNote,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts inventory transactions once credit note deleted.
|
||||
* @param {ICreditNoteDeletedPayload} payload -
|
||||
*/
|
||||
public revertInventoryTransactionsOnceDeleted = async ({
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
oldCreditNote,
|
||||
trx,
|
||||
}: ICreditNoteDeletedPayload) => {
|
||||
// Can't continue if the credit note is open yet.
|
||||
if (!oldCreditNote.isOpen) {
|
||||
return;
|
||||
}
|
||||
await this.inventoryTransactions.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class CreditNoteTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale credit note object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedCreditsRemaining',
|
||||
'formattedCreditNoteDate',
|
||||
'formattedAmount',
|
||||
'formattedCreditsUsed'
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted credit note date.
|
||||
* @param {ICreditNote} credit
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedCreditNoteDate = (credit): string => {
|
||||
return this.formatDate(credit.creditNoteDate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted invoice amount.
|
||||
* @param {ICreditNote} credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (credit): string => {
|
||||
return formatNumber(credit.amount, {
|
||||
currencyCode: credit.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted credits remaining.
|
||||
* @param {ICreditNote} credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCreditsRemaining = (credit) => {
|
||||
return formatNumber(credit.creditsRemaining, {
|
||||
currencyCode: credit.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted credits used.
|
||||
* @param {ICreditNote} credit
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedCreditsUsed = (credit) => {
|
||||
return formatNumber(credit.creditsUsed, {
|
||||
currencyCode: credit.currencyCode,
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class CreditNoteWithInvoicesToApplyTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedInvoiceDate',
|
||||
'formattedDueDate',
|
||||
'formattedAmount',
|
||||
'formattedDueAmount',
|
||||
'formattedPaymentAmount',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted invoice date.
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedInvoiceDate = (invoice): string => {
|
||||
return this.formatDate(invoice.invoiceDate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted due date.
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDueDate = (invoice): string => {
|
||||
return this.formatDate(invoice.dueDate);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted invoice amount.
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (invoice): string => {
|
||||
return formatNumber(invoice.balance, {
|
||||
currencyCode: invoice.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted invoice due amount.
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDueAmount = (invoice): string => {
|
||||
return formatNumber(invoice.dueAmount, {
|
||||
currencyCode: invoice.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted payment amount.
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedPaymentAmount = (invoice): string => {
|
||||
return formatNumber(invoice.paymentAmount, {
|
||||
currencyCode: invoice.currencyCode,
|
||||
});
|
||||
};
|
||||
}
|
||||
134
packages/server/src/services/CreditNotes/CreditNotes.ts
Normal file
134
packages/server/src/services/CreditNotes/CreditNotes.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import moment from 'moment';
|
||||
import { omit } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from './constants';
|
||||
import { ICreditNote, ICreditNoteEditDTO, ICreditNoteNewDTO } from '@/interfaces';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import AutoIncrementOrdersService from '@/services/Sales/AutoIncrementOrdersService';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
|
||||
@Service()
|
||||
export default class BaseCreditNotes {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private autoIncrementOrdersService: AutoIncrementOrdersService;
|
||||
|
||||
@Inject()
|
||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
/**
|
||||
* Transformes the credit/edit DTO to model.
|
||||
* @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO
|
||||
* @param {string} customerCurrencyCode -
|
||||
*/
|
||||
protected transformCreateEditDTOToModel = (
|
||||
tenantId: number,
|
||||
creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO,
|
||||
customerCurrencyCode: string,
|
||||
oldCreditNote?: ICreditNote
|
||||
): ICreditNote => {
|
||||
// Retrieve the total amount of the given items entries.
|
||||
const amount = this.itemsEntriesService.getTotalItemsEntries(
|
||||
creditNoteDTO.entries
|
||||
);
|
||||
const entries = creditNoteDTO.entries.map((entry) => ({
|
||||
...entry,
|
||||
referenceType: 'CreditNote',
|
||||
}));
|
||||
// Retreive the next credit note number.
|
||||
const autoNextNumber = this.getNextCreditNumber(tenantId);
|
||||
|
||||
// Detarmines the credit note number.
|
||||
const creditNoteNumber =
|
||||
creditNoteDTO.creditNoteNumber ||
|
||||
oldCreditNote?.creditNoteNumber ||
|
||||
autoNextNumber;
|
||||
|
||||
const initialDTO = {
|
||||
...omit(creditNoteDTO, ['open']),
|
||||
creditNoteNumber,
|
||||
amount,
|
||||
currencyCode: customerCurrencyCode,
|
||||
exchangeRate: creditNoteDTO.exchangeRate || 1,
|
||||
entries,
|
||||
...(creditNoteDTO.open &&
|
||||
!oldCreditNote?.openedAt && {
|
||||
openedAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
refundedAmount: 0,
|
||||
invoicesAmount: 0,
|
||||
};
|
||||
return R.compose(
|
||||
this.branchDTOTransform.transformDTO<ICreditNote>(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO<ICreditNote>(tenantId)
|
||||
)(initialDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the given credit note or throw not found service error.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} creditNoteId -
|
||||
*/
|
||||
protected getCreditNoteOrThrowError = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
) => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
const creditNote = await CreditNote.query().findById(creditNoteId);
|
||||
|
||||
if (!creditNote) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_NOT_FOUND);
|
||||
}
|
||||
return creditNote;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the next unique credit number.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @return {string}
|
||||
*/
|
||||
private getNextCreditNumber = (tenantId: number): string => {
|
||||
return this.autoIncrementOrdersService.getNextTransactionNumber(
|
||||
tenantId,
|
||||
'credit_note'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Increment the credit note serial next number.
|
||||
* @param {number} tenantId -
|
||||
*/
|
||||
public incrementSerialNumber = (tenantId: number) => {
|
||||
return this.autoIncrementOrdersService.incrementSettingsNextNumber(
|
||||
tenantId,
|
||||
'credit_note'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the credit note remaining amount.
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {number} amount
|
||||
*/
|
||||
public validateCreditRemainingAmount = (
|
||||
creditNote: ICreditNote,
|
||||
amount: number
|
||||
) => {
|
||||
if (creditNote.creditsRemaining < amount) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { ICreditNote } from '@/interfaces';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
|
||||
@Service()
|
||||
export default class CreditNoteInventoryTransactions {
|
||||
@Inject()
|
||||
inventoryService: InventoryService;
|
||||
|
||||
@Inject()
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
/**
|
||||
* Creates credit note inventory transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {ICreditNote} creditNote
|
||||
*/
|
||||
public createInventoryTransactions = async (
|
||||
tenantId: number,
|
||||
creditNote: ICreditNote,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
creditNote.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: creditNote.id,
|
||||
transactionType: 'CreditNote',
|
||||
transactionNumber: creditNote.creditNoteNumber,
|
||||
exchangeRate: creditNote.exchangeRate,
|
||||
date: creditNote.creditNoteDate,
|
||||
direction: 'IN',
|
||||
entries: inventoryEntries,
|
||||
createdAt: creditNote.createdAt,
|
||||
warehouseId: creditNote.warehouseId,
|
||||
};
|
||||
// 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,
|
||||
creditNoteId: number,
|
||||
creditNote: ICreditNote,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
// Deletes inventory transactions.
|
||||
await this.deleteInventoryTransactions(tenantId, creditNoteId, trx);
|
||||
|
||||
// Re-write inventory transactions.
|
||||
await this.createInventoryTransactions(tenantId, creditNote, 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,
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
// Deletes the inventory transactions by the given reference id and type.
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
'CreditNote',
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
119
packages/server/src/services/CreditNotes/DeleteCreditNote.ts
Normal file
119
packages/server/src/services/CreditNotes/DeleteCreditNote.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import Knex from 'knex';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { ICreditNoteDeletedPayload, ICreditNoteDeletingPayload } from '@/interfaces';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import events from '@/subscribers/events';
|
||||
import RefundCreditNote from './RefundCreditNote';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class DeleteCreditNote extends BaseCreditNotes {
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
refundCreditNote: RefundCreditNote;
|
||||
|
||||
/**
|
||||
* Deletes the given credit note transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteCreditNote = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<void> => {
|
||||
const { CreditNote, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the credit note or throw not found service error.
|
||||
const oldCreditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Validate credit note has no refund transactions.
|
||||
await this.validateCreditNoteHasNoRefundTransactions(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Validate credit note has no applied invoices transactions.
|
||||
await this.validateCreditNoteHasNoApplyInvoiceTransactions(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Deletes the credit note transactions under unit-of-work transaction.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onCreditNoteDeleting` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onDeleting, {
|
||||
trx,
|
||||
tenantId,
|
||||
oldCreditNote
|
||||
} as ICreditNoteDeletingPayload);
|
||||
|
||||
// Delets the associated credit note entries.
|
||||
await ItemEntry.query(trx)
|
||||
.where('reference_id', creditNoteId)
|
||||
.where('reference_type', 'CreditNote')
|
||||
.delete();
|
||||
|
||||
// Deletes the credit note transaction.
|
||||
await CreditNote.query(trx).findById(creditNoteId).delete();
|
||||
|
||||
// Triggers `onCreditNoteDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onDeleted, {
|
||||
tenantId,
|
||||
oldCreditNote,
|
||||
creditNoteId,
|
||||
trx,
|
||||
} as ICreditNoteDeletedPayload);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates credit note has no associated refund transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private validateCreditNoteHasNoRefundTransactions = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<void> => {
|
||||
const { RefundCreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
const refundTransactions = await RefundCreditNote.query().where(
|
||||
'creditNoteId',
|
||||
creditNoteId
|
||||
);
|
||||
if (refundTransactions.length > 0) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate credit note has no associated applied invoices transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
private validateCreditNoteHasNoApplyInvoiceTransactions = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
) => {
|
||||
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const appliedTransactions = await CreditNoteAppliedInvoice.query().where(
|
||||
'creditNoteId',
|
||||
creditNoteId
|
||||
);
|
||||
if (appliedTransactions.length > 0) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_HAS_APPLIED_INVOICES);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import Knex from 'knex';
|
||||
import { IApplyCreditToInvoicesDeletedPayload } 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 BaseCreditNotes from './CreditNotes';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class DeletreCreditNoteApplyToInvoices extends BaseCreditNotes {
|
||||
@Inject('PaymentReceives')
|
||||
paymentReceive: PaymentReceiveService;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Apply credit note to the given invoices.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @param {IApplyCreditToInvoicesDTO} applyCreditToInvoicesDTO
|
||||
*/
|
||||
public deleteApplyCreditNoteToInvoices = async (
|
||||
tenantId: number,
|
||||
applyCreditToInvoicesId: number
|
||||
): Promise<void> => {
|
||||
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const creditNoteAppliedToInvoice =
|
||||
await CreditNoteAppliedInvoice.query().findById(applyCreditToInvoicesId);
|
||||
|
||||
if (!creditNoteAppliedToInvoice) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND);
|
||||
}
|
||||
// Retrieve the credit note or throw not found service error.
|
||||
const creditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteAppliedToInvoice.creditNoteId
|
||||
);
|
||||
// Creates credit note apply to invoice transaction.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Delete credit note applied to invoices.
|
||||
await CreditNoteAppliedInvoice.query(trx)
|
||||
.findById(applyCreditToInvoicesId)
|
||||
.delete();
|
||||
|
||||
// Triggers `onCreditNoteApplyToInvoiceDeleted` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.creditNote.onApplyToInvoicesDeleted,
|
||||
{
|
||||
trx,
|
||||
creditNote,
|
||||
creditNoteAppliedToInvoice,
|
||||
tenantId,
|
||||
} as IApplyCreditToInvoicesDeletedPayload
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class DeleteCustomerLinkedCreidtNote {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
/**
|
||||
* Validate the given customer has no linked credit note transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
*/
|
||||
public validateCustomerHasNoCreditTransaction = async (
|
||||
tenantId: number,
|
||||
customerId: number
|
||||
) => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
const associatedCredits = await CreditNote.query().where(
|
||||
'customerId',
|
||||
customerId
|
||||
);
|
||||
if (associatedCredits.length > 0) {
|
||||
throw new ServiceError(ERRORS.CUSTOMER_HAS_LINKED_CREDIT_NOTES);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import events from '@/subscribers/events';
|
||||
import { ICustomerDeletingPayload } from '@/interfaces';
|
||||
import DeleteCustomerLinkedCreidtNote from './DeleteCustomerLinkedCreditNote';
|
||||
|
||||
const ERRORS = {
|
||||
CUSTOMER_HAS_TRANSACTIONS: 'CUSTOMER_HAS_TRANSACTIONS',
|
||||
};
|
||||
|
||||
@Service()
|
||||
export default class DeleteCustomerLinkedCreditSubscriber {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
deleteCustomerLinkedCredit: DeleteCustomerLinkedCreidtNote;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
* @param bus
|
||||
*/
|
||||
public attach = (bus) => {
|
||||
bus.subscribe(
|
||||
events.customers.onDeleting,
|
||||
this.validateCustomerHasNoLinkedCreditsOnDeleting
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate vendor has no assocaited credit transaction once the vendor deleting.
|
||||
* @param {IVendorEventDeletingPayload} payload -
|
||||
*/
|
||||
public validateCustomerHasNoLinkedCreditsOnDeleting = async ({
|
||||
tenantId,
|
||||
customerId,
|
||||
}: ICustomerDeletingPayload) => {
|
||||
try {
|
||||
await this.deleteCustomerLinkedCredit.validateCustomerHasNoCreditTransaction(
|
||||
tenantId,
|
||||
customerId
|
||||
);
|
||||
} catch (error) {
|
||||
throw new ServiceError(ERRORS.CUSTOMER_HAS_TRANSACTIONS);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import Knex from 'knex';
|
||||
import {
|
||||
IRefundCreditNoteDeletedPayload,
|
||||
IRefundCreditNoteDeletingPayload,
|
||||
IRefundVendorCreditDeletedPayload,
|
||||
} 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 RefundCreditNote from './RefundCreditNote';
|
||||
|
||||
@Service()
|
||||
export default class DeleteRefundCreditNote extends RefundCreditNote {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
/**
|
||||
* Retrieve the credit note graph.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns
|
||||
*/
|
||||
public deleteCreditNoteRefund = async (
|
||||
tenantId: number,
|
||||
refundCreditId: number
|
||||
) => {
|
||||
const { RefundCreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the old credit note or throw not found service error.
|
||||
const oldRefundCredit = await this.getCreditNoteRefundOrThrowError(
|
||||
tenantId,
|
||||
refundCreditId
|
||||
);
|
||||
// Triggers `onCreditNoteRefundDeleted` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onRefundDelete, {
|
||||
refundCreditId,
|
||||
oldRefundCredit,
|
||||
tenantId,
|
||||
} as IRefundCreditNoteDeletedPayload);
|
||||
|
||||
// Deletes refund credit note transactions with associated entries.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
const eventPayload = {
|
||||
trx,
|
||||
refundCreditId,
|
||||
oldRefundCredit,
|
||||
tenantId,
|
||||
} as IRefundCreditNoteDeletedPayload | IRefundCreditNoteDeletingPayload;
|
||||
|
||||
// Triggers `onCreditNoteRefundDeleting` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.creditNote.onRefundDeleting,
|
||||
eventPayload
|
||||
);
|
||||
// Deletes the refund credit note graph from the storage.
|
||||
await RefundCreditNote.query(trx).findById(refundCreditId).delete();
|
||||
|
||||
// Triggers `onCreditNoteRefundDeleted` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.creditNote.onRefundDeleted,
|
||||
eventPayload as IRefundVendorCreditDeletedPayload
|
||||
);
|
||||
});
|
||||
};
|
||||
}
|
||||
100
packages/server/src/services/CreditNotes/EditCreditNote.ts
Normal file
100
packages/server/src/services/CreditNotes/EditCreditNote.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
ICreditNoteEditDTO,
|
||||
ICreditNoteEditedPayload,
|
||||
ICreditNoteEditingPayload,
|
||||
} from '@/interfaces';
|
||||
import { Knex } from 'knex';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
|
||||
@Service()
|
||||
export default class EditCreditNote extends BaseCreditNotes {
|
||||
@Inject()
|
||||
itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Edits the given credit note.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICreditNoteEditDTO} creditNoteEditDTO -
|
||||
*/
|
||||
public editCreditNote = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
creditNoteEditDTO: ICreditNoteEditDTO
|
||||
) => {
|
||||
const { CreditNote, Contact } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the sale invoice or throw not found service error.
|
||||
const oldCreditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Validate customer existance.
|
||||
const customer = await Contact.query()
|
||||
.modify('customer')
|
||||
.findById(creditNoteEditDTO.customerId)
|
||||
.throwIfNotFound();
|
||||
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(
|
||||
tenantId,
|
||||
creditNoteEditDTO.entries
|
||||
);
|
||||
// Validate non-sellable entries items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(
|
||||
tenantId,
|
||||
creditNoteEditDTO.entries
|
||||
);
|
||||
// Validate the items entries existance.
|
||||
await this.itemsEntriesService.validateEntriesIdsExistance(
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
'CreditNote',
|
||||
creditNoteEditDTO.entries
|
||||
);
|
||||
// Transformes the given DTO to storage layer data.
|
||||
const creditNoteModel = this.transformCreateEditDTOToModel(
|
||||
tenantId,
|
||||
creditNoteEditDTO,
|
||||
customer.currencyCode,
|
||||
oldCreditNote
|
||||
);
|
||||
// Sales the credit note transactions with associated entries.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
// Triggers `onCreditNoteEditing` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onEditing, {
|
||||
creditNoteEditDTO,
|
||||
oldCreditNote,
|
||||
trx,
|
||||
tenantId,
|
||||
} as ICreditNoteEditingPayload);
|
||||
|
||||
// Saves the credit note graph to the storage.
|
||||
const creditNote = await CreditNote.query(trx).upsertGraph({
|
||||
id: creditNoteId,
|
||||
...creditNoteModel,
|
||||
});
|
||||
// Triggers `onCreditNoteEdited` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onEdited, {
|
||||
trx,
|
||||
oldCreditNote,
|
||||
creditNoteId,
|
||||
creditNote,
|
||||
creditNoteEditDTO,
|
||||
tenantId,
|
||||
} as ICreditNoteEditedPayload);
|
||||
|
||||
return creditNote;
|
||||
});
|
||||
};
|
||||
}
|
||||
43
packages/server/src/services/CreditNotes/GetCreditNote.ts
Normal file
43
packages/server/src/services/CreditNotes/GetCreditNote.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ERRORS } from './constants';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { CreditNoteTransformer } from './CreditNoteTransformer';
|
||||
|
||||
@Service()
|
||||
export default class GetCreditNote extends BaseCreditNotes {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the credit note graph.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns
|
||||
*/
|
||||
public getCreditNote = async (tenantId: number, creditNoteId: number) => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the vendor credit model graph.
|
||||
const creditNote = await CreditNote.query()
|
||||
.findById(creditNoteId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('customer')
|
||||
.withGraphFetched('branch');
|
||||
|
||||
if (!creditNote) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_NOT_FOUND);
|
||||
}
|
||||
// Transforms the credit note model to POJO.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
creditNote,
|
||||
new CreditNoteTransformer(),
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { CreditNoteAppliedInvoiceTransformer } from './CreditNoteAppliedInvoiceTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export default class GetCreditNoteAssociatedAppliedInvoices extends BaseCreditNotes {
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve credit note associated invoices to apply.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<ISaleInvoice[]>}
|
||||
*/
|
||||
public getCreditAssociatedAppliedInvoices = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<ISaleInvoice[]> => {
|
||||
const { CreditNoteAppliedInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireve credit note or throw not found service error.
|
||||
const creditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
const appliedToInvoices = await CreditNoteAppliedInvoice.query()
|
||||
.where('credit_note_id', creditNoteId)
|
||||
.withGraphFetched('saleInvoice')
|
||||
.withGraphFetched('creditNote');
|
||||
|
||||
// Transformes credit note applied to invoices.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
appliedToInvoices,
|
||||
new CreditNoteAppliedInvoiceTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import { CreditNoteWithInvoicesToApplyTransformer } from './CreditNoteWithInvoicesToApplyTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export default class GetCreditNoteAssociatedInvoicesToApply extends BaseCreditNotes {
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve credit note associated invoices to apply.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<ISaleInvoice[]>}
|
||||
*/
|
||||
public getCreditAssociatedInvoicesToApply = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<ISaleInvoice[]> => {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retireve credit note or throw not found service error.
|
||||
const creditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Retrieves the published due invoices that associated to the given customer.
|
||||
const saleInvoices = await SaleInvoice.query()
|
||||
.where('customerId', creditNote.customerId)
|
||||
.modify('dueInvoices')
|
||||
.modify('published');
|
||||
|
||||
// Transformes the sale invoices models to POJO.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
saleInvoices,
|
||||
new CreditNoteWithInvoicesToApplyTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
37
packages/server/src/services/CreditNotes/GetCreditNotePdf.ts
Normal file
37
packages/server/src/services/CreditNotes/GetCreditNotePdf.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import PdfService from '@/services/PDF/PdfService';
|
||||
import { templateRender } from 'utils';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Tenant } from '@/system/models';
|
||||
|
||||
@Service()
|
||||
export default class GetCreditNotePdf {
|
||||
@Inject()
|
||||
pdfService: PdfService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {} saleInvoice -
|
||||
*/
|
||||
async getCreditNotePdf(tenantId: number, creditNote) {
|
||||
const i18n = this.tenancy.i18n(tenantId);
|
||||
|
||||
const organization = await Tenant.query()
|
||||
.findById(tenantId)
|
||||
.withGraphFetched('metadata');
|
||||
|
||||
const htmlContent = templateRender('modules/credit-note-standard', {
|
||||
organization,
|
||||
organizationName: organization.metadata.name,
|
||||
organizationEmail: organization.metadata.email,
|
||||
creditNote,
|
||||
...i18n,
|
||||
});
|
||||
const pdfContent = await this.pdfService.pdfDocument(htmlContent);
|
||||
|
||||
return pdfContent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IRefundCreditNote } from '@/interfaces';
|
||||
import RefundCreditNote from './RefundCreditNote';
|
||||
import RefundCreditNoteTransformer from './RefundCreditNoteTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export default class getRefundCreditNoteTransaction extends RefundCreditNote {
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve credit note associated invoices to apply.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<ISaleInvoice[]>}
|
||||
*/
|
||||
public getRefundCreditTransaction = async (
|
||||
tenantId: number,
|
||||
refundCreditId: number
|
||||
): Promise<IRefundCreditNote> => {
|
||||
const { RefundCreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
await this.getCreditNoteRefundOrThrowError(tenantId, refundCreditId);
|
||||
|
||||
const refundCreditNote = await RefundCreditNote.query()
|
||||
.findById(refundCreditId)
|
||||
.withGraphFetched('fromAccount')
|
||||
.withGraphFetched('creditNote');
|
||||
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
refundCreditNote,
|
||||
new RefundCreditNoteTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { IRefundCreditNotePOJO } from '@/interfaces';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import RefundCreditNoteTransformer from './RefundCreditNoteTransformer';
|
||||
|
||||
@Service()
|
||||
export default class ListCreditNoteRefunds extends BaseCreditNotes {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Retrieve the credit note graph.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @returns {Promise<IRefundCreditNotePOJO[]>}
|
||||
*/
|
||||
public getCreditNoteRefunds = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<IRefundCreditNotePOJO[]> => {
|
||||
const { RefundCreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve refund credit notes associated to the given credit note.
|
||||
const refundCreditTransactions = await RefundCreditNote.query()
|
||||
.where('creditNoteId', creditNoteId)
|
||||
.withGraphFetched('creditNote')
|
||||
.withGraphFetched('fromAccount');
|
||||
|
||||
// Transformes refund credit note models to POJO objects.
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
refundCreditTransactions,
|
||||
new RefundCreditNoteTransformer()
|
||||
);
|
||||
};
|
||||
}
|
||||
67
packages/server/src/services/CreditNotes/ListCreditNotes.ts
Normal file
67
packages/server/src/services/CreditNotes/ListCreditNotes.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import { ICreditNotesQueryDTO } from '@/interfaces';
|
||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { CreditNoteTransformer } from './CreditNoteTransformer';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
@Service()
|
||||
export default class ListCreditNotes extends BaseCreditNotes {
|
||||
@Inject()
|
||||
private dynamicListService: DynamicListingService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
/**
|
||||
* Parses the sale invoice list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO = (filterDTO) => {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the paginated and filterable credit notes list.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICreditNotesQueryDTO} creditNotesQuery -
|
||||
*/
|
||||
public getCreditNotesList = async (
|
||||
tenantId: number,
|
||||
creditNotesQuery: ICreditNotesQueryDTO
|
||||
) => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
// Parses stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(creditNotesQuery);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
tenantId,
|
||||
CreditNote,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await CreditNote.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('customer');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transforomes the credit notes to POJO.
|
||||
const creditNotes = await this.transformer.transform(
|
||||
tenantId,
|
||||
results,
|
||||
new CreditNoteTransformer()
|
||||
);
|
||||
|
||||
return {
|
||||
creditNotes,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
};
|
||||
}
|
||||
92
packages/server/src/services/CreditNotes/OpenCreditNote.ts
Normal file
92
packages/server/src/services/CreditNotes/OpenCreditNote.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import {
|
||||
ICreditNote,
|
||||
ICreditNoteOpenedPayload,
|
||||
ICreditNoteOpeningPayload,
|
||||
} from '@/interfaces';
|
||||
import { Knex } from 'knex';
|
||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import UnitOfWork from '@/services/UnitOfWork';
|
||||
import events from '@/subscribers/events';
|
||||
import { Inject, Service } from 'typedi';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class OpenCreditNote extends BaseCreditNotes {
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private eventPublisher: EventPublisher;
|
||||
|
||||
@Inject()
|
||||
private uow: UnitOfWork;
|
||||
|
||||
/**
|
||||
* Opens the given credit note.
|
||||
* @param {number} tenantId -
|
||||
* @param {ICreditNoteEditDTO} creditNoteEditDTO -
|
||||
* @returns {Promise<ICreditNote>}
|
||||
*/
|
||||
public openCreditNote = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number
|
||||
): Promise<ICreditNote> => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the sale invoice or throw not found service error.
|
||||
const oldCreditNote = await this.getCreditNoteOrThrowError(
|
||||
tenantId,
|
||||
creditNoteId
|
||||
);
|
||||
// Throw service error if the credit note is already open.
|
||||
this.throwErrorIfAlreadyOpen(oldCreditNote);
|
||||
|
||||
// Triggers `onCreditNoteOpen` event.
|
||||
this.eventPublisher.emitAsync(events.creditNote.onOpen, {
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
oldCreditNote,
|
||||
});
|
||||
// Sales the credit note transactions with associated entries.
|
||||
return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => {
|
||||
const eventPayload = {
|
||||
tenantId,
|
||||
creditNoteId,
|
||||
oldCreditNote,
|
||||
trx,
|
||||
} as ICreditNoteOpeningPayload;
|
||||
|
||||
// Triggers `onCreditNoteOpening` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.creditNote.onOpening,
|
||||
eventPayload
|
||||
);
|
||||
// Saves the credit note graph to the storage.
|
||||
const creditNote = await CreditNote.query(trx)
|
||||
.findById(creditNoteId)
|
||||
.update({
|
||||
openedAt: new Date(),
|
||||
});
|
||||
// Triggers `onCreditNoteOpened` event.
|
||||
await this.eventPublisher.emitAsync(events.creditNote.onOpened, {
|
||||
...eventPayload,
|
||||
creditNote,
|
||||
} as ICreditNoteOpenedPayload);
|
||||
|
||||
return creditNote;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param creditNote
|
||||
*/
|
||||
public throwErrorIfAlreadyOpen = (creditNote) => {
|
||||
if (creditNote.openedAt) {
|
||||
throw new ServiceError(ERRORS.CREDIT_NOTE_ALREADY_OPENED);
|
||||
}
|
||||
};
|
||||
}
|
||||
45
packages/server/src/services/CreditNotes/RefundCreditNote.ts
Normal file
45
packages/server/src/services/CreditNotes/RefundCreditNote.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ServiceError } from '@/exceptions';
|
||||
import { IAccount, IRefundCreditNote } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import BaseCreditNotes from './CreditNotes';
|
||||
import { ERRORS } from './constants';
|
||||
|
||||
@Service()
|
||||
export default class RefundCreditNote extends BaseCreditNotes {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieve the credit note graph.
|
||||
* @param {number} tenantId
|
||||
* @param {number} refundCreditId
|
||||
* @returns {Promise<IRefundCreditNote>}
|
||||
*/
|
||||
public getCreditNoteRefundOrThrowError = async (
|
||||
tenantId: number,
|
||||
refundCreditId: number
|
||||
): Promise<IRefundCreditNote> => {
|
||||
const { RefundCreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
const refundCreditNote = await RefundCreditNote.query().findById(
|
||||
refundCreditId
|
||||
);
|
||||
if (!refundCreditNote) {
|
||||
throw new ServiceError(ERRORS.REFUND_CREDIT_NOTE_NOT_FOUND);
|
||||
}
|
||||
return refundCreditNote;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the refund account type.
|
||||
* @param {IAccount} account
|
||||
*/
|
||||
public validateRefundWithdrawwalAccountType = (account: IAccount): void => {
|
||||
const supportedTypes = ['bank', 'cash', 'fixed-asset'];
|
||||
|
||||
if (supportedTypes.indexOf(account.accountType) === -1) {
|
||||
throw new ServiceError(ERRORS.ACCOUNT_INVALID_TYPE);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { AccountNormal, ILedgerEntry, IRefundCreditNote } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
|
||||
@Service()
|
||||
export default class RefundCreditNoteGLEntries {
|
||||
@Inject()
|
||||
ledgerStorage: LedgerStorageService;
|
||||
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
* Retrieves the refund credit common GL entry.
|
||||
* @param {IRefundCreditNote} refundCreditNote
|
||||
* @returns
|
||||
*/
|
||||
private getRefundCreditCommonGLEntry = (
|
||||
refundCreditNote: IRefundCreditNote
|
||||
) => {
|
||||
return {
|
||||
currencyCode: refundCreditNote.currencyCode,
|
||||
exchangeRate: refundCreditNote.exchangeRate,
|
||||
|
||||
transactionType: 'RefundCreditNote',
|
||||
transactionId: refundCreditNote.id,
|
||||
date: refundCreditNote.date,
|
||||
userId: refundCreditNote.userId,
|
||||
|
||||
referenceNumber: refundCreditNote.referenceNo,
|
||||
|
||||
createdAt: refundCreditNote.createdAt,
|
||||
indexGroup: 10,
|
||||
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
|
||||
note: refundCreditNote.description,
|
||||
branchId: refundCreditNote.branchId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the refudn credit receivable GL entry.
|
||||
* @param {IRefundCreditNote} refundCreditNote
|
||||
* @param {number} ARAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getRefundCreditGLReceivableEntry = (
|
||||
refundCreditNote: IRefundCreditNote,
|
||||
ARAccountId: number
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: refundCreditNote.amount,
|
||||
accountId: ARAccountId,
|
||||
contactId: refundCreditNote.creditNote.customerId,
|
||||
index: 1,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the refund credit withdrawal GL entry.
|
||||
* @param {number} refundCreditNote
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getRefundCreditGLWithdrawalEntry = (
|
||||
refundCreditNote: IRefundCreditNote
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getRefundCreditCommonGLEntry(refundCreditNote);
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: refundCreditNote.amount,
|
||||
accountId: refundCreditNote.fromAccountId,
|
||||
index: 2,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the refund credit note GL entries.
|
||||
* @param {IRefundCreditNote} refundCreditNote
|
||||
* @param {number} receivableAccount
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getRefundCreditGLEntries(
|
||||
refundCreditNote: IRefundCreditNote,
|
||||
ARAccountId: number
|
||||
): ILedgerEntry[] {
|
||||
const receivableEntry = this.getRefundCreditGLReceivableEntry(
|
||||
refundCreditNote,
|
||||
ARAccountId
|
||||
);
|
||||
const withdrawalEntry =
|
||||
this.getRefundCreditGLWithdrawalEntry(refundCreditNote);
|
||||
|
||||
return [receivableEntry, withdrawalEntry];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates refund credit GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {IRefundCreditNote} refundCreditNote
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public createRefundCreditGLEntries = async (
|
||||
tenantId: number,
|
||||
refundCreditNoteId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
const { Account, RefundCreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve the refund with associated credit note.
|
||||
const refundCreditNote = await RefundCreditNote.query(trx)
|
||||
.findById(refundCreditNoteId)
|
||||
.withGraphFetched('creditNote');
|
||||
|
||||
// Receivable account A/R.
|
||||
const receivableAccount = await Account.query().findOne(
|
||||
'slug',
|
||||
'accounts-receivable'
|
||||
);
|
||||
// Retrieve refund credit GL entries.
|
||||
const refundGLEntries = this.getRefundCreditGLEntries(
|
||||
refundCreditNote,
|
||||
receivableAccount.id
|
||||
);
|
||||
const ledger = new Ledger(refundGLEntries);
|
||||
|
||||
// Saves refund ledger entries.
|
||||
await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts refund credit note GL entries.
|
||||
* @param {number} tenantId
|
||||
* @param {number} refundCreditNoteId
|
||||
* @param {number} receivableAccount
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public revertRefundCreditGLEntries = async (
|
||||
tenantId: number,
|
||||
refundCreditNoteId: number,
|
||||
trx?: Knex.Transaction
|
||||
) => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
tenantId,
|
||||
refundCreditNoteId,
|
||||
'RefundCreditNote',
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import RefundCreditNoteGLEntries from './RefundCreditNoteGLEntries';
|
||||
import {
|
||||
IRefundCreditNoteCreatedPayload,
|
||||
IRefundCreditNoteDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class RefundCreditNoteGLEntriesSubscriber {
|
||||
@Inject()
|
||||
refundCreditGLEntries: RefundCreditNoteGLEntries;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach = (bus) => {
|
||||
bus.subscribe(
|
||||
events.creditNote.onRefundCreated,
|
||||
this.writeRefundCreditGLEntriesOnceCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onRefundDeleted,
|
||||
this.revertRefundCreditGLEntriesOnceDeleted
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Writes refund credit note GL entries once the transaction created.
|
||||
* @param {IRefundCreditNoteCreatedPayload} payload -
|
||||
*/
|
||||
private writeRefundCreditGLEntriesOnceCreated = async ({
|
||||
trx,
|
||||
refundCreditNote,
|
||||
creditNote,
|
||||
tenantId,
|
||||
}: IRefundCreditNoteCreatedPayload) => {
|
||||
await this.refundCreditGLEntries.createRefundCreditGLEntries(
|
||||
tenantId,
|
||||
refundCreditNote.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts refund credit note GL entries once the transaction deleted.
|
||||
* @param {IRefundCreditNoteDeletedPayload} payload -
|
||||
*/
|
||||
private revertRefundCreditGLEntriesOnceDeleted = async ({
|
||||
trx,
|
||||
refundCreditId,
|
||||
oldRefundCredit,
|
||||
tenantId,
|
||||
}: IRefundCreditNoteDeletedPayload) => {
|
||||
await this.refundCreditGLEntries.revertRefundCreditGLEntries(
|
||||
tenantId,
|
||||
refundCreditId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export default class RefundCreditNoteTransformer 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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import Knex from 'knex';
|
||||
import { IRefundCreditNote } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Inject, Service } from 'typedi';
|
||||
|
||||
@Service()
|
||||
export default class RefundSyncCreditNoteBalance {
|
||||
@Inject()
|
||||
tenancy: HasTenancyService;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {IRefundCreditNote} refundCreditNote
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public incrementCreditNoteRefundAmount = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
amount: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
await CreditNote.query(trx)
|
||||
.findById(creditNoteId)
|
||||
.increment('refunded_amount', amount);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {IRefundCreditNote} refundCreditNote
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public decrementCreditNoteRefundAmount = async (
|
||||
tenantId: number,
|
||||
creditNoteId: number,
|
||||
amount: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> => {
|
||||
const { CreditNote } = this.tenancy.models(tenantId);
|
||||
|
||||
await CreditNote.query(trx)
|
||||
.findById(creditNoteId)
|
||||
.decrement('refunded_amount', amount);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IRefundCreditNoteCreatedPayload,
|
||||
IRefundCreditNoteDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import RefundSyncCreditNoteBalance from './RefundSyncCreditNoteBalance';
|
||||
|
||||
@Service()
|
||||
export default class RefundSyncCreditNoteBalanceSubscriber {
|
||||
@Inject()
|
||||
refundSyncCreditBalance: RefundSyncCreditNoteBalance;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
attach(bus) {
|
||||
bus.subscribe(
|
||||
events.creditNote.onRefundCreated,
|
||||
this.incrementRefundedAmountOnceRefundCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.creditNote.onRefundDeleted,
|
||||
this.decrementRefundedAmountOnceRefundDeleted
|
||||
);
|
||||
return bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment credit note refunded amount once associated refund transaction created.
|
||||
* @param {IRefundCreditNoteCreatedPayload} payload -
|
||||
*/
|
||||
private incrementRefundedAmountOnceRefundCreated = async ({
|
||||
trx,
|
||||
refundCreditNote,
|
||||
tenantId,
|
||||
}: IRefundCreditNoteCreatedPayload) => {
|
||||
await this.refundSyncCreditBalance.incrementCreditNoteRefundAmount(
|
||||
tenantId,
|
||||
refundCreditNote.creditNoteId,
|
||||
refundCreditNote.amount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrement credit note refunded amount once associated refuned transaction deleted.
|
||||
* @param {IRefundCreditNoteDeletedPayload} payload -
|
||||
*/
|
||||
private decrementRefundedAmountOnceRefundDeleted = async ({
|
||||
trx,
|
||||
oldRefundCredit,
|
||||
tenantId,
|
||||
}: IRefundCreditNoteDeletedPayload) => {
|
||||
await this.refundSyncCreditBalance.decrementCreditNoteRefundAmount(
|
||||
tenantId,
|
||||
oldRefundCredit.creditNoteId,
|
||||
oldRefundCredit.amount,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
68
packages/server/src/services/CreditNotes/constants.ts
Normal file
68
packages/server/src/services/CreditNotes/constants.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
export const ERRORS = {
|
||||
CREDIT_NOTE_NOT_FOUND: 'CREDIT_NOTE_NOT_FOUND',
|
||||
REFUND_CREDIT_NOTE_NOT_FOUND: 'REFUND_CREDIT_NOTE_NOT_FOUND',
|
||||
CREDIT_NOTE_ALREADY_OPENED: 'CREDIT_NOTE_ALREADY_OPENED',
|
||||
ACCOUNT_INVALID_TYPE: 'ACCOUNT_INVALID_TYPE',
|
||||
CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT: 'CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT',
|
||||
INVOICES_HAS_NO_REMAINING_AMOUNT: 'INVOICES_HAS_NO_REMAINING_AMOUNT',
|
||||
CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND:
|
||||
'CREDIT_NOTE_APPLY_TO_INVOICES_NOT_FOUND',
|
||||
CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS: 'CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS',
|
||||
CREDIT_NOTE_HAS_APPLIED_INVOICES: 'CREDIT_NOTE_HAS_APPLIED_INVOICES',
|
||||
CUSTOMER_HAS_LINKED_CREDIT_NOTES: 'CUSTOMER_HAS_LINKED_CREDIT_NOTES'
|
||||
};
|
||||
|
||||
export const DEFAULT_VIEW_COLUMNS = [];
|
||||
export const DEFAULT_VIEWS = [
|
||||
{
|
||||
name: 'credit_note.view.draft',
|
||||
slug: 'draft',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{ index: 1, fieldKey: 'status', comparator: 'equals', value: 'draft' },
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'credit_note.view.published',
|
||||
slug: 'published',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
index: 1,
|
||||
fieldKey: 'status',
|
||||
comparator: 'equals',
|
||||
value: 'published',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'credit_note.view.open',
|
||||
slug: 'open',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
index: 1,
|
||||
fieldKey: 'status',
|
||||
comparator: 'equals',
|
||||
value: 'open',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
{
|
||||
name: 'credit_note.view.closed',
|
||||
slug: 'closed',
|
||||
rolesLogicExpression: '1',
|
||||
roles: [
|
||||
{
|
||||
index: 1,
|
||||
fieldKey: 'status',
|
||||
comparator: 'equals',
|
||||
value: 'closed',
|
||||
},
|
||||
],
|
||||
columns: DEFAULT_VIEW_COLUMNS,
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user