mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 21:30:31 +00:00
feat: rewrite repositories with base entity repository class.
feat: sales and purchases status. feat: sales and purchases auto-increment number. fix: settings find query with extra columns.
This commit is contained in:
@@ -119,7 +119,7 @@ export default class PaymentReceiveService {
|
||||
const { accountTypeRepository, accountRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
const currentAssetTypes = await accountTypeRepository.getByChildType('current_asset');
|
||||
const depositAccount = await accountRepository.findById(depositAccountId);
|
||||
const depositAccount = await accountRepository.findOneById(depositAccountId);
|
||||
|
||||
const currentAssetTypesIds = currentAssetTypes.map(type => type.id);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import events from 'subscribers/events';
|
||||
import { ServiceError } from 'exceptions';
|
||||
import CustomersService from 'services/Contacts/CustomersService';
|
||||
import moment from 'moment';
|
||||
|
||||
|
||||
const ERRORS = {
|
||||
@@ -19,6 +20,8 @@ const ERRORS = {
|
||||
CUSTOMER_NOT_FOUND: 'CUSTOMER_NOT_FOUND',
|
||||
SALE_ESTIMATE_NUMBER_EXISTANCE: 'SALE_ESTIMATE_NUMBER_EXISTANCE',
|
||||
ITEMS_IDS_NOT_EXISTS: 'ITEMS_IDS_NOT_EXISTS',
|
||||
SALE_ESTIMATE_ALREADY_DELIVERED: 'SALE_ESTIMATE_ALREADY_DELIVERED',
|
||||
SALE_ESTIMATE_CONVERTED_TO_INVOICE: 'SALE_ESTIMATE_CONVERTED_TO_INVOICE'
|
||||
};
|
||||
/**
|
||||
* Sale estimate service.
|
||||
@@ -80,6 +83,39 @@ export default class SaleEstimateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform DTO object ot model object.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleEstimateDTO} saleEstimateDTO
|
||||
* @param {ISaleEstimate} oldSaleEstimate
|
||||
* @return {ISaleEstimate}
|
||||
*/
|
||||
transformDTOToModel(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO,
|
||||
oldSaleEstimate?: ISaleEstimate,
|
||||
): ISaleEstimate {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
|
||||
return {
|
||||
amount,
|
||||
...formatDateFields(
|
||||
omit(estimateDTO, ['delivered', 'entries']),
|
||||
['estimateDate', 'expirationDate']
|
||||
),
|
||||
entries: estimateDTO.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...omit(entry, ['total', 'amount', 'id']),
|
||||
})),
|
||||
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
...(estimateDTO.delivered && (!oldSaleEstimate?.deliveredAt)) && ({
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new estimate with associated entries.
|
||||
* @async
|
||||
@@ -87,16 +123,16 @@ export default class SaleEstimateService {
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {Promise<ISaleEstimate>}
|
||||
*/
|
||||
public async createEstimate(tenantId: number, estimateDTO: ISaleEstimateDTO): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||
public async createEstimate(
|
||||
tenantId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
this.logger.info('[sale_estimate] inserting sale estimate to the storage.');
|
||||
|
||||
const amount = sumBy(estimateDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
const estimateObj = {
|
||||
amount,
|
||||
...formatDateFields(estimateDTO, ['estimateDate', 'expirationDate']),
|
||||
};
|
||||
// Transform DTO object ot model object.
|
||||
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO);
|
||||
|
||||
// Validate estimate number uniquiness on the storage.
|
||||
if (estimateDTO.estimateNumber) {
|
||||
@@ -112,13 +148,7 @@ export default class SaleEstimateService {
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, estimateDTO.entries);
|
||||
|
||||
const saleEstimate = await SaleEstimate.query()
|
||||
.upsertGraphAndFetch({
|
||||
...omit(estimateObj, ['entries']),
|
||||
entries: estimateObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...omit(entry, ['total', 'amount', 'id']),
|
||||
}))
|
||||
});
|
||||
.upsertGraphAndFetch({ ...estimateObj });
|
||||
|
||||
this.logger.info('[sale_estimate] insert sale estimated success.');
|
||||
await this.eventDispatcher.dispatch(events.saleEstimate.onCreated, {
|
||||
@@ -136,15 +166,16 @@ export default class SaleEstimateService {
|
||||
* @param {EstimateDTO} estimate
|
||||
* @return {void}
|
||||
*/
|
||||
public async editEstimate(tenantId: number, estimateId: number, estimateDTO: ISaleEstimateDTO): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate, ItemEntry } = this.tenancy.models(tenantId);
|
||||
public async editEstimate(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
estimateDTO: ISaleEstimateDTO
|
||||
): Promise<ISaleEstimate> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId);
|
||||
|
||||
const amount = sumBy(estimateDTO.entries, (e) => ItemEntry.calcAmount(e));
|
||||
const estimateObj = {
|
||||
amount,
|
||||
...formatDateFields(estimateDTO, ['estimateDate', 'expirationDate']),
|
||||
};
|
||||
// Transform DTO object ot model object.
|
||||
const estimateObj = this.transformDTOToModel(tenantId, estimateDTO, oldSaleEstimate);
|
||||
|
||||
// Validate estimate number uniquiness on the storage.
|
||||
if (estimateDTO.estimateNumber) {
|
||||
@@ -166,11 +197,7 @@ export default class SaleEstimateService {
|
||||
const saleEstimate = await SaleEstimate.query()
|
||||
.upsertGraphAndFetch({
|
||||
id: estimateId,
|
||||
...omit(estimateObj, ['entries']),
|
||||
entries: estimateObj.entries.map((entry) => ({
|
||||
reference_type: 'SaleEstimate',
|
||||
...omit(entry, ['total', 'amount']),
|
||||
})),
|
||||
...estimateObj
|
||||
});
|
||||
|
||||
await this.eventDispatcher.dispatch(events.saleEstimate.onEdited, {
|
||||
@@ -194,6 +221,11 @@ export default class SaleEstimateService {
|
||||
// Retrieve sale estimate or throw not found service error.
|
||||
const oldSaleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId);
|
||||
|
||||
// Throw error if the sale estimate converted to sale invoice.
|
||||
if (oldSaleEstimate.convertedToInvoiceId) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_CONVERTED_TO_INVOICE);
|
||||
}
|
||||
|
||||
this.logger.info('[sale_estimate] delete sale estimate and associated entries from the storage.');
|
||||
await ItemEntry.query()
|
||||
.where('reference_id', estimateId)
|
||||
@@ -254,4 +286,70 @@ export default class SaleEstimateService {
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts estimate to invoice.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} estimateId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async convertEstimateToInvoice(
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
invoiceId: number,
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate.
|
||||
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, estimateId);
|
||||
|
||||
await SaleEstimate.query().where('id', estimateId).patch({
|
||||
convertedToInvoiceId: invoiceId,
|
||||
convertedToInvoiceAt: moment().toMySqlDateTime(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink the converted sale estimates from the given sale invoice.
|
||||
* @param {number} tenantId -
|
||||
* @param {number} invoiceId -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async unlinkConvertedEstimateFromInvoice(
|
||||
tenantId: number,
|
||||
invoiceId: number,
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
await SaleEstimate.query().where({
|
||||
convertedToInvoiceId: invoiceId,
|
||||
}).patch({
|
||||
convertedToInvoiceId: null,
|
||||
convertedToInvoiceAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the sale estimate as delivered.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleEstimateId - Sale estimate id.
|
||||
*/
|
||||
public async deliverSaleEstimate(
|
||||
tenantId: number,
|
||||
saleEstimateId: number,
|
||||
): Promise<void> {
|
||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// Retrieve details of the given sale estimate id.
|
||||
const saleEstimate = await this.getSaleEstimateOrThrowError(tenantId, saleEstimateId);
|
||||
|
||||
// Throws error in case the sale estimate already published.
|
||||
if (saleEstimate.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_ESTIMATE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Record the delivered at on the storage.
|
||||
await SaleEstimate.query().where('id', saleEstimateId).patch({
|
||||
deliveredAt: moment().toMySqlDateTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { omit, sumBy, difference, pick, chain } from 'lodash';
|
||||
import { omit, sumBy, pick, chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import {
|
||||
EventDispatcher,
|
||||
EventDispatcherInterface,
|
||||
@@ -10,7 +11,9 @@ import {
|
||||
IItemEntry,
|
||||
ISalesInvoicesFilter,
|
||||
IPaginationMeta,
|
||||
IFilterMeta
|
||||
IFilterMeta,
|
||||
ISaleInvoiceCreateDTO,
|
||||
ISaleInvoiceEditDTO,
|
||||
} from 'interfaces';
|
||||
import events from 'subscribers/events';
|
||||
import JournalPoster from 'services/Accounting/JournalPoster';
|
||||
@@ -23,11 +26,13 @@ import { ServiceError } from 'exceptions';
|
||||
import ItemsService from 'services/Items/ItemsService';
|
||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||
import CustomersService from 'services/Contacts/CustomersService';
|
||||
import SaleEstimateService from 'services/Sales/SalesEstimate';
|
||||
|
||||
|
||||
const ERRORS = {
|
||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
||||
SALE_INVOICE_ALREADY_DELIVERED: 'SALE_INVOICE_ALREADY_DELIVERED',
|
||||
ENTRIES_ITEMS_IDS_NOT_EXISTS: 'ENTRIES_ITEMS_IDS_NOT_EXISTS',
|
||||
NOT_SELLABLE_ITEMS: 'NOT_SELLABLE_ITEMS',
|
||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE'
|
||||
@@ -63,6 +68,9 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
@Inject()
|
||||
customersService: CustomersService;
|
||||
|
||||
@Inject()
|
||||
saleEstimatesService: SaleEstimateService;
|
||||
|
||||
/**
|
||||
*
|
||||
* Validate whether sale invoice number unqiue on the storage.
|
||||
@@ -101,6 +109,33 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
return saleInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform DTO object to model object.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {ISaleInvoiceOTD} saleInvoiceDTO - Sale invoice DTO.
|
||||
*/
|
||||
transformDTOToModel(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO|ISaleInvoiceEditDTO,
|
||||
oldSaleInvoice?: ISaleInvoice
|
||||
): ISaleInvoice {
|
||||
const { ItemEntry } = this.tenancy.models(tenantId);
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
|
||||
return {
|
||||
...formatDateFields(
|
||||
omit(saleInvoiceDTO, ['delivered']),
|
||||
['invoiceDate', 'dueDate']
|
||||
),
|
||||
// Avoid rewrite the deliver date in edit mode when already published.
|
||||
...(saleInvoiceDTO.delivered && (!oldSaleInvoice?.deliveredAt)) && ({
|
||||
deliveredAt: moment().toMySqlDateTime(),
|
||||
}),
|
||||
balance,
|
||||
paymentAmount: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sale invoices and store it to the storage
|
||||
* with associated to entries and journal transactions.
|
||||
@@ -109,18 +144,16 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
* @param {ISaleInvoice} saleInvoiceDTO -
|
||||
* @return {ISaleInvoice}
|
||||
*/
|
||||
public async createSaleInvoice(tenantId: number, saleInvoiceDTO: ISaleInvoiceOTD): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice, ItemEntry } = this.tenancy.models(tenantId);
|
||||
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
public async createSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceDTO: ISaleInvoiceCreateDTO
|
||||
): Promise<ISaleInvoice> {
|
||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||
|
||||
const invLotNumber = 1;
|
||||
|
||||
const saleInvoiceObj: ISaleInvoice = {
|
||||
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']),
|
||||
balance,
|
||||
paymentAmount: 0,
|
||||
// invLotNumber,
|
||||
};
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||
@@ -131,6 +164,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
}
|
||||
// Validate items ids existance.
|
||||
await this.itemsEntriesService.validateItemsIdsExistance(tenantId, saleInvoiceDTO.entries);
|
||||
|
||||
// Validate items should be sellable items.
|
||||
await this.itemsEntriesService.validateNonSellableEntriesItems(tenantId, saleInvoiceDTO.entries);
|
||||
|
||||
this.logger.info('[sale_invoice] inserting sale invoice to the storage.');
|
||||
@@ -165,11 +200,8 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
const balance = sumBy(saleInvoiceDTO.entries, e => ItemEntry.calcAmount(e));
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
|
||||
const saleInvoiceObj = {
|
||||
...formatDateFields(saleInvoiceDTO, ['invoiceDate', 'dueDate']),
|
||||
balance,
|
||||
// invLotNumber: oldSaleInvoice.invLotNumber,
|
||||
};
|
||||
// Transform DTO object to model object.
|
||||
const saleInvoiceObj = this.transformDTOToModel(tenantId, saleInvoiceDTO, oldSaleInvoice);
|
||||
|
||||
// Validate customer existance.
|
||||
await this.customersService.getCustomerByIdOrThrowError(tenantId, saleInvoiceDTO.customerId);
|
||||
@@ -203,10 +235,34 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
await this.eventDispatcher.dispatch(events.saleInvoice.onEdited, {
|
||||
saleInvoice, oldSaleInvoice, tenantId, saleInvoiceId,
|
||||
});
|
||||
|
||||
return saleInvoice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver the given sale invoice.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async deliverSaleInvoice(
|
||||
tenantId: number,
|
||||
saleInvoiceId: number,
|
||||
): Promise<void> {
|
||||
const { saleInvoiceRepository } = this.tenancy.repositories(tenantId);
|
||||
|
||||
// Retrieve details of the given sale invoice id.
|
||||
const saleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
|
||||
// Throws error in case the sale invoice already published.
|
||||
if (saleInvoice.isDelivered) {
|
||||
throw new ServiceError(ERRORS.SALE_INVOICE_ALREADY_DELIVERED);
|
||||
}
|
||||
// Record the delivered at on the storage.
|
||||
await saleInvoiceRepository.update({
|
||||
deliveredAt: moment().toMySqlDateTime()
|
||||
}, { id: saleInvoiceId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given sale invoice with associated entries
|
||||
* and journal transactions.
|
||||
@@ -218,6 +274,12 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
||||
|
||||
const oldSaleInvoice = await this.getInvoiceOrThrowError(tenantId, saleInvoiceId);
|
||||
|
||||
// Unlink the converted sale estimates from the given sale invoice.
|
||||
await this.saleEstimatesService.unlinkConvertedEstimateFromInvoice(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
);
|
||||
|
||||
this.logger.info('[sale_invoice] delete sale invoice with entries.');
|
||||
await SaleInvoice.query().where('id', saleInvoiceId).delete();
|
||||
await ItemEntry.query()
|
||||
|
||||
@@ -66,12 +66,12 @@ export default class SalesReceiptService {
|
||||
*/
|
||||
async validateReceiptDepositAccountExistance(tenantId: number, accountId: number) {
|
||||
const { accountRepository, accountTypeRepository } = this.tenancy.repositories(tenantId);
|
||||
const depositAccount = await accountRepository.findById(accountId);
|
||||
const depositAccount = await accountRepository.findOneById(accountId);
|
||||
|
||||
if (!depositAccount) {
|
||||
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_FOUND);
|
||||
}
|
||||
const depositAccountType = await accountTypeRepository.getTypeMeta(depositAccount.accountTypeId);
|
||||
const depositAccountType = await accountTypeRepository.findOneById(depositAccount.accountTypeId);
|
||||
|
||||
if (!depositAccountType || depositAccountType.childRoot === 'current_asset') {
|
||||
throw new ServiceError(ERRORS.DEPOSIT_ACCOUNT_NOT_CURRENT_ASSET);
|
||||
|
||||
Reference in New Issue
Block a user