Files
bigcapital/packages/server/src/services/CreditNotes/CreditNotes.ts
2024-09-17 13:53:57 +02:00

161 lines
4.8 KiB
TypeScript

import { Service, Inject } from 'typedi';
import moment from 'moment';
import { omit } from 'lodash';
import * as R from 'ramda';
import composeAsync from 'async/compose';
import { ServiceError } from '@/exceptions';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { ERRORS } from './constants';
import {
ICreditNote,
ICreditNoteEditDTO,
ICreditNoteEntryNewDTO,
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';
import { assocItemEntriesDefaultIndex } from '../Items/utils';
import { BrandingTemplateDTOTransformer } from '../PdfTemplate/BrandingTemplateDTOTransformer';
@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;
@Inject()
private brandingTemplatesTransformer: BrandingTemplateDTOTransformer;
/**
* Transformes the credit/edit DTO to model.
* @param {ICreditNoteNewDTO | ICreditNoteEditDTO} creditNoteDTO
* @param {string} customerCurrencyCode -
*/
protected transformCreateEditDTOToModel = async (
tenantId: number,
creditNoteDTO: ICreditNoteNewDTO | ICreditNoteEditDTO,
customerCurrencyCode: string,
oldCreditNote?: ICreditNote
): Promise<ICreditNote> => {
// Retrieve the total amount of the given items entries.
const amount = this.itemsEntriesService.getTotalItemsEntries(
creditNoteDTO.entries
);
const entries = R.compose(
// Associate the default index to each item entry.
assocItemEntriesDefaultIndex,
// Associate the reference type to credit note entries.
R.map((entry: ICreditNoteEntryNewDTO) => ({
...entry,
referenceType: 'CreditNote',
}))
)(creditNoteDTO.entries);
// 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', 'attachments']),
creditNoteNumber,
amount,
currencyCode: customerCurrencyCode,
exchangeRate: creditNoteDTO.exchangeRate || 1,
entries,
...(creditNoteDTO.open &&
!oldCreditNote?.openedAt && {
openedAt: moment().toMySqlDateTime(),
}),
refundedAmount: 0,
invoicesAmount: 0,
};
const initialAsyncDTO = await composeAsync(
// Assigns the default branding template id to the invoice DTO.
this.brandingTemplatesTransformer.assocDefaultBrandingTemplate(
tenantId,
'CreditNote'
)
)(initialDTO);
return R.compose(
this.branchDTOTransform.transformDTO<ICreditNote>(tenantId),
this.warehouseDTOTransform.transformDTO<ICreditNote>(tenantId)
)(initialAsyncDTO);
};
/**
* 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);
}
};
}