mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
refactor: dynamic list to nestjs
This commit is contained in:
@@ -52,6 +52,7 @@
|
||||
"form-data": "^4.0.0",
|
||||
"fp-ts": "^2.16.9",
|
||||
"js-money": "^0.6.3",
|
||||
"is-my-json-valid": "^2.20.5",
|
||||
"knex": "^3.1.0",
|
||||
"lamda": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -66,6 +67,7 @@
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"plaid": "^10.3.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"posthog-node": "^4.3.2",
|
||||
"pug": "^3.0.2",
|
||||
"ramda": "^0.30.1",
|
||||
|
||||
16
packages/server-nest/src/common/types/Features.ts
Normal file
16
packages/server-nest/src/common/types/Features.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export enum Features {
|
||||
WAREHOUSES = 'warehouses',
|
||||
BRANCHES = 'branches',
|
||||
BankSyncing = 'BankSyncing',
|
||||
}
|
||||
|
||||
export interface IFeatureAllItem {
|
||||
name: string;
|
||||
isAccessible: boolean;
|
||||
defaultAccessible: boolean;
|
||||
}
|
||||
|
||||
export interface IFeatureConfiugration {
|
||||
name: string;
|
||||
defaultValue?: boolean;
|
||||
}
|
||||
@@ -140,8 +140,9 @@ export interface IModelMeta {
|
||||
|
||||
print?: IModelPrintMeta;
|
||||
|
||||
fields: { [key: string]: IModelMetaField };
|
||||
columns: { [key: string]: IModelMetaColumn };
|
||||
fields: Record<string, IModelMetaField>;
|
||||
fields2: Record<string, IModelMetaField2>;
|
||||
columns: Record<string, IModelMetaColumn>;
|
||||
}
|
||||
|
||||
// ----
|
||||
|
||||
@@ -12,7 +12,7 @@ import { AccountsApplication } from './AccountsApplication.service';
|
||||
import { CreateAccountDTO } from './CreateAccount.dto';
|
||||
import { EditAccountDTO } from './EditAccount.dto';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
import { IAccountsTransactionsFilter } from './Accounts.types';
|
||||
import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||
// import { IAccountsFilter, IAccountsTransactionsFilter } from './Accounts.types';
|
||||
// import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
||||
|
||||
@@ -64,9 +64,9 @@ export class AccountsController {
|
||||
return this.accountsApplication.getAccount(id);
|
||||
}
|
||||
|
||||
// @Get()
|
||||
// async getAccounts(@Query() filter: IAccountsFilter) {
|
||||
// return this.accountsApplication.getAccounts(filter);
|
||||
// }
|
||||
@Get()
|
||||
async getAccounts(@Query() filter: IAccountsFilter) {
|
||||
return this.accountsApplication.getAccounts(filter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,8 +13,12 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv
|
||||
import { ActivateAccount } from './ActivateAccount.service';
|
||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { BankAccount } from '../BankingTransactions/models/BankAccount';
|
||||
// import { GetAccountsService } from './GetAccounts.service';
|
||||
|
||||
const models = [RegisterTenancyModel(BankAccount)];
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule],
|
||||
controllers: [AccountsController],
|
||||
@@ -31,10 +35,8 @@ import { GetAccountTransactionsService } from './GetAccountTransactions.service'
|
||||
ActivateAccount,
|
||||
GetAccountTypesService,
|
||||
GetAccountTransactionsService,
|
||||
...models,
|
||||
],
|
||||
exports: [
|
||||
AccountRepository,
|
||||
CreateAccountService,
|
||||
]
|
||||
exports: [AccountRepository, CreateAccountService, ...models],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
|
||||
@@ -18,11 +18,11 @@ export enum IAccountsStructureType {
|
||||
}
|
||||
|
||||
// export interface IAccountsFilter extends IDynamicListFilterDTO {
|
||||
// stringifiedFilterRoles?: string;
|
||||
// onlyInactive: boolean;
|
||||
// structure?: IAccountsStructureType;
|
||||
// }
|
||||
export interface IAccountsFilter {}
|
||||
export interface IAccountsFilter {
|
||||
onlyInactive: boolean;
|
||||
structure?: IAccountsStructureType;
|
||||
}
|
||||
export interface IAccountType {
|
||||
label: string;
|
||||
key: string;
|
||||
@@ -88,7 +88,4 @@ export interface CreateAccountParams {
|
||||
ignoreUniqueName: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface IGetAccountTransactionPOJO {
|
||||
|
||||
}
|
||||
export interface IGetAccountTransactionPOJO {}
|
||||
|
||||
@@ -11,9 +11,12 @@ import { ActivateAccount } from './ActivateAccount.service';
|
||||
import { GetAccountTypesService } from './GetAccountTypes.service';
|
||||
import { GetAccountTransactionsService } from './GetAccountTransactions.service';
|
||||
import {
|
||||
IAccountsFilter,
|
||||
IAccountsTransactionsFilter,
|
||||
IGetAccountTransactionPOJO,
|
||||
} from './Accounts.types';
|
||||
import { GetAccountsService } from './GetAccounts.service';
|
||||
import { IFilterMeta } from '@/interfaces/Model';
|
||||
|
||||
@Injectable()
|
||||
export class AccountsApplication {
|
||||
@@ -24,8 +27,8 @@ export class AccountsApplication {
|
||||
private readonly activateAccountService: ActivateAccount,
|
||||
private readonly getAccountTypesService: GetAccountTypesService,
|
||||
private readonly getAccountService: GetAccount,
|
||||
// private readonly getAccountsService: GetAccounts,
|
||||
private readonly getAccountTransactionsService: GetAccountTransactionsService,
|
||||
private readonly getAccountsService: GetAccountsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -96,17 +99,16 @@ export class AccountsApplication {
|
||||
return this.getAccountTypesService.getAccountsTypes();
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the accounts list.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IAccountsFilter} filterDTO
|
||||
// * @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
// */
|
||||
// public getAccounts = (
|
||||
// filterDTO: IAccountsFilter,
|
||||
// ): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> => {
|
||||
// return this.getAccountsService.getAccountsList(filterDTO);
|
||||
// };
|
||||
/**
|
||||
* Retrieves the accounts list.
|
||||
* @param {IAccountsFilter} filterDTO - Filter DTO.
|
||||
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
*/
|
||||
public getAccounts = (
|
||||
filterDTO: IAccountsFilter,
|
||||
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> => {
|
||||
return this.getAccountsService.getAccountsList(filterDTO);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given account transactions.
|
||||
|
||||
@@ -1,67 +1,66 @@
|
||||
// import { Injectable } from '@nestjs/common';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IAccountsFilter,
|
||||
// IAccountResponse,
|
||||
// IFilterMeta,
|
||||
// } from './Accounts.types';
|
||||
// import { DynamicListService } from '../DynamicListing/DynamicListService';
|
||||
// import { AccountTransformer } from './Account.transformer';
|
||||
// import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
// import { Account } from './models/Account.model';
|
||||
// import { AccountRepository } from './repositories/Account.repository';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as R from 'ramda';
|
||||
import { IAccountsFilter } from './Accounts.types';
|
||||
import { DynamicListService } from '../DynamicListing/DynamicList.service';
|
||||
import { AccountTransformer } from './Account.transformer';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { Account } from './models/Account.model';
|
||||
import { AccountRepository } from './repositories/Account.repository';
|
||||
import { IFilterMeta } from '@/interfaces/Model';
|
||||
|
||||
// @Injectable()
|
||||
// export class GetAccountsService {
|
||||
// constructor(
|
||||
// private readonly dynamicListService: DynamicListService,
|
||||
// private readonly transformerService: TransformerInjectable,
|
||||
// private readonly accountModel: typeof Account,
|
||||
// private readonly accountRepository: AccountRepository,
|
||||
// ) {}
|
||||
@Injectable()
|
||||
export class GetAccountsService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformerService: TransformerInjectable,
|
||||
|
||||
// /**
|
||||
// * Retrieve accounts datatable list.
|
||||
// * @param {IAccountsFilter} accountsFilter
|
||||
// * @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
// */
|
||||
// public async getAccountsList(
|
||||
// filterDTO: IAccountsFilter,
|
||||
// ): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> {
|
||||
// // Parses the stringified filter roles.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: typeof Account,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
) {}
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicList = await this.dynamicListService.dynamicList(
|
||||
// this.accountModel,
|
||||
// filter,
|
||||
// );
|
||||
// // Retrieve accounts model based on the given query.
|
||||
// const accounts = await this.accountModel.query().onBuild((builder) => {
|
||||
// dynamicList.buildQuery()(builder);
|
||||
// builder.modify('inactiveMode', filter.inactiveMode);
|
||||
// });
|
||||
// const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
/**
|
||||
* Retrieve accounts datatable list.
|
||||
* @param {IAccountsFilter} accountsFilter
|
||||
* @returns {Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }>}
|
||||
*/
|
||||
public async getAccountsList(
|
||||
filterDTO: IAccountsFilter,
|
||||
): Promise<{ accounts: Account[]; filterMeta: IFilterMeta }> {
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Retrieves the transformed accounts collection.
|
||||
// const transformedAccounts = await this.transformerService.transform(
|
||||
// accounts,
|
||||
// new AccountTransformer(),
|
||||
// { accountsGraph, structure: filterDTO.structure },
|
||||
// );
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
this.accountModel,
|
||||
filter,
|
||||
);
|
||||
// Retrieve accounts model based on the given query.
|
||||
const accounts = await this.accountModel.query().onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
});
|
||||
const accountsGraph = await this.accountRepository.getDependencyGraph();
|
||||
|
||||
// return {
|
||||
// accounts: transformedAccounts,
|
||||
// filterMeta: dynamicList.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
// Retrieves the transformed accounts collection.
|
||||
const transformedAccounts = await this.transformerService.transform(
|
||||
accounts,
|
||||
new AccountTransformer(),
|
||||
{ accountsGraph, structure: filterDTO.structure },
|
||||
);
|
||||
|
||||
// /**
|
||||
// * Parsees accounts list filter DTO.
|
||||
// * @param filterDTO
|
||||
// * @returns
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
return {
|
||||
accounts: transformedAccounts,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsees accounts list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { BankingTransactionsApplication } from './BankingTransactionsApplication.service';
|
||||
import { ICashflowNewCommandDTO } from './types/BankingTransactions.types';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
@@ -10,6 +18,11 @@ export class BankingTransactionsController {
|
||||
private readonly bankingTransactionsApplication: BankingTransactionsApplication,
|
||||
) {}
|
||||
|
||||
@Get('')
|
||||
async getBankAccounts(@Query() filterDTO: ICashflowAccountsFilter) {
|
||||
return this.bankingTransactionsApplication.getBankAccounts(filterDTO);
|
||||
}
|
||||
|
||||
@Post()
|
||||
async createTransaction(@Body() transactionDTO: ICashflowNewCommandDTO) {
|
||||
return this.bankingTransactionsApplication.createTransaction(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CreateBankTransactionService } from './commands/CreateBankTransaction.s
|
||||
import { GetBankTransactionService } from './queries/GetBankTransaction.service';
|
||||
import { ICashflowNewCommandDTO } from './types/BankingTransactions.types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GetBankAccountsService } from './queries/GetBankAccounts.service';
|
||||
|
||||
@Injectable()
|
||||
export class BankingTransactionsApplication {
|
||||
@@ -11,7 +12,7 @@ export class BankingTransactionsApplication {
|
||||
private readonly createTransactionService: CreateBankTransactionService,
|
||||
private readonly deleteTransactionService: DeleteCashflowTransaction,
|
||||
private readonly getCashflowTransactionService: GetBankTransactionService,
|
||||
// private readonly getCashflowAccountsService: GetBankingAccountsServic,
|
||||
private readonly getBankAccountsService: GetBankAccountsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -48,11 +49,8 @@ export class BankingTransactionsApplication {
|
||||
/**
|
||||
* Retrieves the cashflow accounts.
|
||||
* @param {ICashflowAccountsFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
public getCashflowAccounts(
|
||||
// filterDTO: ICashflowAccountsFilter,
|
||||
) {
|
||||
// return this.getCashflowAccountsService.getCashflowAccounts(filterDTO);
|
||||
public getBankAccounts(filterDTO: ICashflowAccountsFilter) {
|
||||
return this.getBankAccountsService.getBankAccounts(filterDTO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/* eslint-disable global-require */
|
||||
import { mixin, Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
|
||||
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
|
||||
|
||||
export class BankAccount extends BaseModel {
|
||||
public name!: string;
|
||||
public slug!: string;
|
||||
public code!: string;
|
||||
public index!: number;
|
||||
public accountType!: string;
|
||||
public predefined!: boolean;
|
||||
public currencyCode!: string;
|
||||
public active!: boolean;
|
||||
public bankBalance!: number;
|
||||
public lastFeedsUpdatedAt!: string | null;
|
||||
public amount!: number;
|
||||
public plaidItemId!: number;
|
||||
|
||||
public plaidItem!: PlaidItem;
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'accounts';
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return ['accountTypeLabel'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve account type label.
|
||||
*/
|
||||
get accountTypeLabel() {
|
||||
return AccountTypesUtils.getType(this.accountType, 'label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to mark model as resourceable to viewable and filterable.
|
||||
*/
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
/**
|
||||
* Inactive/Active mode.
|
||||
*/
|
||||
inactiveMode(query, active = false) {
|
||||
query.where('accounts.active', !active);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const AccountTransaction = require('models/AccountTransaction');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Account model may has many transactions.
|
||||
*/
|
||||
transactions: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: AccountTransaction.default,
|
||||
join: {
|
||||
from: 'accounts.id',
|
||||
to: 'accounts_transactions.accountId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines whether the given type equals the account type.
|
||||
* @param {string} accountType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isAccountType(accountType) {
|
||||
const types = castArray(accountType);
|
||||
return types.indexOf(this.accountType) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmine whether the given parent type equals the account type.
|
||||
* @param {string} parentType
|
||||
* @return {boolean}
|
||||
*/
|
||||
isParentType(parentType) {
|
||||
return AccountTypesUtils.isParentTypeEqualsKey(
|
||||
this.accountType,
|
||||
parentType
|
||||
);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Model settings.
|
||||
// */
|
||||
// static get meta() {
|
||||
// return CashflowAccountSettings;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve the default custom views, roles and columns.
|
||||
// */
|
||||
// static get defaultViews() {
|
||||
// return DEFAULT_VIEWS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Model search roles.
|
||||
*/
|
||||
static get searchRoles() {
|
||||
return [
|
||||
{ condition: 'or', fieldKey: 'name', comparator: 'contains' },
|
||||
{ condition: 'or', fieldKey: 'code', comparator: 'like' },
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,61 +1,52 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import { ICashflowAccount, ICashflowAccountsFilter } from '@/interfaces';
|
||||
// import { CashflowAccountTransformer } from './queries/BankAccountTransformer';
|
||||
// import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
// import { ACCOUNT_TYPE } from '@/data/AccountTypes';
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { BankAccount } from '../models/BankAccount';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { CashflowAccountTransformer } from './BankAccountTransformer';
|
||||
import { ACCOUNT_TYPE } from '@/constants/accounts';
|
||||
|
||||
// @Service()
|
||||
// export default class GetCashflowAccountsService {
|
||||
// @Inject()
|
||||
// private tenancy: TenancyService;
|
||||
@Injectable()
|
||||
export class GetBankAccountsService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
@Inject(BankAccount.name)
|
||||
private readonly bankAccountModel: typeof BankAccount
|
||||
) {}
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
/**
|
||||
* Retrieve the cash flow accounts.
|
||||
* @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
|
||||
* @returns {ICashflowAccount[]}
|
||||
*/
|
||||
public async getBankAccounts(
|
||||
filterDTO: ICashflowAccountsFilter,
|
||||
): Promise<BankAccount[]> {
|
||||
// Parsees accounts list filter DTO.
|
||||
const filter = this.dynamicListService.parseStringifiedFilter(filterDTO);
|
||||
|
||||
// /**
|
||||
// * Retrieve the cash flow accounts.
|
||||
// * @param {number} tenantId - Tenant id.
|
||||
// * @param {ICashflowAccountsFilter} filterDTO - Filter DTO.
|
||||
// * @returns {ICashflowAccount[]}
|
||||
// */
|
||||
// public async getCashflowAccounts(
|
||||
// tenantId: number,
|
||||
// filterDTO: ICashflowAccountsFilter
|
||||
// ): Promise<{ cashflowAccounts: ICashflowAccount[] }> {
|
||||
// const { CashflowAccount } = this.tenancy.models(tenantId);
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
BankAccount,
|
||||
filter,
|
||||
);
|
||||
// Retrieve accounts model based on the given query.
|
||||
const accounts = await this.bankAccountModel.query().onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
|
||||
// // Parsees accounts list filter DTO.
|
||||
// const filter = this.dynamicListService.parseStringifiedFilter(filterDTO);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicList = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// CashflowAccount,
|
||||
// filter
|
||||
// );
|
||||
// // Retrieve accounts model based on the given query.
|
||||
// const accounts = await CashflowAccount.query().onBuild((builder) => {
|
||||
// dynamicList.buildQuery()(builder);
|
||||
|
||||
// builder.whereIn('account_type', [
|
||||
// ACCOUNT_TYPE.BANK,
|
||||
// ACCOUNT_TYPE.CASH,
|
||||
// ACCOUNT_TYPE.CREDIT_CARD,
|
||||
// ]);
|
||||
// builder.modify('inactiveMode', filter.inactiveMode);
|
||||
// });
|
||||
// // Retrieves the transformed accounts.
|
||||
// const transformed = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// accounts,
|
||||
// new CashflowAccountTransformer()
|
||||
// );
|
||||
|
||||
// return transformed;
|
||||
// }
|
||||
// }
|
||||
builder.whereIn('account_type', [
|
||||
ACCOUNT_TYPE.BANK,
|
||||
ACCOUNT_TYPE.CASH,
|
||||
ACCOUNT_TYPE.CREDIT_CARD,
|
||||
]);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
});
|
||||
// Retrieves the transformed accounts.
|
||||
const transformed = await this.transformer.transform(
|
||||
accounts,
|
||||
new CashflowAccountTransformer(),
|
||||
);
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
|
||||
import { CreateBill } from './commands/CreateBill.service';
|
||||
import { EditBillService } from './commands/EditBill.service';
|
||||
import { GetBill } from './queries/GetBill';
|
||||
// import { GetBills } from './queries/GetBills';
|
||||
import { DeleteBill } from './commands/DeleteBill.service';
|
||||
import {
|
||||
IBillDTO,
|
||||
IBillEditDTO,
|
||||
} from './Bills.types';
|
||||
import { IBillDTO, IBillEditDTO } from './Bills.types';
|
||||
import { GetDueBills } from './queries/GetDueBills.service';
|
||||
import { OpenBillService } from './commands/OpenBill.service';
|
||||
import { GetBillPayments } from './queries/GetBillPayments';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GetBillsService } from './queries/GetBills.service';
|
||||
|
||||
@Injectable()
|
||||
export class BillsApplication {
|
||||
@@ -22,7 +19,7 @@ export class BillsApplication {
|
||||
private deleteBillService: DeleteBill,
|
||||
private getDueBillsService: GetDueBills,
|
||||
private openBillService: OpenBillService,
|
||||
// private getBillsService: GetBills,
|
||||
private getBillsService: GetBillsService,
|
||||
// private getBillPaymentsService: GetBillPayments,
|
||||
) {}
|
||||
|
||||
@@ -56,14 +53,11 @@ export class BillsApplication {
|
||||
|
||||
/**
|
||||
* Retrieve bills data table list.
|
||||
* @param {number} tenantId -
|
||||
* @param {IBillsFilter} billsFilter -
|
||||
*/
|
||||
// public getBills(
|
||||
// filterDTO: IBillsFilter,
|
||||
// ) {
|
||||
// return this.getBillsService.getBills(filterDTO);
|
||||
// }
|
||||
public getBills(filterDTO: IBillsFilter) {
|
||||
return this.getBillsService.getBills(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given bill details.
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Param,
|
||||
Delete,
|
||||
Get,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { BillsApplication } from './Bills.application';
|
||||
import { IBillDTO, IBillEditDTO } from './Bills.types';
|
||||
@@ -31,6 +32,11 @@ export class BillsController {
|
||||
return this.billsApplication.deleteBill(billId);
|
||||
}
|
||||
|
||||
@Get()
|
||||
getBills(@Query() filterDTO: IBillsFilter) {
|
||||
return this.billsApplication.getBills(filterDTO);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
getBill(@Param('id') billId: number) {
|
||||
return this.billsApplication.getBill(billId);
|
||||
|
||||
@@ -21,6 +21,8 @@ import { BillGLEntriesSubscriber } from './subscribers/BillGLEntriesSubscriber';
|
||||
import { BillGLEntries } from './commands/BillsGLEntries';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { BillWriteInventoryTransactionsSubscriber } from './subscribers/BillWriteInventoryTransactionsSubscriber';
|
||||
import { BillInventoryTransactions } from './commands/BillInventoryTransactions';
|
||||
|
||||
@Module({
|
||||
imports: [BillLandedCostsModule, LedgerModule, AccountsModule],
|
||||
@@ -43,6 +45,8 @@ import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
BillGLEntries,
|
||||
ItemsEntriesService,
|
||||
BillGLEntriesSubscriber,
|
||||
BillInventoryTransactions,
|
||||
BillWriteInventoryTransactionsSubscriber,
|
||||
],
|
||||
controllers: [BillsController],
|
||||
})
|
||||
|
||||
@@ -70,8 +70,6 @@ export interface IBillEditingPayload {
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
export interface IBillEditedPayload {
|
||||
// tenantId: number;
|
||||
// billId: number;
|
||||
oldBill: Bill;
|
||||
bill: Bill;
|
||||
billDTO: IBillDTO;
|
||||
|
||||
@@ -1,82 +1,73 @@
|
||||
// import { Knex } from 'knex';
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import InventoryService from '@/services/Inventory/Inventory';
|
||||
// import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { Knex } from 'knex';
|
||||
import { Bill } from '../models/Bill';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
||||
|
||||
// @Service()
|
||||
// export class BillInventoryTransactions {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
@Injectable()
|
||||
export class BillInventoryTransactions {
|
||||
constructor(
|
||||
private readonly itemsEntriesService: ItemsEntriesService,
|
||||
private readonly inventoryService: InventoryService,
|
||||
|
||||
// @Inject()
|
||||
// private itemsEntriesService: ItemsEntriesService;
|
||||
private readonly bill: typeof Bill
|
||||
) {}
|
||||
|
||||
// @Inject()
|
||||
// private inventoryService: InventoryService;
|
||||
/**
|
||||
* Records the inventory transactions from the given bill input.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTransactions(
|
||||
billId: number,
|
||||
override?: boolean,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
// Retireve bill with assocaited entries and allocated cost entries.
|
||||
|
||||
const bill = await this.bill.query(trx)
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries.allocatedCostEntries');
|
||||
|
||||
// /**
|
||||
// * Records the inventory transactions from the given bill input.
|
||||
// * @param {Bill} bill - Bill model object.
|
||||
// * @param {number} billId - Bill id.
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// public async recordInventoryTransactions(
|
||||
// tenantId: number,
|
||||
// billId: number,
|
||||
// override?: boolean,
|
||||
// trx?: Knex.Transaction
|
||||
// ): Promise<void> {
|
||||
// const { Bill } = this.tenancy.models(tenantId);
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
bill.entries
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: bill.id,
|
||||
transactionType: 'Bill',
|
||||
exchangeRate: bill.exchangeRate,
|
||||
|
||||
// // Retireve bill with assocaited entries and allocated cost entries.
|
||||
// const bill = await Bill.query(trx)
|
||||
// .findById(billId)
|
||||
// .withGraphFetched('entries.allocatedCostEntries');
|
||||
date: bill.billDate,
|
||||
direction: 'IN',
|
||||
entries: inventoryEntries,
|
||||
createdAt: bill.createdAt,
|
||||
|
||||
// // Loads the inventory items entries of the given sale invoice.
|
||||
// const inventoryEntries =
|
||||
// await this.itemsEntriesService.filterInventoryEntries(
|
||||
// tenantId,
|
||||
// bill.entries
|
||||
// );
|
||||
// const transaction = {
|
||||
// transactionId: bill.id,
|
||||
// transactionType: 'Bill',
|
||||
// exchangeRate: bill.exchangeRate,
|
||||
warehouseId: bill.warehouseId,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
transaction,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
// date: bill.billDate,
|
||||
// direction: 'IN',
|
||||
// entries: inventoryEntries,
|
||||
// createdAt: bill.createdAt,
|
||||
|
||||
// warehouseId: bill.warehouseId,
|
||||
// };
|
||||
// await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
// tenantId,
|
||||
// transaction,
|
||||
// override,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Reverts the inventory transactions of the given bill id.
|
||||
// * @param {number} tenantId - Tenant id.
|
||||
// * @param {number} billId - Bill id.
|
||||
// * @return {Promise<void>}
|
||||
// */
|
||||
// public async revertInventoryTransactions(
|
||||
// tenantId: number,
|
||||
// billId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) {
|
||||
// // Deletes the inventory transactions by the given reference id and type.
|
||||
// await this.inventoryService.deleteInventoryTransactions(
|
||||
// tenantId,
|
||||
// billId,
|
||||
// 'Bill',
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* Reverts the inventory transactions of the given bill id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertInventoryTransactions(
|
||||
billId: number,
|
||||
trx?: Knex.Transaction
|
||||
) {
|
||||
// Deletes the inventory transactions by the given reference id and type.
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
billId,
|
||||
'Bill',
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as R from 'ramda';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { Bill } from '../models/Bill';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { BillTransformer } from './Bill.transformer';
|
||||
|
||||
@Injectable()
|
||||
export class GetBillsService {
|
||||
constructor(
|
||||
private transformer: TransformerInjectable,
|
||||
private dynamicListService: DynamicListService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve bills data table list.
|
||||
* @param {IBillsFilter} billsFilter -
|
||||
*/
|
||||
public async getBills(
|
||||
filterDTO: IBillsFilter,
|
||||
): Promise<{
|
||||
bills: Bill;
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses bills list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
Bill,
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await Bill.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('vendor');
|
||||
builder.withGraphFetched('entries.item');
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
|
||||
// Filter query.
|
||||
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Tranform the bills to POJO.
|
||||
const bills = await this.transformer.transform(
|
||||
results,
|
||||
new BillTransformer(),
|
||||
);
|
||||
return {
|
||||
bills,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses bills list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// import { Injectable } from '@nestjs/common';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IBill,
|
||||
// IBillsFilter,
|
||||
// IFilterMeta,
|
||||
// IPaginationMeta,
|
||||
// } from '@/interfaces';
|
||||
// import { BillTransformer } from './Bill.transformer';
|
||||
// import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
// // import { DynamicListingService } from '@/modules/DynamicListing/DynamicListService';
|
||||
|
||||
// @Injectable()
|
||||
// export class GetBills {
|
||||
// constructor(
|
||||
// private transformer: TransformerInjectable,
|
||||
// private dynamicListService: DynamicListingService,
|
||||
// ) {}
|
||||
|
||||
// /**
|
||||
// * Retrieve bills data table list.
|
||||
// * @param {number} tenantId -
|
||||
// * @param {IBillsFilter} billsFilter -
|
||||
// */
|
||||
// public async getBills(
|
||||
// tenantId: number,
|
||||
// filterDTO: IBillsFilter,
|
||||
// ): Promise<{
|
||||
// bills: IBill;
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// // Parses bills list filter DTO.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// Bill,
|
||||
// filter,
|
||||
// );
|
||||
// const { results, pagination } = await Bill.query()
|
||||
// .onBuild((builder) => {
|
||||
// builder.withGraphFetched('vendor');
|
||||
// builder.withGraphFetched('entries.item');
|
||||
// dynamicFilter.buildQuery()(builder);
|
||||
|
||||
// // Filter query.
|
||||
// filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Tranform the bills to POJO.
|
||||
// const bills = await this.transformer.transform(
|
||||
// results,
|
||||
// new PurchaseInvoiceTransformer(),
|
||||
// );
|
||||
// return {
|
||||
// bills,
|
||||
// pagination,
|
||||
// filterMeta: dynamicFilter.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parses bills list filter DTO.
|
||||
// * @param filterDTO -
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
IBIllEventDeletedPayload,
|
||||
IBillOpenedPayload,
|
||||
} from '../Bills.types';
|
||||
import { BillInventoryTransactions } from '../commands/BillInventoryTransactions';
|
||||
import { events } from '@/common/events/events';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class BillWriteInventoryTransactionsSubscriber {
|
||||
constructor(private readonly billsInventory: BillInventoryTransactions) {}
|
||||
|
||||
/**
|
||||
* Handles writing the inventory transactions once bill created.
|
||||
* @param {IBillCreatedPayload | IBillOpenedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.bill.onCreated)
|
||||
@OnEvent(events.bill.onOpened)
|
||||
public async handleWritingInventoryTransactions({
|
||||
bill,
|
||||
trx,
|
||||
}: IBillCreatedPayload | IBillOpenedPayload) {
|
||||
// Can't continue if the bill is not opened yet.
|
||||
if (!bill.openedAt) return null;
|
||||
|
||||
await this.billsInventory.recordInventoryTransactions(bill.id, false, trx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the overwriting the inventory transactions once bill edited.
|
||||
* @param {IBillEditedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.bill.onEdited)
|
||||
public async handleOverwritingInventoryTransactions({
|
||||
bill,
|
||||
trx,
|
||||
}: IBillEditedPayload) {
|
||||
// Can't continue if the bill is not opened yet.
|
||||
if (!bill.openedAt) return null;
|
||||
|
||||
await this.billsInventory.recordInventoryTransactions(bill.id, true, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the reverting the inventory transactions once the bill deleted.
|
||||
* @param {IBIllEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.bill.onDeleted)
|
||||
public async handleRevertInventoryTransactions({
|
||||
billId,
|
||||
trx,
|
||||
}: IBIllEventDeletedPayload) {
|
||||
await this.billsInventory.revertInventoryTransactions(billId, trx);
|
||||
}
|
||||
}
|
||||
@@ -1,90 +1,82 @@
|
||||
// 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';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
// @Service()
|
||||
// export default class CreditNoteInventoryTransactions {
|
||||
// @Inject()
|
||||
// inventoryService: InventoryService;
|
||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||
import { CreditNote } from '../models/CreditNote';
|
||||
import { Knex } from 'knex';
|
||||
@Injectable()
|
||||
export class CreditNoteInventoryTransactions {
|
||||
constructor(
|
||||
private readonly inventoryService: InventoryService,
|
||||
private readonly itemsEntriesService: ItemsEntriesService,
|
||||
) {}
|
||||
|
||||
// @Inject()
|
||||
// itemsEntriesService: ItemsEntriesService;
|
||||
/**
|
||||
* Creates credit note inventory transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {ICreditNote} creditNote
|
||||
*/
|
||||
public createInventoryTransactions = async (
|
||||
creditNote: CreditNote,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(creditNote.entries);
|
||||
|
||||
// /**
|
||||
// * 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
|
||||
// );
|
||||
// };
|
||||
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(
|
||||
transaction,
|
||||
false,
|
||||
trx,
|
||||
);
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Edits vendor credit associated 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);
|
||||
/**
|
||||
* Edits vendor credit associated inventory transactions.
|
||||
* @param {number} tenantId
|
||||
* @param {number} creditNoteId
|
||||
* @param {ICreditNote} creditNote
|
||||
* @param {Knex.Transactions} trx
|
||||
*/
|
||||
public editInventoryTransactions = async (
|
||||
creditNoteId: number,
|
||||
creditNote: CreditNote,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Deletes inventory transactions.
|
||||
await this.deleteInventoryTransactions(creditNoteId, trx);
|
||||
|
||||
// // Re-write inventory transactions.
|
||||
// await this.createInventoryTransactions(tenantId, creditNote, trx);
|
||||
// };
|
||||
// Re-write inventory transactions.
|
||||
await this.createInventoryTransactions(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
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
/**
|
||||
* Deletes credit note associated inventory transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} creditNoteId - Credit note id.
|
||||
* @param {Knex.Transaction} trx -
|
||||
*/
|
||||
public deleteInventoryTransactions = async (
|
||||
creditNoteId: number,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
// Deletes the inventory transactions by the given reference id and type.
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
creditNoteId,
|
||||
'CreditNote',
|
||||
trx,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,98 +1,74 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import events from '@/subscribers/events';
|
||||
// import CreditNoteInventoryTransactions from '../commands/CreditNotesInventoryTransactions';
|
||||
// import {
|
||||
// ICreditNoteCreatedPayload,
|
||||
// ICreditNoteDeletedPayload,
|
||||
// ICreditNoteEditedPayload,
|
||||
// } from '@/interfaces';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
ICreditNoteCreatedPayload,
|
||||
ICreditNoteDeletedPayload,
|
||||
ICreditNoteEditedPayload,
|
||||
} from '../types/CreditNotes.types';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { CreditNoteInventoryTransactions } from '../commands/CreditNotesInventoryTransactions';
|
||||
|
||||
// @Service()
|
||||
// export default class CreditNoteInventoryTransactionsSubscriber {
|
||||
// @Inject()
|
||||
// inventoryTransactions: CreditNoteInventoryTransactions;
|
||||
@Injectable()
|
||||
export class CreditNoteInventoryTransactionsSubscriber {
|
||||
constructor(
|
||||
private readonly inventoryTransactions: CreditNoteInventoryTransactions;
|
||||
) {}
|
||||
|
||||
// /**
|
||||
// * Attaches events with publisher.
|
||||
// */
|
||||
// public 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 -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@OnEvent(events.creditNote.onCreated)
|
||||
@OnEvent(events.creditNote.onOpened)
|
||||
public async writeInventoryTranscationsOnceCreated({
|
||||
creditNote,
|
||||
trx,
|
||||
}: ICreditNoteCreatedPayload) {
|
||||
// Can't continue if the credit note is open yet.
|
||||
if (!creditNote.isOpen) return;
|
||||
|
||||
// /**
|
||||
// * Writes inventory transactions once credit note created.
|
||||
// * @param {ICreditNoteCreatedPayload} payload -
|
||||
// * @returns {Promise<void>}
|
||||
// */
|
||||
// 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(
|
||||
creditNote,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
// await this.inventoryTransactions.createInventoryTransactions(
|
||||
// tenantId,
|
||||
// creditNote,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
/**
|
||||
* Rewrites inventory transactions once credit note edited.
|
||||
* @param {ICreditNoteEditedPayload} payload -
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@OnEvent(events.creditNote.onEdited)
|
||||
public async rewriteInventoryTransactionsOnceEdited({
|
||||
creditNote,
|
||||
trx,
|
||||
}: ICreditNoteEditedPayload) {
|
||||
// Can't continue if the credit note is open yet.
|
||||
if (!creditNote.isOpen) return;
|
||||
|
||||
// /**
|
||||
// * Rewrites inventory transactions once credit note edited.
|
||||
// * @param {ICreditNoteEditedPayload} payload -
|
||||
// * @returns {Promise<void>}
|
||||
// */
|
||||
// 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(
|
||||
creditNoteId,
|
||||
creditNote,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
// await this.inventoryTransactions.editInventoryTransactions(
|
||||
// tenantId,
|
||||
// creditNoteId,
|
||||
// creditNote,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
/**
|
||||
* Reverts inventory transactions once credit note deleted.
|
||||
* @param {ICreditNoteDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.creditNote.onDeleted)
|
||||
public async revertInventoryTransactionsOnceDeleted({
|
||||
oldCreditNote,
|
||||
trx,
|
||||
}: ICreditNoteDeletedPayload) {
|
||||
// Can't continue if the credit note is open yet.
|
||||
if (!oldCreditNote.isOpen) return;
|
||||
|
||||
// /**
|
||||
// * 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
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
await this.inventoryTransactions.deleteInventoryTransactions(
|
||||
creditNoteId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Model as ObjectionModel } from 'objection';
|
||||
import { BaseModel } from "@/models/Model";
|
||||
;
|
||||
type GConstructor<T = {}> = new (...args: any[]) => T;
|
||||
|
||||
export const CustomViewBaseModel = (Model) =>
|
||||
class extends Model {
|
||||
export const CustomViewBaseModelMixin = <T extends GConstructor<BaseModel>>(Model: T) =>
|
||||
class CustomViewBaseModel extends Model {
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CreateCustomer } from './commands/CreateCustomer.service';
|
||||
import { CustomerValidators } from './commands/CustomerValidators.service';
|
||||
import { EditCustomer } from './commands/EditCustomer.service';
|
||||
import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service';
|
||||
import { GetCustomerService } from './commands/GetCustomer.service';
|
||||
import { GetCustomerService } from './queries/GetCustomer.service';
|
||||
import { CreateEditCustomerDTO } from './commands/CreateEditCustomerDTO.service';
|
||||
import { CustomersController } from './Customers.controller';
|
||||
import { CustomersApplication } from './CustomersApplication.service';
|
||||
@@ -29,6 +29,7 @@ import { DeleteCustomer } from './commands/DeleteCustomer.service';
|
||||
DeleteCustomer,
|
||||
TenancyContext,
|
||||
TransformerInjectable,
|
||||
GetCustomerService
|
||||
],
|
||||
})
|
||||
export class CustomersModule {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GetCustomerService } from './commands/GetCustomer.service';
|
||||
import { GetCustomerService } from './queries/GetCustomer.service';
|
||||
import { CreateCustomer } from './commands/CreateCustomer.service';
|
||||
import { EditCustomer } from './commands/EditCustomer.service';
|
||||
import { DeleteCustomer } from './commands/DeleteCustomer.service';
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// ICustomer,
|
||||
// ICustomersFilter,
|
||||
// IFilterMeta,
|
||||
// IPaginationMeta,
|
||||
// } from '@/interfaces';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
// import CustomerTransfromer from '../queries/CustomerTransformer';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
|
||||
// @Service()
|
||||
// export class GetCustomers {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
|
||||
// /**
|
||||
// * Parses customers list filter DTO.
|
||||
// * @param filterDTO -
|
||||
// */
|
||||
// private parseCustomersListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Retrieve customers paginated list.
|
||||
// * @param {number} tenantId - Tenant id.
|
||||
// * @param {ICustomersFilter} filter - Cusotmers filter.
|
||||
// */
|
||||
// public async getCustomersList(
|
||||
// filterDTO: ICustomersFilter
|
||||
// ): Promise<{
|
||||
// customers: ICustomer[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// const { Customer } = this.tenancy.models(tenantId);
|
||||
|
||||
// // Parses customers list filter DTO.
|
||||
// const filter = this.parseCustomersListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic list.
|
||||
// const dynamicList = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// Customer,
|
||||
// filter
|
||||
// );
|
||||
// // Customers.
|
||||
// const { results, pagination } = await Customer.query()
|
||||
// .onBuild((builder) => {
|
||||
// dynamicList.buildQuery()(builder);
|
||||
// builder.modify('inactiveMode', filter.inactiveMode);
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Retrieves the transformed customers.
|
||||
// const customers = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new CustomerTransfromer()
|
||||
// );
|
||||
// return {
|
||||
// customers,
|
||||
// pagination,
|
||||
// filterMeta: dynamicList.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { CustomerTransfromer } from '../queries/CustomerTransformer';
|
||||
import { CustomerTransfromer } from './CustomerTransformer';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { Customer } from '../models/Customer';
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as R from 'ramda';
|
||||
import { Customer } from '../models/Customer';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { CustomerTransfromer } from './CustomerTransformer';
|
||||
|
||||
@Injectable()
|
||||
export class GetCustomers {
|
||||
constructor(
|
||||
private dynamicListService: DynamicListService,
|
||||
private transformer: TransformerInjectable,
|
||||
|
||||
@Inject(Customer.name) private customerModel: typeof Customer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parses customers list filter DTO.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseCustomersListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve customers paginated list.
|
||||
* @param {ICustomersFilter} filter - Cusotmers filter.
|
||||
*/
|
||||
public async getCustomersList(filterDTO: ICustomersFilter): Promise<{
|
||||
customers: Customer[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses customers list filter DTO.
|
||||
const filter = this.parseCustomersListFilterDTO(filterDTO);
|
||||
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
Customer,
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await this.customerModel
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
dynamicList.buildQuery()(builder);
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Retrieves the transformed customers.
|
||||
const customers = await this.transformer.transform(
|
||||
results,
|
||||
new CustomerTransfromer(),
|
||||
);
|
||||
return {
|
||||
customers,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
import { forEach } from 'lodash';
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
|
||||
import { IDynamicFilter, IFilterRole, IModel } from '@/interfaces';
|
||||
import { IDynamicFilter, IFilterRole } from './DynamicFilter.types';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
|
||||
|
||||
export class DynamicFilter extends DynamicFilterAbstractor {
|
||||
private model: BaseModel;
|
||||
private dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param {String} tableName -
|
||||
*/
|
||||
constructor(model: BaseModel) {
|
||||
constructor(model: typeof BaseModel) {
|
||||
super();
|
||||
|
||||
this.model = model;
|
||||
@@ -22,7 +20,7 @@ export class DynamicFilter extends DynamicFilterAbstractor {
|
||||
* Registers the given dynamic filter.
|
||||
* @param {IDynamicFilter} filterRole - Filter role.
|
||||
*/
|
||||
public setFilter = (dynamicFilter: IDynamicFilter) => {
|
||||
public setFilter = (dynamicFilter: DynamicFilterRoleAbstractor) => {
|
||||
dynamicFilter.setModel(this.model);
|
||||
dynamicFilter.onInitialize();
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
// import { IModel, ISortOrder } from "./Model";
|
||||
|
||||
export type ISortOrder = 'DESC' | 'ASC';
|
||||
|
||||
export interface IDynamicFilter {
|
||||
setModel(model: BaseModel): void;
|
||||
setModel(model: typeof BaseModel): void;
|
||||
onInitialize(): void;
|
||||
buildQuery(): void;
|
||||
getResponseMeta();
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export interface IDynamicListFilter {
|
||||
filterRoles?: IFilterRole[];
|
||||
columnSortBy: ISortOrder;
|
||||
sortOrder: string;
|
||||
stringifiedFilterRoles: string;
|
||||
stringifiedFilterRoles?: string;
|
||||
searchKeyword?: string;
|
||||
viewSlug?: string;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import { BaseModel } from '@/models/Model';
|
||||
import { IDynamicFilter } from './DynamicFilter.types';
|
||||
|
||||
export class DynamicFilterAbstractor {
|
||||
model: BaseModel;
|
||||
dynamicFilters: IDynamicFilter[];
|
||||
public model: typeof BaseModel;
|
||||
public dynamicFilters: IDynamicFilter[];
|
||||
|
||||
/**
|
||||
* Extract relation table name from relation.
|
||||
|
||||
@@ -2,8 +2,6 @@ import { IFilterRole } from './DynamicFilter.types';
|
||||
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
|
||||
|
||||
export class DynamicFilterAdvancedFilter extends DynamicFilterFilterRoles {
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Array} filterRoles -
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterRoleAbstractor';
|
||||
import { IFilterRole } from '@/interfaces';
|
||||
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
|
||||
|
||||
export class DynamicFilterFilterRoles extends DynamicFilterAbstractor {
|
||||
private filterRoles: IFilterRole[];
|
||||
export class DynamicFilterFilterRoles extends DynamicFilterRoleAbstractor {
|
||||
/**
|
||||
* On initialize filter roles.
|
||||
*/
|
||||
@@ -28,14 +26,14 @@ export class DynamicFilterFilterRoles extends DynamicFilterAbstractor {
|
||||
/**
|
||||
* Builds database query of view roles.
|
||||
*/
|
||||
protected buildQuery() {
|
||||
public buildQuery() {
|
||||
const logicExpression = this.buildLogicExpression();
|
||||
|
||||
return (builder) => {
|
||||
this.buildFilterQuery(
|
||||
this.model,
|
||||
this.filterRoles,
|
||||
logicExpression
|
||||
logicExpression,
|
||||
)(builder);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import moment from 'moment';
|
||||
import * as R from 'ramda';
|
||||
import { IFilterRole, IDynamicFilter, } from './DynamicFilter.types';
|
||||
import { IFilterRole, IDynamicFilter } from './DynamicFilter.types';
|
||||
import Parser from '@/libs/logic-evaluation/Parser';
|
||||
import { Lexer } from '@/libs/logic-evaluation/Lexer';
|
||||
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
|
||||
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { IMetadataModel } from '../models/MetadataModel';
|
||||
|
||||
export abstract class DynamicFilterAbstractor
|
||||
implements IDynamicFilter
|
||||
{
|
||||
type MetadataModel = typeof BaseModel & IMetadataModel;
|
||||
|
||||
export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
|
||||
protected filterRoles: IFilterRole[] = [];
|
||||
protected tableName: string;
|
||||
protected model: BaseModel;
|
||||
protected model: MetadataModel;
|
||||
protected responseMeta: { [key: string]: any } = {};
|
||||
public relationFields = [];
|
||||
|
||||
@@ -20,7 +21,7 @@ export abstract class DynamicFilterAbstractor
|
||||
* Sets model the dynamic filter service.
|
||||
* @param {IModel} model
|
||||
*/
|
||||
public setModel(model: BaseModel) {
|
||||
public setModel(model: MetadataModel) {
|
||||
this.model = model;
|
||||
this.tableName = model.tableName;
|
||||
}
|
||||
@@ -46,9 +47,9 @@ export abstract class DynamicFilterAbstractor
|
||||
* @return {Function}
|
||||
*/
|
||||
protected buildFilterRolesQuery = (
|
||||
model: IModel,
|
||||
model: typeof BaseModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string = ''
|
||||
logicExpression: string = '',
|
||||
) => {
|
||||
const rolesIndexSet = this.convertRolesMapByIndex(model, roles);
|
||||
|
||||
@@ -67,7 +68,7 @@ export abstract class DynamicFilterAbstractor
|
||||
|
||||
/**
|
||||
* Parses the logic expression to base expression.
|
||||
* @param {string} logicExpression -
|
||||
* @param {string} logicExpression -
|
||||
* @return {string}
|
||||
*/
|
||||
private parseLogicExpression(logicExpression: string): string {
|
||||
@@ -84,9 +85,9 @@ export abstract class DynamicFilterAbstractor
|
||||
* @param {String} logicExpression - Logic expression.
|
||||
*/
|
||||
protected buildFilterQuery = (
|
||||
model: IModel,
|
||||
model: typeof BaseModel,
|
||||
roles: IFilterRole[],
|
||||
logicExpression: string
|
||||
logicExpression: string,
|
||||
) => {
|
||||
const basicExpression = this.parseLogicExpression(logicExpression);
|
||||
|
||||
@@ -98,7 +99,7 @@ export abstract class DynamicFilterAbstractor
|
||||
/**
|
||||
* Retrieve relation column of comparator fieldز
|
||||
*/
|
||||
private getFieldComparatorRelationColumn(field) {
|
||||
protected getFieldComparatorRelationColumn(field: any): string {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
@@ -128,7 +129,7 @@ export abstract class DynamicFilterAbstractor
|
||||
* @param {IModel} model -
|
||||
* @param {Object} role -
|
||||
*/
|
||||
protected buildRoleQuery = (model: BaseModel, role: IFilterRole) => {
|
||||
protected buildRoleQuery = (model: MetadataModel, role: IFilterRole) => {
|
||||
const field = model.getField(role.fieldKey);
|
||||
const comparatorColumn = this.getFieldComparatorColumn(field);
|
||||
|
||||
@@ -160,7 +161,7 @@ export abstract class DynamicFilterAbstractor
|
||||
*/
|
||||
protected booleanRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
comparatorColumn: string,
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
@@ -187,7 +188,7 @@ export abstract class DynamicFilterAbstractor
|
||||
*/
|
||||
protected numberRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
comparatorColumn: string,
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUALS:
|
||||
@@ -230,7 +231,7 @@ export abstract class DynamicFilterAbstractor
|
||||
*/
|
||||
protected textRoleQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
comparatorColumn: string,
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.EQUAL:
|
||||
@@ -266,7 +267,6 @@ export abstract class DynamicFilterAbstractor
|
||||
return (builder) => {
|
||||
builder.where(comparatorColumn, 'LIKE', `%${role.value}`);
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@@ -278,7 +278,7 @@ export abstract class DynamicFilterAbstractor
|
||||
*/
|
||||
protected dateQueryBuilder = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string
|
||||
comparatorColumn: string,
|
||||
) => {
|
||||
switch (role.comparator) {
|
||||
case COMPARATOR_TYPE.AFTER:
|
||||
@@ -302,12 +302,12 @@ export abstract class DynamicFilterAbstractor
|
||||
protected dateQueryInComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
builder,
|
||||
) => {
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
true,
|
||||
).isValid();
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
|
||||
@@ -332,13 +332,13 @@ export abstract class DynamicFilterAbstractor
|
||||
protected dateQueryAfterBeforeComparator = (
|
||||
role: IFilterRole,
|
||||
comparatorColumn: string,
|
||||
builder
|
||||
builder,
|
||||
) => {
|
||||
const comparator = role.comparator === COMPARATOR_TYPE.BEFORE ? '<' : '>';
|
||||
const hasTimeFormat = moment(
|
||||
role.value,
|
||||
'YYYY-MM-DD HH:MM',
|
||||
true
|
||||
true,
|
||||
).isValid();
|
||||
const targetDate = moment(role.value);
|
||||
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
||||
@@ -355,16 +355,14 @@ export abstract class DynamicFilterAbstractor
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers relation field if the given field was relation type
|
||||
* and not registered.
|
||||
* Registers relation field if the given field was relation type and not registered.
|
||||
* @param {string} fieldKey - Field key.
|
||||
*/
|
||||
protected setRelationIfRelationField = (fieldKey: string): void => {
|
||||
const field = this.model.getField(fieldKey);
|
||||
const isAlreadyRegistered = this.relationFields.some(
|
||||
(field) => field === fieldKey
|
||||
(field) => field === fieldKey,
|
||||
);
|
||||
|
||||
if (
|
||||
!isAlreadyRegistered &&
|
||||
field &&
|
||||
@@ -385,4 +383,11 @@ export abstract class DynamicFilterAbstractor
|
||||
* On initialize the registered dynamic filter.
|
||||
*/
|
||||
onInitialize() {}
|
||||
|
||||
buildQuery(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getResponseMeta() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
|
||||
|
||||
export class DynamicFilterSearch extends DynamicFilterFilterRoles {
|
||||
private searchKeyword: string;
|
||||
private filterRoles: IFilterRole[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { FIELD_TYPE } from './constants';
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
|
||||
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
|
||||
|
||||
interface ISortRole {
|
||||
fieldKey: string;
|
||||
order: string;
|
||||
}
|
||||
|
||||
export class DynamicFilterSortBy extends DynamicFilterAbstractor {
|
||||
export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
|
||||
private sortRole: ISortRole = {};
|
||||
|
||||
/**
|
||||
@@ -36,7 +37,7 @@ export class DynamicFilterSortBy extends DynamicFilterAbstractor {
|
||||
* @param field
|
||||
* @returns {string}
|
||||
*/
|
||||
private getFieldComparatorRelationColumn = (field): string => {
|
||||
protected getFieldComparatorRelationColumn(field: any): string {
|
||||
const relation = this.model.relationMappings[field.relationKey];
|
||||
|
||||
if (relation) {
|
||||
@@ -46,7 +47,7 @@ export class DynamicFilterSortBy extends DynamicFilterAbstractor {
|
||||
return `${relationModel.tableName}.${relationField.column}`;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the comparator field column.
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { omit } from 'lodash';
|
||||
import { IView, IViewRole } from '@/interfaces';
|
||||
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
|
||||
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
|
||||
|
||||
export class DynamicFilterViews extends DynamicFilterAbstractor {
|
||||
export class DynamicFilterViews extends DynamicFilterRoleAbstractor {
|
||||
private viewSlug: string;
|
||||
private logicExpression: string;
|
||||
private filterRoles: IViewRole[];
|
||||
private viewColumns = [];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DynamicListService } from './DynamicList.service';
|
||||
import { DynamicListCustomView } from './DynamicListCustomView.service';
|
||||
import { DynamicListSortBy } from './DynamicListSortBy.service';
|
||||
import { DynamicListSearch } from './DynamicListSearch.service';
|
||||
import { DynamicListFilterRoles } from './DynamicListFilterRoles.service';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
DynamicListService,
|
||||
DynamicListCustomView,
|
||||
DynamicListSortBy,
|
||||
DynamicListSearch,
|
||||
DynamicListFilterRoles,
|
||||
],
|
||||
exports: [DynamicListService],
|
||||
})
|
||||
export class DynamicListModule {}
|
||||
@@ -1,18 +1,15 @@
|
||||
import { castArray, isEmpty } from 'lodash';
|
||||
import {
|
||||
IDynamicListFilter,
|
||||
IDynamicListService,
|
||||
} from './DynamicFilter/DynamicFilter.types';
|
||||
import { DynamicListSortBy } from './DynamicListSortBy';
|
||||
import { DynamicListSearch } from './DynamicListSearch';
|
||||
import { DynamicListCustomView } from './DynamicListCustomView';
|
||||
import { IDynamicListFilter } from './DynamicFilter/DynamicFilter.types';
|
||||
import { DynamicListSortBy } from './DynamicListSortBy.service';
|
||||
import { DynamicListSearch } from './DynamicListSearch.service';
|
||||
import { DynamicListCustomView } from './DynamicListCustomView.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListFilterRoles } from './DynamicListFilterRoles';
|
||||
import { DynamicListFilterRoles } from './DynamicListFilterRoles.service';
|
||||
import { DynamicFilter } from './DynamicFilter';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListService implements IDynamicListService {
|
||||
export class DynamicListService {
|
||||
constructor(
|
||||
private dynamicListFilterRoles: DynamicListFilterRoles,
|
||||
private dynamicListSearch: DynamicListSearch,
|
||||
@@ -44,7 +41,10 @@ export class DynamicListService implements IDynamicListService {
|
||||
* @param {IModel} model - Model.
|
||||
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
|
||||
*/
|
||||
public dynamicList = async (model: BaseModel, filter: IDynamicListFilter) => {
|
||||
public dynamicList = async (
|
||||
model: typeof BaseModel,
|
||||
filter: IDynamicListFilter,
|
||||
) => {
|
||||
const dynamicFilter = new DynamicFilter(model);
|
||||
|
||||
// Parses the filter object.
|
||||
@@ -90,7 +90,9 @@ export class DynamicListService implements IDynamicListService {
|
||||
* Parses stringified filter roles.
|
||||
* @param {string} stringifiedFilterRoles - Stringified filter roles.
|
||||
*/
|
||||
public parseStringifiedFilter = (filterRoles: IDynamicListFilter) => {
|
||||
public parseStringifiedFilter<T extends IDynamicListFilter>(
|
||||
filterRoles: T,
|
||||
): T {
|
||||
return {
|
||||
...filterRoles,
|
||||
filterRoles: filterRoles.stringifiedFilterRoles
|
||||
@@ -1 +0,0 @@
|
||||
export class DynamicListAbstract {}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { ERRORS } from './constants';
|
||||
import { DynamicFilterViews } from './DynamicFilter';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { DynamicListServiceAbstract } from './DynamicListServiceAbstract';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListCustomView extends DynamicListAbstract {
|
||||
export class DynamicListCustomView extends DynamicListServiceAbstract {
|
||||
/**
|
||||
* Retreive custom view or throws error not found.
|
||||
* @param {number} tenantId
|
||||
@@ -30,7 +30,7 @@ export class DynamicListCustomView extends DynamicListAbstract {
|
||||
* Dynamic list custom view.
|
||||
* @param {IModel} model
|
||||
* @param {number} customViewId
|
||||
* @returns
|
||||
* @returns {DynamicFilterRoleAbstractor}
|
||||
*/
|
||||
public dynamicListCustomView = async (
|
||||
dynamicFilter: any,
|
||||
@@ -40,6 +40,7 @@ export class DynamicListCustomView extends DynamicListAbstract {
|
||||
|
||||
// Retrieve the custom view or throw not found.
|
||||
const view = await this.getCustomViewOrThrowError(customViewSlug, model);
|
||||
|
||||
return new DynamicFilterViews(view);
|
||||
};
|
||||
}
|
||||
@@ -2,14 +2,14 @@ import * as R from 'ramda';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import validator from 'is-my-json-valid';
|
||||
import { IFilterRole } from './DynamicFilter/DynamicFilter.types';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter';
|
||||
import { ERRORS } from './constants';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListFilterRoles extends DynamicListAbstract {
|
||||
export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
|
||||
/**
|
||||
* Validates filter roles schema.
|
||||
* @param {IFilterRole[]} filterRoles - Filter roles.
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { DynamicFilterSearch } from './DynamicFilter/DynamicFilterSearch';
|
||||
import { DynamicListServiceAbstract } from './DynamicListServiceAbstract';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListSearch extends DynamicListAbstract {
|
||||
export class DynamicListSearch extends DynamicListServiceAbstract {
|
||||
/**
|
||||
* Dynamic list filter roles.
|
||||
* @param {string} searchKeyword - Search keyword.
|
||||
@@ -0,0 +1 @@
|
||||
export class DynamicListServiceAbstract {}
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DynamicListAbstract } from './DynamicListAbstract';
|
||||
import { ISortOrder } from './DynamicFilter/DynamicFilter.types';
|
||||
import { ERRORS } from './constants';
|
||||
import { DynamicFilterSortBy } from './DynamicFilter';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
|
||||
|
||||
@Injectable()
|
||||
export class DynamicListSortBy extends DynamicListAbstract {
|
||||
export class DynamicListSortBy extends DynamicFilterRoleAbstractor {
|
||||
/**
|
||||
* Dynamic list sort by.
|
||||
* @param {BaseModel} model
|
||||
@@ -0,0 +1,26 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export const CustomViewBaseModel = (Model: typeof BaseModel) =>
|
||||
class extends Model {
|
||||
/**
|
||||
* Retrieve the default custom views, roles and columns.
|
||||
*/
|
||||
static get defaultViews() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default view by the given slug.
|
||||
*/
|
||||
static getDefaultViewBySlug(viewSlug) {
|
||||
return this.defaultViews.find((view) => view.slug === viewSlug) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default views.
|
||||
* @returns {IView[]}
|
||||
*/
|
||||
static getDefaultViews() {
|
||||
return this.defaultViews;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
import { get } from 'lodash';
|
||||
import {
|
||||
IModelMeta,
|
||||
IModelMetaField,
|
||||
IModelMetaDefaultSort,
|
||||
} from '@/interfaces/Model';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
const defaultModelMeta = {
|
||||
fields: {},
|
||||
fields2: {},
|
||||
};
|
||||
|
||||
export interface IMetadataModel extends BaseModel {
|
||||
meta: IModelMeta;
|
||||
parsedMeta: IModelMeta;
|
||||
fields: { [key: string]: IModelMetaField };
|
||||
defaultSort: IModelMetaDefaultSort;
|
||||
defaultFilterField: string;
|
||||
|
||||
getField(key: string, attribute?: string): IModelMetaField;
|
||||
getMeta(key?: string): IModelMeta;
|
||||
}
|
||||
|
||||
type GConstructor<T = {}> = new (...args: any[]) => T;
|
||||
|
||||
export const MetadataModelMixin = <T extends GConstructor<BaseModel>>(
|
||||
Model: T,
|
||||
) =>
|
||||
class ModelSettings extends Model {
|
||||
/**
|
||||
* Retrieve the model meta.
|
||||
* @returns {IModelMeta}
|
||||
*/
|
||||
static get meta(): IModelMeta {
|
||||
throw new Error('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsed meta merged with default emta.
|
||||
* @returns {IModelMeta}
|
||||
*/
|
||||
static get parsedMeta(): IModelMeta {
|
||||
return {
|
||||
...defaultModelMeta,
|
||||
...this.meta,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve specific model field meta of the given field key.
|
||||
* @param {string} key
|
||||
* @returns {IModelMetaField}
|
||||
*/
|
||||
public static getField(key: string, attribute?: string): IModelMetaField {
|
||||
const field = get(this.meta.fields, key);
|
||||
|
||||
return attribute ? get(field, attribute) : field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the specific model meta.
|
||||
* @param {string} key
|
||||
* @returns
|
||||
*/
|
||||
public static getMeta(key?: string) {
|
||||
return key ? get(this.parsedMeta, key) : this.parsedMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model meta fields.
|
||||
* @return {{ [key: string]: IModelMetaField }}
|
||||
*/
|
||||
public static get fields(): { [key: string]: IModelMetaField } {
|
||||
return this.getMeta('fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the model default sort settings.
|
||||
* @return {IModelMetaDefaultSort}
|
||||
*/
|
||||
public static get defaultSort(): IModelMetaDefaultSort {
|
||||
return this.getMeta('defaultSort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the default filter field key.
|
||||
* @return {string}
|
||||
*/
|
||||
public static get defaultFilterField(): string {
|
||||
return this.getMeta('defaultFilterField');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { IModelMeta } from '@/interfaces/Model';
|
||||
import { ISearchRole } from '../DynamicFilter.types';
|
||||
|
||||
type GConstructor<T = {}> = new (...args: any[]) => T;
|
||||
|
||||
export const SearchableBaseModelMixin = <T extends GConstructor<BaseModel>>(
|
||||
Model: T,
|
||||
) =>
|
||||
class SearchableBaseModel extends Model {
|
||||
/**
|
||||
* Searchable model.
|
||||
*/
|
||||
static get searchable(): IModelMeta {
|
||||
throw true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search roles.
|
||||
*/
|
||||
static get searchRoles(): ISearchRole[] {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ISortOrder } from '@/interfaces/Model';
|
||||
import { IFilterRole } from '../DynamicFilter/DynamicFilter.types';
|
||||
|
||||
export interface IDynamicListFilter {
|
||||
customViewId?: number;
|
||||
filterRoles?: IFilterRole[];
|
||||
columnSortBy: ISortOrder;
|
||||
sortOrder: string;
|
||||
stringifiedFilterRoles: string;
|
||||
searchKeyword?: string;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ExpensesApplication } from './ExpensesApplication.service';
|
||||
import {
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
IExpenseEditDTO,
|
||||
} from './interfaces/Expenses.interface';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
import { IExpensesFilter } from './Expenses.types';
|
||||
|
||||
@Controller('expenses')
|
||||
@PublicRoute()
|
||||
@@ -59,6 +61,14 @@ export class ExpensesController {
|
||||
return this.expensesApplication.publishExpense(expenseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expense transaction details.
|
||||
*/
|
||||
@Get('')
|
||||
public getExpenses(@Query() filterDTO: IExpensesFilter) {
|
||||
return this.expensesApplication.getExpenses(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expense transaction details.
|
||||
* @param {number} expenseId
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ExpenseGLEntriesStorageService } from './subscribers/ExpenseGLEntriesSt
|
||||
import { ExpenseGLEntriesService } from './subscribers/ExpenseGLEntries.service';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { BranchesModule } from '../Branches/Branches.module';
|
||||
import { GetExpensesService } from './queries/GetExpenses.service';
|
||||
|
||||
@Module({
|
||||
imports: [LedgerModule, BranchesModule],
|
||||
@@ -35,7 +36,9 @@ import { BranchesModule } from '../Branches/Branches.module';
|
||||
TransformerInjectable,
|
||||
ExpensesWriteGLSubscriber,
|
||||
ExpenseGLEntriesStorageService,
|
||||
ExpenseGLEntriesService
|
||||
ExpenseGLEntriesService,
|
||||
GetExpensesService,
|
||||
],
|
||||
})
|
||||
export class ExpensesModule {}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@ import { GetExpenseService } from './queries/GetExpense.service';
|
||||
import {
|
||||
IExpenseCreateDTO,
|
||||
IExpenseEditDTO,
|
||||
IExpensesFilter,
|
||||
} from './interfaces/Expenses.interface';
|
||||
import { GetExpensesService } from './queries/GetExpenses.service';
|
||||
|
||||
@Injectable()
|
||||
export class ExpensesApplication {
|
||||
@@ -17,7 +19,7 @@ export class ExpensesApplication {
|
||||
private readonly deleteExpenseService: DeleteExpense,
|
||||
private readonly publishExpenseService: PublishExpense,
|
||||
private readonly getExpenseService: GetExpenseService,
|
||||
// private readonly getExpensesService: GetExpenseService,
|
||||
private readonly getExpensesService: GetExpensesService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -66,12 +68,11 @@ export class ExpensesApplication {
|
||||
return this.getExpenseService.getExpense(expenseId);
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieve expenses paginated list.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IExpensesFilter} expensesFilter
|
||||
// */
|
||||
// public getExpenses = (tenantId: number, filterDTO: IExpensesFilter) => {
|
||||
// return this.getExpensesService.getExpensesList(tenantId, filterDTO);
|
||||
// };
|
||||
/**
|
||||
* Retrieve expenses paginated list.
|
||||
* @param {IExpensesFilter} expensesFilter
|
||||
*/
|
||||
public getExpenses = (filterDTO: IExpensesFilter) => {
|
||||
return this.getExpensesService.getExpensesList(filterDTO);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,81 +1,70 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IExpensesFilter,
|
||||
// IExpense,
|
||||
// IPaginationMeta,
|
||||
// IFilterMeta,
|
||||
// } from '@/interfaces';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import { ExpenseTransfromer } from './Expense.transformer';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import * as R from 'ramda';
|
||||
import { ExpenseTransfromer } from './Expense.transformer';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { IExpensesFilter, IPaginationMeta } from '../Expenses.types';
|
||||
import { Expense } from '../models/Expense.model';
|
||||
import { IFilterMeta } from '@/interfaces/Model';
|
||||
|
||||
// @Service()
|
||||
// export class GetExpenses {
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
@Injectable()
|
||||
export class GetExpensesService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
@Inject(Expense.name)
|
||||
private readonly expense: typeof Expense,
|
||||
) {}
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
/**
|
||||
* Retrieve expenses paginated list.
|
||||
* @param {IExpensesFilter} expensesFilter
|
||||
* @return {IExpense[]}
|
||||
*/
|
||||
public async getExpensesList(
|
||||
filterDTO: IExpensesFilter
|
||||
): Promise<{
|
||||
expenses: Expense[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses list filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// /**
|
||||
// * Retrieve expenses paginated list.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IExpensesFilter} expensesFilter
|
||||
// * @return {IExpense[]}
|
||||
// */
|
||||
// public getExpensesList = async (
|
||||
// tenantId: number,
|
||||
// filterDTO: IExpensesFilter
|
||||
// ): Promise<{
|
||||
// expenses: IExpense[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> => {
|
||||
// const { Expense } = this.tenancy.models(tenantId);
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
this.expense,
|
||||
filter
|
||||
);
|
||||
// Retrieves the paginated results.
|
||||
const { results, pagination } = await this.expense.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('paymentAccount');
|
||||
builder.withGraphFetched('categories.expenseAccount');
|
||||
|
||||
// // Parses list filter DTO.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
dynamicList.buildQuery()(builder);
|
||||
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicList = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// Expense,
|
||||
// filter
|
||||
// );
|
||||
// // Retrieves the paginated results.
|
||||
// const { results, pagination } = await Expense.query()
|
||||
// .onBuild((builder) => {
|
||||
// builder.withGraphFetched('paymentAccount');
|
||||
// builder.withGraphFetched('categories.expenseAccount');
|
||||
// Transformes the expenses models to POJO.
|
||||
const expenses = await this.transformer.transform(
|
||||
results,
|
||||
new ExpenseTransfromer()
|
||||
);
|
||||
return {
|
||||
expenses,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
};
|
||||
|
||||
// dynamicList.buildQuery()(builder);
|
||||
// filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Transformes the expenses models to POJO.
|
||||
// const expenses = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new ExpenseTransfromer()
|
||||
// );
|
||||
// return {
|
||||
// expenses,
|
||||
// pagination,
|
||||
// filterMeta: dynamicList.getResponseMeta(),
|
||||
// };
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Parses filter DTO of expenses list.
|
||||
// * @param filterDTO -
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* Parses filter DTO of expenses list.
|
||||
* @param filterDTO -
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ import { InventoryAdjustmentsGLSubscriber } from './subscribers/InventoryAdjustm
|
||||
import { InventoryAdjustmentsGLEntries } from './commands/ledger/InventoryAdjustmentsGLEntries';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { InventoryAdjustmentInventoryTransactionsSubscriber } from './inventory/InventoryAdjustmentInventoryTransactionsSubscriber';
|
||||
import { InventoryAdjustmentInventoryTransactions } from './inventory/InventoryAdjustmentInventoryTransactions';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(InventoryAdjustment),
|
||||
@@ -34,6 +36,8 @@ const models = [
|
||||
InventoryAdjustmentsGLSubscriber,
|
||||
InventoryAdjustmentsGLEntries,
|
||||
TenancyContext,
|
||||
InventoryAdjustmentInventoryTransactionsSubscriber,
|
||||
InventoryAdjustmentInventoryTransactions
|
||||
],
|
||||
exports: [...models],
|
||||
})
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { Knex } from "knex";
|
||||
import { InventoryAdjustment } from "../models/InventoryAdjustment";
|
||||
import { InventoryTransaction } from "@/modules/InventoryCost/models/InventoryTransaction";
|
||||
import { InventoryService } from "@/modules/InventoryCost/Inventory";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentInventoryTransactions {
|
||||
constructor(
|
||||
private readonly inventoryService: InventoryService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes the inventory transactions from the inventory adjustment transaction.
|
||||
* @param {number} tenantId -
|
||||
* @param {IInventoryAdjustment} inventoryAdjustment -
|
||||
* @param {boolean} override -
|
||||
* @param {Knex.Transaction} trx -
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async writeInventoryTransactions(
|
||||
inventoryAdjustment: InventoryAdjustment,
|
||||
override: boolean = false,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
const commonTransaction = {
|
||||
direction: inventoryAdjustment.inventoryDirection,
|
||||
date: inventoryAdjustment.date,
|
||||
transactionType: 'InventoryAdjustment',
|
||||
transactionId: inventoryAdjustment.id,
|
||||
createdAt: inventoryAdjustment.createdAt,
|
||||
costAccountId: inventoryAdjustment.adjustmentAccountId,
|
||||
|
||||
branchId: inventoryAdjustment.branchId,
|
||||
warehouseId: inventoryAdjustment.warehouseId,
|
||||
};
|
||||
const inventoryTransactions = [];
|
||||
|
||||
inventoryAdjustment.entries.forEach((entry) => {
|
||||
inventoryTransactions.push({
|
||||
...commonTransaction,
|
||||
itemId: entry.itemId,
|
||||
quantity: entry.quantity,
|
||||
rate: entry.cost,
|
||||
});
|
||||
});
|
||||
// Saves the given inventory transactions to the storage.
|
||||
await this.inventoryService.recordInventoryTransactions(
|
||||
inventoryTransactions,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory transactions from the inventory adjustment transaction.
|
||||
* @param {number} inventoryAdjustmentId
|
||||
*/
|
||||
async revertInventoryTransactions(
|
||||
inventoryAdjustmentId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<{ oldInventoryTransactions: InventoryTransaction[] }> {
|
||||
return this.inventoryService.deleteInventoryTransactions(
|
||||
inventoryAdjustmentId,
|
||||
'InventoryAdjustment',
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
IInventoryAdjustmentEventCreatedPayload,
|
||||
IInventoryAdjustmentEventPublishedPayload,
|
||||
} from '../types/InventoryAdjustments.types';
|
||||
import { IInventoryAdjustmentEventDeletedPayload } from '../types/InventoryAdjustments.types';
|
||||
import { InventoryAdjustmentInventoryTransactions } from './InventoryAdjustmentInventoryTransactions';
|
||||
import { events } from '@/common/events/events';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryAdjustmentInventoryTransactionsSubscriber {
|
||||
constructor(
|
||||
private readonly inventoryTransactions: InventoryAdjustmentInventoryTransactions,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles writing inventory transactions once the quick adjustment created.
|
||||
* @param {IInventoryAdjustmentEventPublishedPayload} payload
|
||||
* @param {IInventoryAdjustmentEventCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onQuickCreated)
|
||||
public async handleWriteInventoryTransactionsOncePublished({
|
||||
inventoryAdjustment,
|
||||
trx,
|
||||
}:
|
||||
| IInventoryAdjustmentEventPublishedPayload
|
||||
| IInventoryAdjustmentEventCreatedPayload) {
|
||||
await this.inventoryTransactions.writeInventoryTransactions(
|
||||
inventoryAdjustment,
|
||||
false,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles reverting invetory transactions once the inventory adjustment deleted.
|
||||
* @param {IInventoryAdjustmentEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onDeleted)
|
||||
public async handleRevertInventoryTransactionsOnceDeleted({
|
||||
inventoryAdjustmentId,
|
||||
oldInventoryAdjustment,
|
||||
trx,
|
||||
}: IInventoryAdjustmentEventDeletedPayload) {
|
||||
// Can't continue if the inventory adjustment is not published.
|
||||
if (!oldInventoryAdjustment.isPublished) {
|
||||
return;
|
||||
}
|
||||
// Reverts the inventory transactions of adjustment transaction.
|
||||
await this.inventoryTransactions.revertInventoryTransactions(
|
||||
inventoryAdjustmentId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
366
packages/server-nest/src/modules/InventoryCost/Inventory.ts
Normal file
366
packages/server-nest/src/modules/InventoryCost/Inventory.ts
Normal file
@@ -0,0 +1,366 @@
|
||||
import { pick } from 'lodash';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
IInventoryLotCost,
|
||||
IInventoryTransaction,
|
||||
TInventoryTransactionDirection,
|
||||
IItemEntry,
|
||||
IItemEntryTransactionType,
|
||||
IInventoryTransactionsCreatedPayload,
|
||||
IInventoryTransactionsDeletedPayload,
|
||||
IInventoryItemCostScheduledPayload,
|
||||
} from '@/interfaces';
|
||||
import { InventoryAverageCostMethod } from './InventoryAverageCost';
|
||||
import { InventoryCostLotTracker } from './InventoryCostLotTracker';
|
||||
import { ItemsEntriesService } from '../Items/ItemsEntries.service';
|
||||
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Item } from '../Items/models/Item';
|
||||
import { SETTINGS_PROVIDER } from '../Settings/Settings.types';
|
||||
import { SettingsStore } from '../Settings/SettingsStore';
|
||||
import { events } from '@/common/events/events';
|
||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
||||
import InventoryCostMethod from './InventoryCostMethod';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryService {
|
||||
constructor(
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly itemsEntriesService: ItemsEntriesService,
|
||||
private readonly uow: UnitOfWork,
|
||||
|
||||
@Inject(Item.name)
|
||||
private readonly itemModel: typeof Item,
|
||||
|
||||
@Inject(InventoryTransaction.name)
|
||||
private readonly inventoryTransactionModel: typeof InventoryTransaction,
|
||||
|
||||
@Inject(InventoryCostLotTracker.name)
|
||||
private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker,
|
||||
|
||||
@Inject(SETTINGS_PROVIDER)
|
||||
private readonly settings: SettingsStore,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Transforms the items entries to inventory transactions.
|
||||
*/
|
||||
transformItemEntriesToInventory(transaction: {
|
||||
transactionId: number;
|
||||
transactionType: IItemEntryTransactionType;
|
||||
transactionNumber?: string;
|
||||
|
||||
exchangeRate?: number;
|
||||
|
||||
warehouseId: number | null;
|
||||
|
||||
date: Date | string;
|
||||
direction: TInventoryTransactionDirection;
|
||||
entries: IItemEntry[];
|
||||
createdAt: Date;
|
||||
}): IInventoryTransaction[] {
|
||||
const exchangeRate = transaction.exchangeRate || 1;
|
||||
|
||||
return transaction.entries.map((entry: IItemEntry) => ({
|
||||
...pick(entry, ['itemId', 'quantity']),
|
||||
rate: entry.rate * exchangeRate,
|
||||
transactionType: transaction.transactionType,
|
||||
transactionId: transaction.transactionId,
|
||||
direction: transaction.direction,
|
||||
date: transaction.date,
|
||||
entryId: entry.id,
|
||||
createdAt: transaction.createdAt,
|
||||
costAccountId: entry.costAccountId,
|
||||
|
||||
warehouseId: entry.warehouseId || transaction.warehouseId,
|
||||
meta: {
|
||||
transactionNumber: transaction.transactionNumber,
|
||||
description: entry.description,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
async computeItemCost(fromDate: Date, itemId: number) {
|
||||
return this.uow.withTransaction((trx: Knex.Transaction) => {
|
||||
return this.computeInventoryItemCost(fromDate, itemId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the given item cost and records the inventory lots transactions
|
||||
* and journal entries based on the cost method FIFO, LIFO or average cost rate.
|
||||
* @param {Date} fromDate - From date.
|
||||
* @param {number} itemId - Item id.
|
||||
*/
|
||||
async computeInventoryItemCost(
|
||||
fromDate: Date,
|
||||
itemId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
// Fetches the item with associated item category.
|
||||
const item = await Item.query().findById(itemId);
|
||||
|
||||
// Cannot continue if the given item was not inventory item.
|
||||
if (item.type !== 'inventory') {
|
||||
throw new Error('You could not compute item cost has no inventory type.');
|
||||
}
|
||||
let costMethodComputer: InventoryCostMethod;
|
||||
|
||||
// Switch between methods based on the item cost method.
|
||||
switch ('AVG') {
|
||||
case 'FIFO':
|
||||
case 'LIFO':
|
||||
costMethodComputer = new InventoryCostLotTracker(
|
||||
tenantId,
|
||||
fromDate,
|
||||
itemId,
|
||||
);
|
||||
break;
|
||||
case 'AVG':
|
||||
costMethodComputer = new InventoryAverageCostMethod(
|
||||
fromDate,
|
||||
itemId,
|
||||
trx,
|
||||
);
|
||||
break;
|
||||
}
|
||||
return costMethodComputer.computeItemCost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule item cost compute job.
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {Date} startingDate
|
||||
*/
|
||||
async scheduleComputeItemCost(
|
||||
tenantId: number,
|
||||
itemId: number,
|
||||
startingDate: Date | string,
|
||||
) {
|
||||
const agenda = Container.get('agenda');
|
||||
|
||||
const commonJobsQuery = {
|
||||
name: 'compute-item-cost',
|
||||
lastRunAt: { $exists: false },
|
||||
'data.tenantId': tenantId,
|
||||
'data.itemId': itemId,
|
||||
};
|
||||
// Cancel any `compute-item-cost` in the queue has upper starting date
|
||||
// with the same given item.
|
||||
await agenda.cancel({
|
||||
...commonJobsQuery,
|
||||
'data.startingDate': { $lte: startingDate },
|
||||
});
|
||||
// Retrieve any `compute-item-cost` in the queue has lower starting date
|
||||
// with the same given item.
|
||||
const dependsJobs = await agenda.jobs({
|
||||
...commonJobsQuery,
|
||||
'data.startingDate': { $gte: startingDate },
|
||||
});
|
||||
// If the depends jobs cleared.
|
||||
if (dependsJobs.length === 0) {
|
||||
await agenda.schedule(
|
||||
config.scheduleComputeItemCost,
|
||||
'compute-item-cost',
|
||||
{
|
||||
startingDate,
|
||||
itemId,
|
||||
tenantId,
|
||||
},
|
||||
);
|
||||
// Triggers `onComputeItemCostJobScheduled` event.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.inventory.onComputeItemCostJobScheduled,
|
||||
{
|
||||
startingDate,
|
||||
itemId,
|
||||
tenantId,
|
||||
} as IInventoryItemCostScheduledPayload,
|
||||
);
|
||||
} else {
|
||||
// Re-schedule the jobs that have higher date from current moment.
|
||||
await Promise.all(
|
||||
dependsJobs.map((job) =>
|
||||
job.schedule(config.scheduleComputeItemCost).save(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async recordInventoryTransactions(
|
||||
transactions: IInventoryTransaction[],
|
||||
override: boolean = false,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const bulkInsertOpers = [];
|
||||
|
||||
transactions.forEach((transaction: IInventoryTransaction) => {
|
||||
const oper = this.recordInventoryTransaction(transaction, override, trx);
|
||||
bulkInsertOpers.push(oper);
|
||||
});
|
||||
const inventoryTransactions = await Promise.all(bulkInsertOpers);
|
||||
|
||||
// Triggers `onInventoryTransactionsCreated` event.
|
||||
await this.eventEmitter.emitAsync(
|
||||
events.inventory.onInventoryTransactionsCreated,
|
||||
{
|
||||
inventoryTransactions,
|
||||
trx,
|
||||
} as IInventoryTransactionsCreatedPayload,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the inventory transactiosn on the storage from the given
|
||||
* inventory transactions entries.
|
||||
*
|
||||
* @param {number} tenantId -
|
||||
* @param {IInventoryTransaction} inventoryEntry -
|
||||
* @param {boolean} deleteOld -
|
||||
*/
|
||||
async recordInventoryTransaction(
|
||||
inventoryEntry: IInventoryTransaction,
|
||||
deleteOld: boolean = false,
|
||||
trx: Knex.Transaction,
|
||||
): Promise<IInventoryTransaction> {
|
||||
if (deleteOld) {
|
||||
await this.deleteInventoryTransactions(
|
||||
inventoryEntry.transactionId,
|
||||
inventoryEntry.transactionType,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
return this.inventoryTransactionModel.query(trx).insertGraph({
|
||||
...inventoryEntry,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory transactions from items entries that have (inventory) type.
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {number} transactionId
|
||||
* @param {string} transactionType
|
||||
* @param {Date|string} transactionDate
|
||||
* @param {boolean} override
|
||||
*/
|
||||
async recordInventoryTransactionsFromItemsEntries(
|
||||
transaction: {
|
||||
transactionId: number;
|
||||
transactionType: IItemEntryTransactionType;
|
||||
exchangeRate: number;
|
||||
|
||||
date: Date | string;
|
||||
direction: TInventoryTransactionDirection;
|
||||
entries: IItemEntry[];
|
||||
createdAt: Date | string;
|
||||
|
||||
warehouseId: number;
|
||||
},
|
||||
override: boolean = false,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
// Can't continue if there is no entries has inventory items in the invoice.
|
||||
if (transaction.entries.length <= 0) {
|
||||
return;
|
||||
}
|
||||
// Inventory transactions.
|
||||
const inventoryTranscations =
|
||||
this.transformItemEntriesToInventory(transaction);
|
||||
|
||||
// Records the inventory transactions of the given sale invoice.
|
||||
await this.recordInventoryTransactions(
|
||||
inventoryTranscations,
|
||||
override,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given inventory transactions.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {string} transactionType
|
||||
* @param {number} transactionId
|
||||
* @return {Promise<{
|
||||
* oldInventoryTransactions: IInventoryTransaction[]
|
||||
* }>}
|
||||
*/
|
||||
async deleteInventoryTransactions(
|
||||
transactionId: number,
|
||||
transactionType: string,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<{ oldInventoryTransactions: InventoryTransaction[] }> {
|
||||
// Retrieve the inventory transactions of the given sale invoice.
|
||||
const oldInventoryTransactions = await this.inventoryTransactionModel
|
||||
.query(trx)
|
||||
.where({ transactionId, transactionType });
|
||||
|
||||
// Deletes the inventory transactions by the given transaction type and id.
|
||||
await this.inventoryTransactionModel
|
||||
.query(trx)
|
||||
.where({ transactionType, transactionId })
|
||||
.delete();
|
||||
|
||||
// Triggers `onInventoryTransactionsDeleted` event.
|
||||
await this.eventEmitter.emitAsync(
|
||||
events.inventory.onInventoryTransactionsDeleted,
|
||||
{
|
||||
oldInventoryTransactions,
|
||||
transactionId,
|
||||
transactionType,
|
||||
trx,
|
||||
} as IInventoryTransactionsDeletedPayload,
|
||||
);
|
||||
return { oldInventoryTransactions };
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the inventory cost lot transaction.
|
||||
* @param {number} tenantId
|
||||
* @param {IInventoryLotCost} inventoryLotEntry
|
||||
* @return {Promise<IInventoryLotCost>}
|
||||
*/
|
||||
async recordInventoryCostLotTransaction(
|
||||
tenantId: number,
|
||||
inventoryLotEntry: IInventoryLotCost,
|
||||
): Promise<void> {
|
||||
return this.inventoryCostLotTracker.query().insert({
|
||||
...inventoryLotEntry,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark item cost computing is running.
|
||||
* @param {boolean} isRunning -
|
||||
*/
|
||||
async markItemsCostComputeRunning(isRunning: boolean = true) {
|
||||
this.settings.set({
|
||||
key: 'cost_compute_running',
|
||||
group: 'inventory',
|
||||
value: isRunning,
|
||||
});
|
||||
await this.settings.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the items cost compute is running.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isItemsCostComputeRunning() {
|
||||
return (
|
||||
this.settings.get({
|
||||
key: 'cost_compute_running',
|
||||
group: 'inventory',
|
||||
}) ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
import { pick } from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import InventoryCostMethod from './InventoryCostMethod';
|
||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
||||
|
||||
export class InventoryAverageCostMethod extends InventoryCostMethod {
|
||||
startingDate: Date;
|
||||
itemId: number;
|
||||
costTransactions: any[];
|
||||
trx: Knex.Transaction;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
* @param {Date} startingDate -
|
||||
* @param {number} itemId - The given inventory item id.
|
||||
*/
|
||||
constructor(
|
||||
tenantId: number,
|
||||
startingDate: Date,
|
||||
itemId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
super(tenantId, startingDate, itemId);
|
||||
|
||||
this.trx = trx;
|
||||
this.startingDate = startingDate;
|
||||
this.itemId = itemId;
|
||||
this.costTransactions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes items costs from the given date using average cost method.
|
||||
* ----------
|
||||
* - Calculate the items average cost in the given date.
|
||||
* - Remove the journal entries that associated to the inventory transacions
|
||||
* after the given date.
|
||||
* - Re-compute the inventory transactions and re-write the journal entries
|
||||
* after the given date.
|
||||
* ----------
|
||||
* @async
|
||||
* @param {Date} startingDate
|
||||
* @param {number} referenceId
|
||||
* @param {string} referenceType
|
||||
*/
|
||||
public async computeItemCost() {
|
||||
const { InventoryTransaction } = this.tenantModels;
|
||||
const { averageCost, openingQuantity, openingCost } =
|
||||
await this.getOpeningAverageCost(this.startingDate, this.itemId);
|
||||
|
||||
const afterInvTransactions =
|
||||
await InventoryTransaction.query()
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
||||
.orderBy('createdAt', 'ASC')
|
||||
.where('item_id', this.itemId)
|
||||
.withGraphFetched('item');
|
||||
|
||||
// Tracking inventroy transactions and retrieve cost transactions based on
|
||||
// average rate cost method.
|
||||
const costTransactions = this.trackingCostTransactions(
|
||||
afterInvTransactions,
|
||||
openingQuantity,
|
||||
openingCost,
|
||||
);
|
||||
// Revert the inveout out lots transactions
|
||||
await this.revertTheInventoryOutLotTrans();
|
||||
|
||||
// Store inventory lots cost transactions.
|
||||
await this.storeInventoryLotsCost(costTransactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items Average cost from specific date from inventory transactions.
|
||||
* @async
|
||||
* @param {Date} closingDate
|
||||
* @return {number}
|
||||
*/
|
||||
public async getOpeningAverageCost(closingDate: Date, itemId: number) {
|
||||
const { InventoryCostLotTracker } = this.tenantModels;
|
||||
|
||||
const commonBuilder = (builder: any) => {
|
||||
if (closingDate) {
|
||||
builder.where('date', '<', closingDate);
|
||||
}
|
||||
builder.where('item_id', itemId);
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
builder.sum('cost as cost');
|
||||
builder.first();
|
||||
};
|
||||
// Calculates the total inventory total quantity and rate `IN` transactions.
|
||||
const inInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'IN');
|
||||
|
||||
// Calculates the total inventory total quantity and rate `OUT` transactions.
|
||||
const outInvSumationOper: Promise<any> = InventoryCostLotTracker.query()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
const [inInvSumation, outInvSumation] = await Promise.all([
|
||||
inInvSumationOper,
|
||||
outInvSumationOper,
|
||||
]);
|
||||
return this.computeItemAverageCost(
|
||||
inInvSumation?.cost || 0,
|
||||
inInvSumation?.quantity || 0,
|
||||
outInvSumation?.cost || 0,
|
||||
outInvSumation?.quantity || 0,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the item average cost.
|
||||
* @static
|
||||
* @param {number} quantityIn
|
||||
* @param {number} rateIn
|
||||
* @param {number} quantityOut
|
||||
* @param {number} rateOut
|
||||
*/
|
||||
public computeItemAverageCost(
|
||||
totalCostIn: number,
|
||||
totalQuantityIn: number,
|
||||
|
||||
totalCostOut: number,
|
||||
totalQuantityOut: number,
|
||||
) {
|
||||
const openingCost = totalCostIn - totalCostOut;
|
||||
const openingQuantity = totalQuantityIn - totalQuantityOut;
|
||||
|
||||
const averageCost = openingQuantity ? openingCost / openingQuantity : 0;
|
||||
|
||||
return { averageCost, openingCost, openingQuantity };
|
||||
}
|
||||
|
||||
private getCost(rate: number, quantity: number) {
|
||||
return quantity ? rate * quantity : rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the journal entries from specific item inventory transactions.
|
||||
* @param {IInventoryTransaction[]} invTransactions
|
||||
* @param {number} openingAverageCost
|
||||
* @param {string} referenceType
|
||||
* @param {number} referenceId
|
||||
* @param {JournalCommand} journalCommands
|
||||
*/
|
||||
public trackingCostTransactions(
|
||||
invTransactions: InventoryTransaction[],
|
||||
openingQuantity: number = 0,
|
||||
openingCost: number = 0,
|
||||
) {
|
||||
const costTransactions: any[] = [];
|
||||
|
||||
// Cumulative item quantity and cost. This will decrement after
|
||||
// each out transactions depends on its quantity and cost.
|
||||
let accQuantity: number = openingQuantity;
|
||||
let accCost: number = openingCost;
|
||||
|
||||
invTransactions.forEach((invTransaction: InventoryTransaction) => {
|
||||
const commonEntry = {
|
||||
invTransId: invTransaction.id,
|
||||
...pick(invTransaction, [
|
||||
'date',
|
||||
'direction',
|
||||
'itemId',
|
||||
'quantity',
|
||||
'rate',
|
||||
'entryId',
|
||||
'transactionId',
|
||||
'transactionType',
|
||||
'createdAt',
|
||||
'costAccountId',
|
||||
'branchId',
|
||||
'warehouseId',
|
||||
]),
|
||||
inventoryTransactionId: invTransaction.id,
|
||||
};
|
||||
switch (invTransaction.direction) {
|
||||
case 'IN':
|
||||
const inCost = this.getCost(
|
||||
invTransaction.rate,
|
||||
invTransaction.quantity,
|
||||
);
|
||||
// Increases the quantity and cost in `IN` inventory transactions.
|
||||
accQuantity += invTransaction.quantity;
|
||||
accCost += inCost;
|
||||
|
||||
costTransactions.push({
|
||||
...commonEntry,
|
||||
cost: inCost,
|
||||
});
|
||||
break;
|
||||
case 'OUT':
|
||||
// Average cost = Total cost / Total quantity
|
||||
const averageCost = accQuantity ? accCost / accQuantity : 0;
|
||||
|
||||
const quantity =
|
||||
accQuantity > 0
|
||||
? Math.min(invTransaction.quantity, accQuantity)
|
||||
: invTransaction.quantity;
|
||||
|
||||
// Cost = the transaction quantity * Average cost.
|
||||
const cost = this.getCost(averageCost, quantity);
|
||||
|
||||
// Revenue = transaction quanity * rate.
|
||||
// const revenue = quantity * invTransaction.rate;
|
||||
costTransactions.push({
|
||||
...commonEntry,
|
||||
quantity,
|
||||
cost,
|
||||
});
|
||||
accQuantity = Math.max(accQuantity - quantity, 0);
|
||||
accCost = Math.max(accCost - cost, 0);
|
||||
|
||||
if (invTransaction.quantity > quantity) {
|
||||
const remainingQuantity = Math.max(
|
||||
invTransaction.quantity - quantity,
|
||||
0,
|
||||
);
|
||||
const remainingIncome = remainingQuantity * invTransaction.rate;
|
||||
|
||||
costTransactions.push({
|
||||
...commonEntry,
|
||||
quantity: remainingQuantity,
|
||||
cost: 0,
|
||||
});
|
||||
accQuantity = Math.max(accQuantity - remainingQuantity, 0);
|
||||
accCost = Math.max(accCost - remainingIncome, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return costTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory lots `OUT` transactions.
|
||||
* @param {Date} openingDate - Opening date.
|
||||
* @param {number} itemId - Item id.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async revertTheInventoryOutLotTrans(): Promise<void> {
|
||||
const { InventoryCostLotTracker } = this.tenantModels;
|
||||
|
||||
await InventoryCostLotTracker.query(this.trx)
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'DESC')
|
||||
.where('item_id', this.itemId)
|
||||
.delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { InventoryCostGLBeforeWriteSubscriber } from './subscribers/InventoryCostGLBeforeWriteSubscriber';
|
||||
import { InventoryCostGLStorage } from './InventoryCostGLStorage.service';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { InventoryCostLotTracker } from './models/InventoryCostLotTracker';
|
||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(InventoryCostLotTracker),
|
||||
RegisterTenancyModel(InventoryTransaction),
|
||||
];
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
...models,
|
||||
InventoryCostGLBeforeWriteSubscriber,
|
||||
InventoryCostGLStorage,
|
||||
],
|
||||
exports: [...models],
|
||||
})
|
||||
export class InventoryCostModule {}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { IInventoryItemCostMeta } from '@/interfaces';
|
||||
import { Service, Inject } from 'typedi';
|
||||
import { InventoryItemCostService } from './InventoryCosts.service';
|
||||
|
||||
@Service()
|
||||
export class InventoryCostApplication {
|
||||
@Inject()
|
||||
inventoryCost: InventoryItemCostService;
|
||||
|
||||
/**
|
||||
* Retrieves the items inventory valuation list.
|
||||
* @param {number} tenantId
|
||||
* @param {number[]} itemsId
|
||||
* @param {Date} date
|
||||
* @returns {Promise<IInventoryItemCostMeta[]>}
|
||||
*/
|
||||
public getItemsInventoryValuationList = async (
|
||||
itemsId: number[],
|
||||
date: Date
|
||||
): Promise<IInventoryItemCostMeta[]> => {
|
||||
const itemsMap = await this.inventoryCost.getItemsInventoryValuation(
|
||||
tenantId,
|
||||
itemsId,
|
||||
date
|
||||
);
|
||||
return [...itemsMap.values()];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { LedgerStorageService } from '../Ledger/LedgerStorage.service';
|
||||
import { Ledger } from '../Ledger/Ledger';
|
||||
import { AccountTransaction } from '../Accounts/models/AccountTransaction.model';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryCostGLStorage {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
|
||||
@Inject(AccountTransaction.name)
|
||||
private readonly accountTransactionModel: typeof AccountTransaction,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reverts the inventory cost GL entries from the given starting date.
|
||||
* @param {Date} startingDate - Starting date.
|
||||
* @param {Knex.Transaction} trx - Transaction.
|
||||
*/
|
||||
public async revertInventoryCostGLEntries(
|
||||
startingDate: Date,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
// Retrieve transactions from specific date range and costable transactions only.
|
||||
const transactions = await this.accountTransactionModel
|
||||
.query()
|
||||
.where('costable', true)
|
||||
.modify('filterDateRange', startingDate)
|
||||
.withGraphFetched('account');
|
||||
|
||||
// Transform transaction to ledger entries and reverse them.
|
||||
const reversedLedger = Ledger.fromTransactions(transactions).reverse();
|
||||
|
||||
// Deletes and reverts balances of the given ledger.
|
||||
await this.ledgerStorage.delete(reversedLedger, trx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
import { pick, chain } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { IInventoryLotCost, IInventoryTransaction } from "interfaces";
|
||||
import InventoryCostMethod from '@/services/Inventory/InventoryCostMethod';
|
||||
|
||||
type TCostMethod = 'FIFO' | 'LIFO';
|
||||
|
||||
export class InventoryCostLotTracker extends InventoryCostMethod {
|
||||
startingDate: Date;
|
||||
itemId: number;
|
||||
costMethod: TCostMethod;
|
||||
itemsById: Map<number, any>;
|
||||
inventoryINTrans: any;
|
||||
inventoryByItem: any;
|
||||
costLotsTransactions: IInventoryLotCost[];
|
||||
inTransactions: any[];
|
||||
outTransactions: IInventoryTransaction[];
|
||||
revertJEntriesTransactions: IInventoryTransaction[];
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {Date} startingDate -
|
||||
* @param {number} itemId -
|
||||
* @param {string} costMethod -
|
||||
*/
|
||||
constructor(
|
||||
tenantId: number,
|
||||
startingDate: Date,
|
||||
itemId: number,
|
||||
costMethod: TCostMethod = 'FIFO'
|
||||
) {
|
||||
super(tenantId, startingDate, itemId);
|
||||
|
||||
this.startingDate = startingDate;
|
||||
this.itemId = itemId;
|
||||
this.costMethod = costMethod;
|
||||
|
||||
// Collect cost lots transactions to insert them to the storage in bulk.
|
||||
this.costLotsTransactions= [];
|
||||
// Collect inventory transactions by item id.
|
||||
this.inventoryByItem = {};
|
||||
// Collection `IN` inventory tranaction by transaction id.
|
||||
this.inventoryINTrans = {};
|
||||
// Collects `IN` transactions.
|
||||
this.inTransactions = [];
|
||||
// Collects `OUT` transactions.
|
||||
this.outTransactions = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes items costs from the given date using FIFO or LIFO cost method.
|
||||
* --------
|
||||
* - Revert the inventory lots after the given date.
|
||||
* - Remove all the journal entries from the inventory transactions
|
||||
* after the given date.
|
||||
* - Re-tracking the inventory lots from inventory transactions.
|
||||
* - Re-write the journal entries from the given inventory transactions.
|
||||
* @async
|
||||
* @return {void}
|
||||
*/
|
||||
public async computeItemCost(): Promise<any> {
|
||||
await this.revertInventoryLots(this.startingDate);
|
||||
await this.fetchInvINTransactions();
|
||||
await this.fetchInvOUTTransactions();
|
||||
await this.fetchRevertInvJReferenceIds();
|
||||
await this.fetchItemsMapped();
|
||||
|
||||
this.trackingInventoryINLots(this.inTransactions);
|
||||
this.trackingInventoryOUTLots(this.outTransactions);
|
||||
|
||||
// Re-tracking the inventory `IN` and `OUT` lots costs.
|
||||
const storedTrackedInvLotsOper = this.storeInventoryLotsCost(
|
||||
this.costLotsTransactions,
|
||||
);
|
||||
return Promise.all([
|
||||
storedTrackedInvLotsOper,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetched inventory transactions that has date from the starting date and
|
||||
* fetches available IN LOTs transactions that has remaining bigger than zero.
|
||||
* @private
|
||||
*/
|
||||
private async fetchInvINTransactions() {
|
||||
const { InventoryTransaction, InventoryLotCostTracker } = this.tenantModels;
|
||||
|
||||
const commonBuilder = (builder: any) => {
|
||||
builder.orderBy('date', (this.costMethod === 'LIFO') ? 'DESC': 'ASC');
|
||||
builder.where('item_id', this.itemId);
|
||||
};
|
||||
const afterInvTransactions: IInventoryTransaction[] =
|
||||
await InventoryTransaction.query()
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderByRaw("FIELD(direction, 'IN', 'OUT')")
|
||||
.onBuild(commonBuilder)
|
||||
.orderBy('lot_number', (this.costMethod === 'LIFO') ? 'DESC' : 'ASC')
|
||||
.withGraphFetched('item');
|
||||
|
||||
const availableINLots: IInventoryLotCost[] =
|
||||
await InventoryLotCostTracker.query()
|
||||
.modify('filterDateRange', null, this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.where('direction', 'IN')
|
||||
.orderBy('lot_number', 'ASC')
|
||||
.onBuild(commonBuilder)
|
||||
.whereNot('remaining', 0);
|
||||
|
||||
this.inTransactions = [
|
||||
...availableINLots.map((trans) => ({ lotTransId: trans.id, ...trans })),
|
||||
...afterInvTransactions.map((trans) => ({ invTransId: trans.id, ...trans })),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches inventory OUT transactions that has date from the starting date.
|
||||
* @private
|
||||
*/
|
||||
private async fetchInvOUTTransactions() {
|
||||
const { InventoryTransaction } = this.tenantModels;
|
||||
|
||||
const afterOUTTransactions: IInventoryTransaction[] =
|
||||
await InventoryTransaction.query()
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.orderBy('lot_number', 'ASC')
|
||||
.where('item_id', this.itemId)
|
||||
.where('direction', 'OUT')
|
||||
.withGraphFetched('item');
|
||||
|
||||
this.outTransactions = [ ...afterOUTTransactions ];
|
||||
}
|
||||
|
||||
private async fetchItemsMapped() {
|
||||
const itemsIds = chain(this.inTransactions).map((e) => e.itemId).uniq().value();
|
||||
const { Item } = this.tenantModels;
|
||||
const storedItems = await Item.query()
|
||||
.where('type', 'inventory')
|
||||
.whereIn('id', itemsIds);
|
||||
|
||||
this.itemsById = new Map(storedItems.map((item: any) => [item.id, item]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the inventory transactions that should revert its journal entries.
|
||||
* @private
|
||||
*/
|
||||
private async fetchRevertInvJReferenceIds() {
|
||||
const { InventoryTransaction } = this.tenantModels;
|
||||
const revertJEntriesTransactions: IInventoryTransaction[] =
|
||||
await InventoryTransaction.query()
|
||||
.select(['transactionId', 'transactionType'])
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.where('direction', 'OUT')
|
||||
.where('item_id', this.itemId);
|
||||
|
||||
this.revertJEntriesTransactions = revertJEntriesTransactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert the inventory lots to the given date by removing the inventory lots
|
||||
* transactions after the given date and increment the remaining that
|
||||
* associate to lot number.
|
||||
* @async
|
||||
* @return {Promise}
|
||||
*/
|
||||
public async revertInventoryLots(startingDate: Date) {
|
||||
const { InventoryLotCostTracker } = this.tenantModels;
|
||||
const asyncOpers: any[] = [];
|
||||
const inventoryLotsTrans = await InventoryLotCostTracker.query()
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.orderBy('date', 'DESC')
|
||||
.where('item_id', this.itemId)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
const deleteInvLotsTrans = InventoryLotCostTracker.query()
|
||||
.modify('filterDateRange', this.startingDate)
|
||||
.where('item_id', this.itemId)
|
||||
.delete();
|
||||
|
||||
inventoryLotsTrans.forEach((inventoryLot: IInventoryLotCost) => {
|
||||
if (!inventoryLot.lotNumber) { return; }
|
||||
|
||||
const incrementOper = InventoryLotCostTracker.query()
|
||||
.where('lot_number', inventoryLot.lotNumber)
|
||||
.where('direction', 'IN')
|
||||
.increment('remaining', inventoryLot.quantity);
|
||||
|
||||
asyncOpers.push(incrementOper);
|
||||
});
|
||||
return Promise.all([deleteInvLotsTrans, ...asyncOpers]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking inventory `IN` lots transactions.
|
||||
* @public
|
||||
* @param {IInventoryTransaction[]} inventoryTransactions -
|
||||
* @return {void}
|
||||
*/
|
||||
public trackingInventoryINLots(
|
||||
inventoryTransactions: IInventoryTransaction[],
|
||||
) {
|
||||
inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
||||
const { itemId, id } = transaction;
|
||||
(this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = []));
|
||||
|
||||
const commonLotTransaction: IInventoryLotCost = {
|
||||
...pick(transaction, [
|
||||
'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId',
|
||||
'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
||||
]),
|
||||
};
|
||||
this.inventoryByItem[itemId].push(id);
|
||||
this.inventoryINTrans[id] = {
|
||||
...commonLotTransaction,
|
||||
decrement: 0,
|
||||
remaining: commonLotTransaction.remaining || commonLotTransaction.quantity,
|
||||
};
|
||||
this.costLotsTransactions.push(this.inventoryINTrans[id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracking inventory `OUT` lots transactions.
|
||||
* @public
|
||||
* @param {IInventoryTransaction[]} inventoryTransactions -
|
||||
* @return {void}
|
||||
*/
|
||||
public trackingInventoryOUTLots(
|
||||
inventoryTransactions: IInventoryTransaction[],
|
||||
) {
|
||||
inventoryTransactions.forEach((transaction: IInventoryTransaction) => {
|
||||
const { itemId, id } = transaction;
|
||||
(this.inventoryByItem[itemId] || (this.inventoryByItem[itemId] = []));
|
||||
|
||||
const commonLotTransaction: IInventoryLotCost = {
|
||||
...pick(transaction, [
|
||||
'date', 'rate', 'itemId', 'quantity', 'invTransId', 'lotTransId', 'entryId',
|
||||
'direction', 'transactionType', 'transactionId', 'lotNumber', 'remaining'
|
||||
]),
|
||||
};
|
||||
let invRemaining = transaction.quantity;
|
||||
const idsShouldDel: number[] = [];
|
||||
|
||||
this.inventoryByItem?.[itemId]?.some((_invTransactionId: number) => {
|
||||
const _invINTransaction = this.inventoryINTrans[_invTransactionId];
|
||||
|
||||
// Can't continue if the IN transaction remaining equals zero.
|
||||
if (invRemaining <= 0) { return true; }
|
||||
|
||||
// Can't continue if the IN transaction date is after the current transaction date.
|
||||
if (moment(_invINTransaction.date).isAfter(transaction.date)) {
|
||||
return true;
|
||||
}
|
||||
// Detarmines the 'OUT' lot tranasctions whether bigger than 'IN' remaining transaction.
|
||||
const biggerThanRemaining = (_invINTransaction.remaining - transaction.quantity) > 0;
|
||||
const decrement = (biggerThanRemaining) ? transaction.quantity : _invINTransaction.remaining;
|
||||
const maxDecrement = Math.min(decrement, invRemaining);
|
||||
const cost = maxDecrement * _invINTransaction.rate;
|
||||
|
||||
_invINTransaction.decrement += maxDecrement;
|
||||
_invINTransaction.remaining = Math.max(
|
||||
_invINTransaction.remaining - maxDecrement,
|
||||
0,
|
||||
);
|
||||
invRemaining = Math.max(invRemaining - maxDecrement, 0);
|
||||
|
||||
this.costLotsTransactions.push({
|
||||
...commonLotTransaction,
|
||||
cost,
|
||||
quantity: maxDecrement,
|
||||
lotNumber: _invINTransaction.lotNumber,
|
||||
});
|
||||
// Pop the 'IN' lots that has zero remaining.
|
||||
if (_invINTransaction.remaining === 0) {
|
||||
idsShouldDel.push(_invTransactionId);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (invRemaining > 0) {
|
||||
this.costLotsTransactions.push({
|
||||
...commonLotTransaction,
|
||||
quantity: invRemaining,
|
||||
});
|
||||
}
|
||||
this.removeInventoryItems(itemId, idsShouldDel);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inventory transactions for specific item id.
|
||||
* @private
|
||||
* @param {number} itemId
|
||||
* @param {number[]} idsShouldDel
|
||||
* @return {void}
|
||||
*/
|
||||
private removeInventoryItems(itemId: number, idsShouldDel: number[]) {
|
||||
// Remove the IN transactions that has zero remaining amount.
|
||||
this.inventoryByItem[itemId] = this.inventoryByItem?.[itemId]
|
||||
?.filter((transId: number) => idsShouldDel.indexOf(transId) === -1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { omit } from 'lodash';
|
||||
import { Container } from 'typedi';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { IInventoryLotCost } from '@/interfaces';
|
||||
|
||||
export default class InventoryCostMethod {
|
||||
tenancy: TenancyService;
|
||||
tenantModels: any;
|
||||
|
||||
/**
|
||||
* Constructor method.
|
||||
* @param {number} tenantId - The given tenant id.
|
||||
*/
|
||||
constructor(tenantId: number, startingDate: Date, itemId: number) {
|
||||
const tenancyService = Container.get(TenancyService);
|
||||
|
||||
this.tenantModels = tenancyService.models(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the inventory lots costs transactions in bulk.
|
||||
* @param {IInventoryLotCost[]} costLotsTransactions
|
||||
* @return {Promise[]}
|
||||
*/
|
||||
public storeInventoryLotsCost(
|
||||
costLotsTransactions: IInventoryLotCost[]
|
||||
): Promise<object> {
|
||||
const { InventoryCostLotTracker } = this.tenantModels;
|
||||
const opers: any = [];
|
||||
|
||||
costLotsTransactions.forEach((transaction: any) => {
|
||||
if (transaction.lotTransId && transaction.decrement) {
|
||||
const decrementOper = InventoryCostLotTracker.query(this.trx)
|
||||
.where('id', transaction.lotTransId)
|
||||
.decrement('remaining', transaction.decrement);
|
||||
opers.push(decrementOper);
|
||||
} else if (!transaction.lotTransId) {
|
||||
const operation = InventoryCostLotTracker.query(this.trx).insert({
|
||||
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
|
||||
});
|
||||
opers.push(operation);
|
||||
}
|
||||
});
|
||||
return Promise.all(opers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { keyBy, get } from 'lodash';
|
||||
import { Knex } from 'knex';
|
||||
import * as R from 'ramda';
|
||||
import { IInventoryItemCostMeta } from './types/InventoryCost.types';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { InventoryTransaction } from './models/InventoryTransaction';
|
||||
import { InventoryCostLotTracker } from './models/InventoryCostLotTracker';
|
||||
import { Item } from '../Items/models/Item';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryItemCostService {
|
||||
constructor(
|
||||
@Inject(InventoryTransaction.name)
|
||||
private readonly inventoryTransactionModel: typeof InventoryTransaction,
|
||||
|
||||
@Inject(InventoryCostLotTracker.name)
|
||||
private readonly inventoryCostLotTrackerModel: typeof InventoryCostLotTracker,
|
||||
|
||||
@Inject(Item.name)
|
||||
private readonly itemModel: typeof Item,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Common query of items inventory valuation.
|
||||
* @param {number[]} itemsIds -
|
||||
* @param {Date} date -
|
||||
* @param {Knex.QueryBuilder} builder -
|
||||
*/
|
||||
private itemsInventoryValuationCommonQuery = R.curry(
|
||||
(itemsIds: number[], date: Date, builder: Knex.QueryBuilder) => {
|
||||
if (date) {
|
||||
builder.where('date', '<', date);
|
||||
}
|
||||
builder.whereIn('item_id', itemsIds);
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
builder.sum('cost as cost');
|
||||
|
||||
builder.groupBy('item_id');
|
||||
builder.select(['item_id']);
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {} INValuationMap -
|
||||
* @param {} OUTValuationMap -
|
||||
* @param {number} itemId
|
||||
*/
|
||||
private getItemInventoryMeta = R.curry(
|
||||
(
|
||||
INValuationMap,
|
||||
OUTValuationMap,
|
||||
itemId: number
|
||||
): IInventoryItemCostMeta => {
|
||||
const INCost = get(INValuationMap, `[${itemId}].cost`, 0);
|
||||
const INQuantity = get(INValuationMap, `[${itemId}].quantity`, 0);
|
||||
|
||||
const OUTCost = get(OUTValuationMap, `[${itemId}].cost`, 0);
|
||||
const OUTQuantity = get(OUTValuationMap, `[${itemId}].quantity`, 0);
|
||||
|
||||
const valuation = INCost - OUTCost;
|
||||
const quantity = INQuantity - OUTQuantity;
|
||||
const average = quantity ? valuation / quantity : 0;
|
||||
|
||||
return { itemId, valuation, quantity, average };
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemsId
|
||||
* @param {Date} date
|
||||
* @returns
|
||||
*/
|
||||
private getItemsInventoryINAndOutAggregated = (
|
||||
itemsId: number[],
|
||||
date: Date
|
||||
): Promise<any> => {
|
||||
|
||||
const commonBuilder = this.itemsInventoryValuationCommonQuery(
|
||||
itemsId,
|
||||
date
|
||||
);
|
||||
const INValuationOper = this.inventoryCostLotTrackerModel.query()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'IN');
|
||||
|
||||
const OUTValuationOper = this.inventoryCostLotTrackerModel.query()
|
||||
.onBuild(commonBuilder)
|
||||
.where('direction', 'OUT');
|
||||
|
||||
return Promise.all([OUTValuationOper, INValuationOper]);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId -
|
||||
* @param {number[]} itemsIds -
|
||||
* @param {Date} date -
|
||||
*/
|
||||
private getItemsInventoryInOutMap = async (
|
||||
itemsId: number[],
|
||||
date: Date
|
||||
) => {
|
||||
const [OUTValuation, INValuation] =
|
||||
await this.getItemsInventoryINAndOutAggregated(itemsId, date);
|
||||
|
||||
const OUTValuationMap = keyBy(OUTValuation, 'itemId');
|
||||
const INValuationMap = keyBy(INValuation, 'itemId');
|
||||
|
||||
return [OUTValuationMap, INValuationMap];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {number} itemId
|
||||
* @param {Date} date
|
||||
* @returns {Promise<Map<number, IInventoryItemCostMeta>>}
|
||||
*/
|
||||
public getItemsInventoryValuation = async (
|
||||
itemsId: number[],
|
||||
date: Date
|
||||
): Promise<Map<number, IInventoryItemCostMeta>> => {
|
||||
// Retrieves the inventory items.
|
||||
const items = await this.itemModel.query()
|
||||
.whereIn('id', itemsId)
|
||||
.where('type', 'inventory');
|
||||
|
||||
// Retrieves the inventory items ids.
|
||||
const inventoryItemsIds: number[] = items.map((item) => item.id);
|
||||
|
||||
// Retreives the items inventory IN/OUT map.
|
||||
const [OUTValuationMap, INValuationMap] =
|
||||
await this.getItemsInventoryInOutMap(itemsId, date);
|
||||
|
||||
const getItemValuation = this.getItemInventoryMeta(
|
||||
INValuationMap,
|
||||
OUTValuationMap
|
||||
);
|
||||
const itemsValuations = inventoryItemsIds.map(getItemValuation);
|
||||
const itemsValuationsMap = new Map(
|
||||
itemsValuations.map((i) => [i.itemId, i])
|
||||
);
|
||||
return itemsValuationsMap;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { toSafeInteger } from 'lodash';
|
||||
import { IInventoryTransaction, IItemsQuantityChanges } from '@/interfaces';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { Item } from '../Items/models/Item';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* Syncs the inventory transactions with inventory items quantity.
|
||||
*/
|
||||
@Injectable()
|
||||
export class InventoryItemsQuantitySyncService {
|
||||
constructor(
|
||||
@Inject(Item.name)
|
||||
private readonly itemModel: typeof Item,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Reverse the given inventory transactions.
|
||||
* @param {IInventoryTransaction[]} inventroyTransactions
|
||||
* @return {IInventoryTransaction[]}
|
||||
*/
|
||||
public reverseInventoryTransactions(
|
||||
inventroyTransactions: IInventoryTransaction[],
|
||||
): IInventoryTransaction[] {
|
||||
return inventroyTransactions.map((transaction) => ({
|
||||
...transaction,
|
||||
direction: transaction.direction === 'OUT' ? 'IN' : 'OUT',
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the inventory transactions.
|
||||
* @param {IInventoryTransaction[]} inventroyTransactions -
|
||||
* @return {IItemsQuantityChanges[]}
|
||||
*/
|
||||
public getReverseItemsQuantityChanges(
|
||||
inventroyTransactions: IInventoryTransaction[],
|
||||
): IItemsQuantityChanges[] {
|
||||
const reversedTransactions = this.reverseInventoryTransactions(
|
||||
inventroyTransactions,
|
||||
);
|
||||
return this.getItemsQuantityChanges(reversedTransactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the items quantity changes from the given inventory transactions.
|
||||
* @param {IInventoryTransaction[]} inventroyTransactions - Inventory transactions.
|
||||
* @return {IItemsQuantityChanges[]}
|
||||
*/
|
||||
public getItemsQuantityChanges(
|
||||
inventroyTransactions: IInventoryTransaction[],
|
||||
): IItemsQuantityChanges[] {
|
||||
const balanceMap: { [itemId: number]: number } = {};
|
||||
|
||||
inventroyTransactions.forEach(
|
||||
(inventoryTransaction: IInventoryTransaction) => {
|
||||
const { itemId, direction, quantity } = inventoryTransaction;
|
||||
|
||||
if (!balanceMap[itemId]) {
|
||||
balanceMap[itemId] = 0;
|
||||
}
|
||||
balanceMap[itemId] += direction === 'IN' ? quantity : 0;
|
||||
balanceMap[itemId] -= direction === 'OUT' ? quantity : 0;
|
||||
},
|
||||
);
|
||||
return Object.entries(balanceMap).map(([itemId, balanceChange]) => ({
|
||||
itemId: toSafeInteger(itemId),
|
||||
balanceChange,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the items quantity changes.
|
||||
* @param {IItemsQuantityChanges[]} itemsQuantity - Items quantity changes.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async changeItemsQuantity(
|
||||
itemsQuantity: IItemsQuantityChanges[],
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
const opers = [];
|
||||
|
||||
itemsQuantity.forEach((itemQuantity: IItemsQuantityChanges) => {
|
||||
const changeQuantityOper = this.itemModel
|
||||
.query(trx)
|
||||
.where({ id: itemQuantity.itemId, type: 'inventory' })
|
||||
.modify('quantityOnHand', itemQuantity.balanceChange);
|
||||
|
||||
opers.push(changeQuantityOper);
|
||||
});
|
||||
await Promise.all(opers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import { Model } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
|
||||
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
|
||||
import { Item } from '@/modules/Items/models/Item';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
export class InventoryCostLotTracker extends BaseModel {
|
||||
date: Date;
|
||||
direction: string;
|
||||
itemId: number;
|
||||
quantity: number;
|
||||
rate: number;
|
||||
remaining: number;
|
||||
cost: number;
|
||||
transactionType: string;
|
||||
transactionId: number;
|
||||
costAccountId: number;
|
||||
entryId: number;
|
||||
createdAt: Date;
|
||||
|
||||
exchangeRate: number;
|
||||
currencyCode: string;
|
||||
|
||||
item?: Item;
|
||||
invoice?: SaleInvoice;
|
||||
receipt?: SaleReceipt;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_cost_lot_tracker';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
static get timestamps() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
groupedEntriesCost(query) {
|
||||
query.select(['date', 'item_id', 'transaction_id', 'transaction_type']);
|
||||
query.sum('cost as cost');
|
||||
|
||||
query.groupBy('transaction_id');
|
||||
query.groupBy('transaction_type');
|
||||
query.groupBy('date');
|
||||
query.groupBy('item_id');
|
||||
},
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given branches.
|
||||
*/
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branchId', formattedBranchesIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given warehosues.
|
||||
*/
|
||||
filterByWarehouses(query, branchesIds) {
|
||||
const formattedWarehousesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('warehouseId', formattedWarehousesIds);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const SaleInvoice = require('models/SaleInvoice');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const SaleReceipt = require('models/SaleReceipt');
|
||||
|
||||
return {
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
invoice: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleInvoice.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.transactionId',
|
||||
to: 'sales_invoices.id',
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
receipt: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: SaleReceipt.default,
|
||||
join: {
|
||||
from: 'inventory_cost_lot_tracker.transactionId',
|
||||
to: 'sales_receipts.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import { Model, raw } from 'objection';
|
||||
import { castArray } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { getTransactionTypeLabel } from '@/modules/BankingTransactions/utils';
|
||||
import { TInventoryTransactionDirection } from '../types/InventoryCost.types';
|
||||
|
||||
export class InventoryTransaction extends BaseModel {
|
||||
date: Date | string;
|
||||
direction: TInventoryTransactionDirection;
|
||||
itemId: number;
|
||||
quantity: number | null;
|
||||
rate: number;
|
||||
transactionType: string;
|
||||
transactionId: number;
|
||||
costAccountId?: number;
|
||||
entryId: number;
|
||||
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
|
||||
warehouseId?: number;
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
static get tableName() {
|
||||
return 'inventory_transactions';
|
||||
}
|
||||
|
||||
/**
|
||||
* Model timestamps.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve formatted reference type.
|
||||
* @return {string}
|
||||
*/
|
||||
get transcationTypeFormatted() {
|
||||
return getTransactionTypeLabel(this.transactionType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model modifiers.
|
||||
*/
|
||||
static get modifiers() {
|
||||
return {
|
||||
filterDateRange(query, startDate, endDate, type = 'day') {
|
||||
const dateFormat = 'YYYY-MM-DD';
|
||||
const fromDate = moment(startDate).startOf(type).format(dateFormat);
|
||||
const toDate = moment(endDate).endOf(type).format(dateFormat);
|
||||
|
||||
if (startDate) {
|
||||
query.where('date', '>=', fromDate);
|
||||
}
|
||||
if (endDate) {
|
||||
query.where('date', '<=', toDate);
|
||||
}
|
||||
},
|
||||
|
||||
itemsTotals(builder) {
|
||||
builder.select('itemId');
|
||||
builder.sum('rate as rate');
|
||||
builder.sum('quantity as quantity');
|
||||
builder.select(raw('SUM(`QUANTITY` * `RATE`) as COST'));
|
||||
builder.groupBy('itemId');
|
||||
},
|
||||
|
||||
INDirection(builder) {
|
||||
builder.where('direction', 'IN');
|
||||
},
|
||||
|
||||
OUTDirection(builder) {
|
||||
builder.where('direction', 'OUT');
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given branches.
|
||||
*/
|
||||
filterByBranches(query, branchesIds) {
|
||||
const formattedBranchesIds = castArray(branchesIds);
|
||||
|
||||
query.whereIn('branch_id', formattedBranchesIds);
|
||||
},
|
||||
|
||||
/**
|
||||
* Filters transactions by the given warehosues.
|
||||
*/
|
||||
filterByWarehouses(query, warehousesIds) {
|
||||
const formattedWarehousesIds = castArray(warehousesIds);
|
||||
|
||||
query.whereIn('warehouse_id', formattedWarehousesIds);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Relationship mapping.
|
||||
*/
|
||||
static get relationMappings() {
|
||||
const Item = require('models/Item');
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const InventoryTransactionMeta = require('models/InventoryTransactionMeta');
|
||||
const InventoryCostLots = require('models/InventoryCostLotTracker');
|
||||
|
||||
return {
|
||||
// Transaction meta.
|
||||
meta: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: InventoryTransactionMeta.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.id',
|
||||
to: 'inventory_transaction_meta.inventoryTransactionId',
|
||||
},
|
||||
},
|
||||
// Item cost aggregated.
|
||||
itemCostAggregated: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: InventoryCostLots.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.itemId',
|
||||
to: 'inventory_cost_lot_tracker.itemId',
|
||||
},
|
||||
filter(query) {
|
||||
query.select('itemId');
|
||||
query.sum('cost as cost');
|
||||
query.sum('quantity as quantity');
|
||||
query.groupBy('itemId');
|
||||
},
|
||||
},
|
||||
costLotAggregated: {
|
||||
relation: Model.HasOneRelation,
|
||||
modelClass: InventoryCostLots.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.id',
|
||||
to: 'inventory_cost_lot_tracker.inventoryTransactionId',
|
||||
},
|
||||
filter(query) {
|
||||
query.sum('cost as cost');
|
||||
query.sum('quantity as quantity');
|
||||
query.groupBy('inventoryTransactionId');
|
||||
},
|
||||
},
|
||||
item: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: Item.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.itemId',
|
||||
to: 'items.id',
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry.default,
|
||||
join: {
|
||||
from: 'inventory_transactions.entryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { IInventoryCostLotsGLEntriesWriteEvent } from '../types/InventoryCost.types';
|
||||
import { InventoryCostGLStorage } from '../InventoryCostGLStorage.service';
|
||||
|
||||
@Injectable()
|
||||
export class InventoryCostGLBeforeWriteSubscriber {
|
||||
constructor(
|
||||
private readonly inventoryCostGLStorage: InventoryCostGLStorage,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes the receipts cost GL entries once the inventory cost lots be written.
|
||||
* @param {IInventoryCostLotsGLEntriesWriteEvent}
|
||||
*/
|
||||
@OnEvent(events.inventory.onCostLotsGLEntriesBeforeWrite)
|
||||
public async revertsInventoryCostGLEntries({
|
||||
trx,
|
||||
startingDate,
|
||||
}: IInventoryCostLotsGLEntriesWriteEvent) {
|
||||
await this.inventoryCostGLStorage.revertInventoryCostGLEntries(
|
||||
startingDate,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Knex } from "knex";
|
||||
import { InventoryTransaction } from "../models/InventoryTransaction";
|
||||
|
||||
|
||||
export interface IInventoryItemCostMeta {
|
||||
itemId: number;
|
||||
valuation: number;
|
||||
quantity: number;
|
||||
average: number;
|
||||
}
|
||||
|
||||
export interface IInventoryCostLotsGLEntriesWriteEvent {
|
||||
startingDate: Date,
|
||||
trx: Knex.Transaction
|
||||
}
|
||||
|
||||
export type TInventoryTransactionDirection = 'IN' | 'OUT';
|
||||
|
||||
export type TCostMethod = 'FIFO' | 'LIFO' | 'AVG';
|
||||
|
||||
export interface IInventoryTransactionMeta {
|
||||
id?: number;
|
||||
transactionNumber: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface IInventoryCostLotAggregated {
|
||||
cost: number;
|
||||
quantity: number;
|
||||
}
|
||||
|
||||
export interface IItemsQuantityChanges {
|
||||
itemId: number;
|
||||
balanceChange: number;
|
||||
}
|
||||
|
||||
export interface IInventoryTransactionsCreatedPayload {
|
||||
inventoryTransactions: InventoryTransaction[];
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IInventoryTransactionsDeletedPayload {
|
||||
oldInventoryTransactions: InventoryTransaction[];
|
||||
transactionId: number;
|
||||
transactionType: string;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IInventoryItemCostScheduledPayload {
|
||||
startingDate: Date | string;
|
||||
itemId: number;
|
||||
}
|
||||
|
||||
export interface IComputeItemCostJobStartedPayload {
|
||||
startingDate: Date | string;
|
||||
itemId: number;
|
||||
}
|
||||
export interface IComputeItemCostJobCompletedPayload {
|
||||
startingDate: Date | string;
|
||||
itemId: number;
|
||||
}
|
||||
15
packages/server-nest/src/modules/InventoryCost/utils.ts
Normal file
15
packages/server-nest/src/modules/InventoryCost/utils.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { chain } from 'lodash';
|
||||
|
||||
/**
|
||||
* Grpups by transaction type and id the inventory transactions.
|
||||
* @param {IInventoryTransaction} invTransactions
|
||||
* @returns
|
||||
*/
|
||||
export function groupInventoryTransactionsByTypeId(
|
||||
transactions: { transactionType: string; transactionId: number }[]
|
||||
): { transactionType: string; transactionId: number }[][] {
|
||||
return chain(transactions)
|
||||
.groupBy((t) => `${t.transactionType}-${t.transactionId}`)
|
||||
.values()
|
||||
.value();
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { CreateItemCategoryService } from './commands/CreateItemCategory.service
|
||||
import { DeleteItemCategoryService } from './commands/DeleteItemCategory.service';
|
||||
import { EditItemCategoryService } from './commands/EditItemCategory.service';
|
||||
import { GetItemCategoryService } from './queries/GetItemCategory.service';
|
||||
import { GetItemCategoriesService } from './queries/GetItemCategories.service';
|
||||
|
||||
@Injectable()
|
||||
export class ItemCategoryApplication {
|
||||
@@ -18,6 +19,7 @@ export class ItemCategoryApplication {
|
||||
private readonly editItemCategoryService: EditItemCategoryService,
|
||||
private readonly getItemCategoryService: GetItemCategoryService,
|
||||
private readonly deleteItemCategoryService: DeleteItemCategoryService,
|
||||
private readonly getItemCategoriesService: GetItemCategoriesService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -69,4 +71,13 @@ export class ItemCategoryApplication {
|
||||
public deleteItemCategory(itemCategoryId: number) {
|
||||
return this.deleteItemCategoryService.deleteItemCategory(itemCategoryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item categories list.
|
||||
* @param {IItemCategoriesFilter} filterDTO - The item categories filter DTO.
|
||||
* @returns {Promise<IItemCategory[]>}
|
||||
*/
|
||||
public getItemCategories(filterDTO: IItemCategoriesFilter) {
|
||||
return this.getItemCategoriesService.getItemCategories(filterDTO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as R from 'ramda';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { ItemCategory } from '../models/ItemCategory.model';
|
||||
import { Inject } from '@nestjs/common';
|
||||
|
||||
export class GetItemCategoriesService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
|
||||
@Inject(ItemCategory.name)
|
||||
private readonly itemCategoryModel: typeof ItemCategory,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parses items categories filter DTO.
|
||||
* @param {} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parsesListFilterDTO(filterDTO) {
|
||||
return R.compose(
|
||||
// Parses stringified filter roles.
|
||||
this.dynamicListService.parseStringifiedFilter,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve item categories list.
|
||||
* @param {number} tenantId
|
||||
* @param filter
|
||||
*/
|
||||
public async getItemCategories(
|
||||
filterDTO: IItemCategoriesFilter,
|
||||
): Promise<{ itemCategories: ItemCategory[]; filterMeta: IFilterMeta }> {
|
||||
// Parses list filter DTO.
|
||||
const filter = this.parsesListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
this.itemCategoryModel,
|
||||
filter,
|
||||
);
|
||||
// Items categories.
|
||||
const itemCategories = await this.itemCategoryModel
|
||||
.query()
|
||||
.onBuild((query) => {
|
||||
// Subquery to calculate sumation of associated items to the item category.
|
||||
query.select(
|
||||
'*',
|
||||
this.itemCategoryModel.relatedQuery('items').count().as('count'),
|
||||
);
|
||||
|
||||
dynamicList.buildQuery()(query);
|
||||
});
|
||||
return { itemCategories, filterMeta: dynamicList.getResponseMeta() };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import * as R from 'ramda';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '../DynamicListing/DynamicList.service';
|
||||
import { Item } from './models/Item';
|
||||
import { IItemsFilter } from './types/Items.types';
|
||||
import { ItemTransformer } from './Item.transformer';
|
||||
|
||||
@Injectable()
|
||||
export class GetItemsService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(Item.name)
|
||||
private readonly itemModel: typeof Item,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Parses items list filter DTO.
|
||||
* @param {} filterDTO - Filter DTO.
|
||||
*/
|
||||
private parseItemsListFilterDTO(filterDTO: IItemsFilter) {
|
||||
return R.compose(
|
||||
this.dynamicListService.parseStringifiedFilter<IItemsFilter>,
|
||||
)(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves items datatable list.
|
||||
* @param {IItemsFilter} itemsFilter - Items filter.
|
||||
*/
|
||||
public async getItems(filterDTO: IItemsFilter) {
|
||||
// Parses items list filter DTO.
|
||||
const filter = this.parseItemsListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
Item,
|
||||
filter,
|
||||
);
|
||||
const { results: items, pagination } = await this.itemModel
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
builder.modify('inactiveMode', filter.inactiveMode);
|
||||
|
||||
builder.withGraphFetched('inventoryAccount');
|
||||
builder.withGraphFetched('sellAccount');
|
||||
builder.withGraphFetched('costAccount');
|
||||
builder.withGraphFetched('category');
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Retrieves the transformed items.
|
||||
const transformedItems = await this.transformer.transform(
|
||||
items,
|
||||
new ItemTransformer(),
|
||||
);
|
||||
return {
|
||||
items: transformedItems,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,32 +4,21 @@ import {
|
||||
Delete,
|
||||
Param,
|
||||
Post,
|
||||
UsePipes,
|
||||
UseGuards,
|
||||
Patch,
|
||||
Get,
|
||||
Put,
|
||||
} from '@nestjs/common';
|
||||
import { ZodValidationPipe } from '@/common/pipes/ZodValidation.pipe';
|
||||
import { createItemSchema } from './Item.schema';
|
||||
import { CreateItemService } from './CreateItem.service';
|
||||
import { DeleteItemService } from './DeleteItem.service';
|
||||
import { TenantController } from '../Tenancy/Tenant.controller';
|
||||
import { SubscriptionGuard } from '../Subscription/interceptors/Subscription.guard';
|
||||
import { PublicRoute } from '../Auth/Jwt.guard';
|
||||
import { EditItemService } from './EditItem.service';
|
||||
import { ItemsApplicationService } from './ItemsApplication.service';
|
||||
|
||||
@Controller('/items')
|
||||
@UseGuards(SubscriptionGuard)
|
||||
@PublicRoute()
|
||||
export class ItemsController extends TenantController {
|
||||
constructor(
|
||||
private readonly createItemService: CreateItemService,
|
||||
private readonly deleteItemService: DeleteItemService,
|
||||
private readonly editItemService: EditItemService,
|
||||
private readonly itemsApplication: ItemsApplicationService,
|
||||
) {
|
||||
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -46,7 +35,7 @@ export class ItemsController extends TenantController {
|
||||
@Body() editItemDto: any,
|
||||
): Promise<number> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.editItemService.editItem(itemId, editItemDto);
|
||||
return this.itemsApplication.editItem(itemId, editItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +46,7 @@ export class ItemsController extends TenantController {
|
||||
@Post()
|
||||
// @UsePipes(new ZodValidationPipe(createItemSchema))
|
||||
async createItem(@Body() createItemDto: any): Promise<number> {
|
||||
return this.createItemService.createItem(createItemDto);
|
||||
return this.itemsApplication.createItem(createItemDto);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +56,7 @@ export class ItemsController extends TenantController {
|
||||
@Delete(':id')
|
||||
async deleteItem(@Param('id') id: string): Promise<void> {
|
||||
const itemId = parseInt(id, 10);
|
||||
return this.deleteItemService.deleteItem(itemId);
|
||||
return this.itemsApplication.deleteItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,9 +13,11 @@ import { ItemTransactionsService } from './ItemTransactions.service';
|
||||
import { GetItemService } from './GetItem.service';
|
||||
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
|
||||
import { ItemsEntriesService } from './ItemsEntries.service';
|
||||
import { GetItemsService } from './GetItems.service';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule],
|
||||
imports: [TenancyDatabaseModule, DynamicListModule],
|
||||
controllers: [ItemsController],
|
||||
providers: [
|
||||
ItemsValidators,
|
||||
@@ -26,6 +28,7 @@ import { ItemsEntriesService } from './ItemsEntries.service';
|
||||
DeleteItemService,
|
||||
ItemsApplicationService,
|
||||
GetItemService,
|
||||
GetItemsService,
|
||||
ItemTransactionsService,
|
||||
TenancyContext,
|
||||
TransformerInjectable,
|
||||
|
||||
@@ -9,6 +9,8 @@ import { ActivateItemService } from './ActivateItem.service';
|
||||
import { GetItemService } from './GetItem.service';
|
||||
import { ItemTransactionsService } from './ItemTransactions.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GetItemsService } from './GetItems.service';
|
||||
import { IItemsFilter } from './types/Items.types';
|
||||
|
||||
@Injectable()
|
||||
export class ItemsApplicationService {
|
||||
@@ -19,6 +21,7 @@ export class ItemsApplicationService {
|
||||
private readonly activateItemService: ActivateItemService,
|
||||
private readonly inactivateItemService: InactivateItem,
|
||||
private readonly getItemService: GetItemService,
|
||||
private readonly getItemsService: GetItemsService,
|
||||
private readonly itemTransactionsService: ItemTransactionsService,
|
||||
) {}
|
||||
|
||||
@@ -86,6 +89,14 @@ export class ItemsApplicationService {
|
||||
return this.getItemService.getItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the paginated filterable items list.
|
||||
* @param {IItemsFilter} filterDTO
|
||||
*/
|
||||
async getItems(filterDTO: IItemsFilter) {
|
||||
return this.getItemsService.getItems(filterDTO)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item associated invoices transactions.
|
||||
* @param {number} itemId
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import * as F from 'fp-ts/function';
|
||||
import * as R from 'ramda';
|
||||
import { SearchableModel } from '@/modules/Search/SearchableMdel';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
|
||||
// import { TenantModel } from '@/modules/System/models/TenantModel';
|
||||
import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel';
|
||||
import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel';
|
||||
import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableModel';
|
||||
import { MetadataModelMixin } from '@/modules/DynamicListing/models/MetadataModel';
|
||||
|
||||
// const Extend = R.compose(SearchableModel)(TenantModel);
|
||||
const ExtendedItem = R.pipe(
|
||||
CustomViewBaseModelMixin,
|
||||
SearchableBaseModelMixin,
|
||||
ResourceableModelMixin,
|
||||
MetadataModelMixin
|
||||
)(BaseModel);
|
||||
|
||||
export class Item extends BaseModel {
|
||||
export class Item extends ExtendedItem {
|
||||
public readonly quantityOnHand: number;
|
||||
public readonly name: string;
|
||||
public readonly active: boolean;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||
|
||||
export interface IItemsFilter extends IDynamicListFilter {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
inactiveMode: boolean;
|
||||
viewSlug?: string;
|
||||
}
|
||||
@@ -1,77 +1,66 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IManualJournalsFilter,
|
||||
// IManualJournal,
|
||||
// IPaginationMeta,
|
||||
// IFilterMeta,
|
||||
// } from '@/interfaces';
|
||||
// import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
// import { ManualJournalTransfromer } from './ManualJournalTransformer';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import * as R from 'ramda';
|
||||
import { ManualJournalTransfromer } from './ManualJournalTransformer';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { ManualJournal } from '../models/ManualJournal';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
|
||||
// @Service()
|
||||
// export class GetManualJournals {
|
||||
// @Inject()
|
||||
// private tenancy: TenancyService;
|
||||
@Injectable()
|
||||
export class GetManualJournals {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
@Inject(ManualJournal.name)
|
||||
private readonly manualJournalModel: typeof ManualJournal,
|
||||
) {}
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
/**
|
||||
* Parses filter DTO of the manual journals list.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO = (filterDTO) => {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Parses filter DTO of the manual journals list.
|
||||
// * @param filterDTO
|
||||
// */
|
||||
// private parseListFilterDTO = (filterDTO) => {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// };
|
||||
/**
|
||||
* Retrieve manual journals datatable list.
|
||||
* @param {number} tenantId -
|
||||
* @param {IManualJournalsFilter} filter -
|
||||
*/
|
||||
public getManualJournals = async (
|
||||
filterDTO: IManualJournalsFilter,
|
||||
): Promise<{
|
||||
manualJournals: ManualJournal[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> => {
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// /**
|
||||
// * Retrieve manual journals datatable list.
|
||||
// * @param {number} tenantId -
|
||||
// * @param {IManualJournalsFilter} filter -
|
||||
// */
|
||||
// public getManualJournals = async (
|
||||
// tenantId: number,
|
||||
// filterDTO: IManualJournalsFilter
|
||||
// ): Promise<{
|
||||
// manualJournals: IManualJournal[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> => {
|
||||
// const { ManualJournal } = this.tenancy.models(tenantId);
|
||||
// Dynamic service.
|
||||
const dynamicService = await this.dynamicListService.dynamicList(
|
||||
ManualJournal,
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await this.manualJournalModel.query()
|
||||
.onBuild((builder) => {
|
||||
dynamicService.buildQuery()(builder);
|
||||
builder.withGraphFetched('entries.account');
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Parses filter DTO.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
// Transformes the manual journals models to POJO.
|
||||
const manualJournals = await this.transformer.transform(
|
||||
results,
|
||||
new ManualJournalTransfromer(),
|
||||
);
|
||||
|
||||
// // Dynamic service.
|
||||
// const dynamicService = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// ManualJournal,
|
||||
// filter
|
||||
// );
|
||||
// const { results, pagination } = await ManualJournal.query()
|
||||
// .onBuild((builder) => {
|
||||
// dynamicService.buildQuery()(builder);
|
||||
// builder.withGraphFetched('entries.account');
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Transformes the manual journals models to POJO.
|
||||
// const manualJournals = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new ManualJournalTransfromer()
|
||||
// );
|
||||
|
||||
// return {
|
||||
// manualJournals,
|
||||
// pagination,
|
||||
// filterMeta: dynamicService.getResponseMeta(),
|
||||
// };
|
||||
// };
|
||||
// }
|
||||
return {
|
||||
manualJournals,
|
||||
pagination,
|
||||
filterMeta: dynamicService.getResponseMeta(),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { GetPaymentReceivedInvoices } from './queries/GetPaymentReceivedInvoices
|
||||
import { GetPaymentReceivedPdfService } from './queries/GetPaymentReceivedPdf.service';
|
||||
// import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
|
||||
import { GetPaymentReceivedStateService } from './queries/GetPaymentReceivedState.service';
|
||||
import { GetPaymentsReceivedService } from './queries/GetPaymentsReceived.service';
|
||||
|
||||
@Injectable()
|
||||
export class PaymentReceivesApplication {
|
||||
@@ -24,7 +25,7 @@ export class PaymentReceivesApplication {
|
||||
private createPaymentReceivedService: CreatePaymentReceivedService,
|
||||
private editPaymentReceivedService: EditPaymentReceivedService,
|
||||
private deletePaymentReceivedService: DeletePaymentReceivedService,
|
||||
// private getPaymentsReceivedService: GetPaymentReceives,
|
||||
private getPaymentsReceivedService: GetPaymentsReceivedService,
|
||||
private getPaymentReceivedService: GetPaymentReceivedService,
|
||||
private getPaymentReceiveInvoicesService: GetPaymentReceivedInvoices,
|
||||
// private paymentSmsNotify: PaymentReceiveNotifyBySms,
|
||||
@@ -77,19 +78,9 @@ export class PaymentReceivesApplication {
|
||||
* @param {IPaymentsReceivedFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
// public async getPaymentReceives(
|
||||
// tenantId: number,
|
||||
// filterDTO: IPaymentsReceivedFilter,
|
||||
// ): Promise<{
|
||||
// paymentReceives: IPaymentReceived[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// return this.getPaymentsReceivedService.getPaymentReceives(
|
||||
// tenantId,
|
||||
// filterDTO,
|
||||
// );
|
||||
// }
|
||||
public async getPaymentsReceived(filterDTO: IPaymentsReceivedFilter) {
|
||||
return this.getPaymentsReceivedService.getPaymentReceives(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the given payment receive.
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { PaymentReceivesApplication } from './PaymentReceived.application';
|
||||
import {
|
||||
@@ -49,6 +50,11 @@ export class PaymentReceivesController {
|
||||
);
|
||||
}
|
||||
|
||||
@Get()
|
||||
public getPaymentsReceived(@Query() filterDTO: IPaymentsReceivedFilter) {
|
||||
return this.paymentReceivesApplication.getPaymentsReceived(filterDTO);
|
||||
}
|
||||
|
||||
@Get('state')
|
||||
public getPaymentReceivedState() {
|
||||
return this.paymentReceivesApplication.getPaymentReceivedState();
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as R from 'ramda';
|
||||
import { PaymentReceiveTransfromer } from './PaymentReceivedTransformer';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { PaymentReceived } from '../models/PaymentReceived';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
|
||||
@Injectable()
|
||||
export class GetPaymentsReceivedService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(PaymentReceived.name)
|
||||
private readonly paymentReceivedModel: typeof PaymentReceived,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve payment receives paginated and filterable list.
|
||||
* @param {IPaymentsReceivedFilter} paymentReceivesFilter
|
||||
*/
|
||||
public async getPaymentReceives(filterDTO: IPaymentsReceivedFilter): Promise<{
|
||||
paymentReceives: PaymentReceived[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicList = await this.dynamicListService.dynamicList(
|
||||
PaymentReceive,
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await this.paymentReceivedModel
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('depositAccount');
|
||||
|
||||
dynamicList.buildQuery()(builder);
|
||||
filterDTO?.filterQuery && filterDTO.filterQuery(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformer the payment receives models to POJO.
|
||||
const transformedPayments = await this.transformer.transform(
|
||||
results,
|
||||
new PaymentReceiveTransfromer(),
|
||||
);
|
||||
return {
|
||||
paymentReceives: transformedPayments,
|
||||
pagination,
|
||||
filterMeta: dynamicList.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses payments receive list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IFilterMeta,
|
||||
// IPaginationMeta,
|
||||
// IPaymentReceived,
|
||||
// IPaymentsReceivedFilter,
|
||||
// } from '@/interfaces';
|
||||
// import { PaymentReceiveTransfromer } from './PaymentReceivedTransformer';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
|
||||
// @Service()
|
||||
// export class GetPaymentReceives {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
|
||||
// /**
|
||||
// * Retrieve payment receives paginated and filterable list.
|
||||
// * @param {number} tenantId
|
||||
// * @param {IPaymentsReceivedFilter} paymentReceivesFilter
|
||||
// */
|
||||
// public async getPaymentReceives(
|
||||
// tenantId: number,
|
||||
// filterDTO: IPaymentsReceivedFilter
|
||||
// ): Promise<{
|
||||
// paymentReceives: IPaymentReceived[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||
|
||||
// // Parses filter DTO.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicList = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// PaymentReceive,
|
||||
// filter
|
||||
// );
|
||||
// const { results, pagination } = await PaymentReceive.query()
|
||||
// .onBuild((builder) => {
|
||||
// builder.withGraphFetched('customer');
|
||||
// builder.withGraphFetched('depositAccount');
|
||||
|
||||
// dynamicList.buildQuery()(builder);
|
||||
// filterDTO?.filterQuery && filterDTO.filterQuery(builder);
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Transformer the payment receives models to POJO.
|
||||
// const transformedPayments = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new PaymentReceiveTransfromer()
|
||||
// );
|
||||
// return {
|
||||
// paymentReceives: transformedPayments,
|
||||
// pagination,
|
||||
// filterMeta: dynamicList.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parses payments receive list filter DTO.
|
||||
// * @param filterDTO
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ResourceService } from './ResourceService';
|
||||
|
||||
@Module({
|
||||
providers: [ResourceService],
|
||||
exports: [ResourceService],
|
||||
})
|
||||
export class ResourceModule {}
|
||||
170
packages/server-nest/src/modules/Resource/ResourceService.ts
Normal file
170
packages/server-nest/src/modules/Resource/ResourceService.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { pickBy } from 'lodash';
|
||||
import { WarehousesSettings } from '../Warehouses/WarehousesSettings';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { BranchesSettingsService } from '../Branches/BranchesSettings';
|
||||
import { ServiceError } from '../Items/ServiceError';
|
||||
import { IModelMetaColumn, IModelMetaField2 } from '@/interfaces/Model';
|
||||
import { IModelMeta } from '@/interfaces/Model';
|
||||
import { IModelMetaField } from '@/interfaces/Model';
|
||||
import { Features } from '@/common/types/Features';
|
||||
import { resourceToModelName } from './_utils';
|
||||
|
||||
const ERRORS = {
|
||||
RESOURCE_MODEL_NOT_FOUND: 'RESOURCE_MODEL_NOT_FOUND',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ResourceService {
|
||||
constructor(
|
||||
private readonly branchesSettings: BranchesSettingsService,
|
||||
private readonly warehousesSettings: WarehousesSettings,
|
||||
private readonly moduleRef: ModuleRef,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve resource model object.
|
||||
* @param {string} inputModelName - Input model name.
|
||||
*/
|
||||
public getResourceModel(inputModelName: string) {
|
||||
const modelName = resourceToModelName(inputModelName);
|
||||
const resourceModel = this.moduleRef.get(modelName);
|
||||
|
||||
if (!resourceModel) {
|
||||
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
|
||||
}
|
||||
return resourceModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource meta.
|
||||
* @param {string} modelName - Model name.
|
||||
* @param {string} metakey - Meta key.
|
||||
* @returns {IModelMeta}
|
||||
*/
|
||||
public getResourceMeta(modelName: string, metakey?: string): IModelMeta {
|
||||
const resourceModel = this.getResourceModel(modelName);
|
||||
|
||||
// Retrieve the resource meta.
|
||||
const resourceMeta = resourceModel.getMeta(metakey);
|
||||
|
||||
// Localization the fields names.
|
||||
return resourceMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource fields.
|
||||
* @param {string} modelName
|
||||
* @returns {IModelMetaField}
|
||||
*/
|
||||
public getResourceFields(modelName: string): {
|
||||
[key: string]: IModelMetaField;
|
||||
} {
|
||||
const meta = this.getResourceMeta(modelName);
|
||||
|
||||
return meta.fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the fields based on the features.
|
||||
* @param {IModelMetaField2} fields
|
||||
* @returns {IModelMetaField2}
|
||||
*/
|
||||
public filterSupportFeatures = (
|
||||
fields: Record<string, IModelMetaField2 | IModelMetaColumn>,
|
||||
) => {
|
||||
const isMultiFeaturesEnabled =
|
||||
this.branchesSettings.isMultiBranchesActive();
|
||||
const isMultiWarehousesEnabled =
|
||||
this.warehousesSettings.isMultiWarehousesActive();
|
||||
|
||||
return pickBy(fields, (field) => {
|
||||
if (
|
||||
!isMultiWarehousesEnabled &&
|
||||
field.features?.includes(Features.WAREHOUSES)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!isMultiFeaturesEnabled &&
|
||||
field.features?.includes(Features.BRANCHES)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the resource fields.
|
||||
* @param {string} modelName
|
||||
* @returns {IModelMetaField2}
|
||||
*/
|
||||
public getResourceFields2(modelName: string): {
|
||||
[key: string]: IModelMetaField2;
|
||||
} {
|
||||
const meta = this.getResourceMeta(modelName);
|
||||
|
||||
return this.filterSupportFeatures(meta.fields2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource columns.
|
||||
* @param {string} modelName - The model name.
|
||||
* @returns {IModelMetaColumn}
|
||||
*/
|
||||
public getResourceColumns(modelName: string) {
|
||||
const meta = this.getResourceMeta(modelName);
|
||||
|
||||
return this.filterSupportFeatures(meta.columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource importable fields.
|
||||
* @param {string} modelName - The model name.
|
||||
* @returns {IModelMetaField}
|
||||
*/
|
||||
public getResourceImportableFields(modelName: string): {
|
||||
[key: string]: IModelMetaField;
|
||||
} {
|
||||
const fields = this.getResourceFields(modelName);
|
||||
|
||||
return pickBy(fields, (field) => field.importable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the resource meta localized based on the current user language.
|
||||
*/
|
||||
// public getResourceMetaLocalized(meta, tenantId) {
|
||||
// const $enumerationType = (field) =>
|
||||
// field.fieldType === 'enumeration' ? field : undefined;
|
||||
|
||||
// const $hasFields = (field) =>
|
||||
// 'undefined' !== typeof field.fields ? field : undefined;
|
||||
|
||||
// const $ColumnHasColumns = (column) =>
|
||||
// 'undefined' !== typeof column.columns ? column : undefined;
|
||||
|
||||
// const $hasColumns = (columns) =>
|
||||
// 'undefined' !== typeof columns ? columns : undefined;
|
||||
|
||||
// const naviagations = [
|
||||
// ['fields', qim.$each, 'name'],
|
||||
// ['fields', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||
// ['fields2', qim.$each, 'name'],
|
||||
// ['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'],
|
||||
// ['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'],
|
||||
// ['columns', $hasColumns, qim.$each, 'name'],
|
||||
// [
|
||||
// 'columns',
|
||||
// $hasColumns,
|
||||
// qim.$each,
|
||||
// $ColumnHasColumns,
|
||||
// 'columns',
|
||||
// qim.$each,
|
||||
// 'name',
|
||||
// ],
|
||||
// ];
|
||||
// return this.i18nService.i18nApply(naviagations, meta, tenantId);
|
||||
// }
|
||||
}
|
||||
6
packages/server-nest/src/modules/Resource/_utils.ts
Normal file
6
packages/server-nest/src/modules/Resource/_utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { camelCase, upperFirst } from 'lodash';
|
||||
import pluralize from 'pluralize';
|
||||
|
||||
export const resourceToModelName = (resourceName: string): string => {
|
||||
return upperFirst(camelCase(pluralize.singular(resourceName)));
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import { BaseModel } from '@/models/Model';
|
||||
|
||||
type GConstructor<T = {}> = new (...args: any[]) => T;
|
||||
|
||||
export const ResourceableModelMixin = <T extends GConstructor<BaseModel>>(
|
||||
Model: T,
|
||||
) =>
|
||||
class ResourceableModel extends Model {
|
||||
static get resourceable() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -21,6 +21,7 @@ import { RejectSaleEstimateService } from './commands/RejectSaleEstimate.service
|
||||
import { GetSaleEstimateState } from './queries/GetSaleEstimateState.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { GetSaleEstimatesService } from './queries/GetSaleEstimates.service';
|
||||
|
||||
@Injectable()
|
||||
export class SaleEstimatesApplication {
|
||||
@@ -29,7 +30,7 @@ export class SaleEstimatesApplication {
|
||||
private readonly editSaleEstimateService: EditSaleEstimate,
|
||||
private readonly deleteSaleEstimateService: DeleteSaleEstimate,
|
||||
private readonly getSaleEstimateService: GetSaleEstimate,
|
||||
// private readonly getSaleEstimatesService: GetSaleEstimates,
|
||||
private readonly getSaleEstimatesService: GetSaleEstimatesService,
|
||||
private readonly deliverSaleEstimateService: DeliverSaleEstimateService,
|
||||
private readonly approveSaleEstimateService: ApproveSaleEstimateService,
|
||||
private readonly rejectSaleEstimateService: RejectSaleEstimateService,
|
||||
@@ -80,9 +81,9 @@ export class SaleEstimatesApplication {
|
||||
* @param {ISalesEstimatesFilter} filterDTO - Sales estimates filter DTO.
|
||||
* @returns
|
||||
*/
|
||||
// public getSaleEstimates(filterDTO: ISalesEstimatesFilter) {
|
||||
// return this.getSaleEstimatesService.getEstimates(filterDTO);
|
||||
// }
|
||||
public getSaleEstimates(filterDTO: ISalesEstimatesFilter) {
|
||||
return this.getSaleEstimatesService.getEstimates(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver the given sale estimate.
|
||||
@@ -148,8 +149,7 @@ export class SaleEstimatesApplication {
|
||||
* @param {number} saleEstimateId - Sale estimate ID.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public sendSaleEstimateMail() // saleEstimateId: number,
|
||||
// saleEstimateMailOpts: SaleEstimateMailOptionsDTO,
|
||||
public sendSaleEstimateMail() // saleEstimateMailOpts: SaleEstimateMailOptionsDTO, // saleEstimateId: number,
|
||||
{
|
||||
// return this.sendEstimateMailService.triggerMail(
|
||||
// saleEstimateId,
|
||||
|
||||
@@ -58,10 +58,10 @@ export class SaleEstimatesController {
|
||||
return this.saleEstimatesApplication.getSaleEstimateState();
|
||||
}
|
||||
|
||||
// @Get()
|
||||
// public getSaleEstimates(@Query() filterDTO: ISalesEstimatesFilter) {
|
||||
// return this.saleEstimatesApplication.getSaleEstimates(filterDTO);
|
||||
// }
|
||||
@Get()
|
||||
public getSaleEstimates(@Query() filterDTO: ISalesEstimatesFilter) {
|
||||
return this.saleEstimatesApplication.getSaleEstimates(filterDTO);
|
||||
}
|
||||
|
||||
@Post(':id/deliver')
|
||||
public deliverSaleEstimate(
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import * as R from 'ramda';
|
||||
import { SaleEstimateTransfromer } from './SaleEstimate.transformer';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { SaleEstimate } from '../models/SaleEstimate';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { ISalesEstimatesFilter } from '../types/SaleEstimates.types';
|
||||
|
||||
@Injectable()
|
||||
export class GetSaleEstimatesService {
|
||||
constructor(
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
private readonly transformer: TransformerInjectable,
|
||||
|
||||
@Inject(SaleEstimate.name)
|
||||
private readonly saleEstimateModel: typeof SaleEstimate,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves estimates filterable and paginated list.
|
||||
* @param {IEstimatesFilter} estimatesFilter -
|
||||
*/
|
||||
public async getEstimates(
|
||||
filterDTO: ISalesEstimatesFilter
|
||||
): Promise<{
|
||||
salesEstimates: SaleEstimate[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses filter DTO.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
SaleEstimate,
|
||||
filter
|
||||
);
|
||||
const { results, pagination } = await this.saleEstimateModel.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('entries');
|
||||
builder.withGraphFetched('entries.item');
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
const transformedEstimates = await this.transformer.transform(
|
||||
results,
|
||||
new SaleEstimateTransfromer()
|
||||
);
|
||||
return {
|
||||
salesEstimates: transformedEstimates,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale receipts list filter DTO.
|
||||
* @param filterDTO
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
// import * as R from 'ramda';
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import {
|
||||
// IFilterMeta,
|
||||
// IPaginationMeta,
|
||||
// ISaleEstimate,
|
||||
// ISalesEstimatesFilter,
|
||||
// } from '@/interfaces';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
// import { SaleEstimateDTOTransformer } from '../commands/SaleEstimateDTOTransformer';
|
||||
// import { SaleEstimateTransfromer } from './SaleEstimate.transformer';
|
||||
|
||||
// @Service()
|
||||
// export class GetSaleEstimates {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
|
||||
// /**
|
||||
// * Retrieves estimates filterable and paginated list.
|
||||
// * @param {number} tenantId -
|
||||
// * @param {IEstimatesFilter} estimatesFilter -
|
||||
// */
|
||||
// public async getEstimates(
|
||||
// tenantId: number,
|
||||
// filterDTO: ISalesEstimatesFilter
|
||||
// ): Promise<{
|
||||
// salesEstimates: ISaleEstimate[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||
|
||||
// // Parses filter DTO.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// SaleEstimate,
|
||||
// filter
|
||||
// );
|
||||
// const { results, pagination } = await SaleEstimate.query()
|
||||
// .onBuild((builder) => {
|
||||
// builder.withGraphFetched('customer');
|
||||
// builder.withGraphFetched('entries');
|
||||
// builder.withGraphFetched('entries.item');
|
||||
// dynamicFilter.buildQuery()(builder);
|
||||
// filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// const transformedEstimates = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new SaleEstimateTransfromer()
|
||||
// );
|
||||
// return {
|
||||
// salesEstimates: transformedEstimates,
|
||||
// pagination,
|
||||
// filterMeta: dynamicFilter.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parses the sale receipts list filter DTO.
|
||||
// * @param filterDTO
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
@@ -4,6 +4,7 @@ import { Knex } from 'knex';
|
||||
import { SaleEstimate } from '../models/SaleEstimate';
|
||||
import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types';
|
||||
import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types';
|
||||
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||
|
||||
export interface ISaleEstimateDTO {
|
||||
customerId: number;
|
||||
@@ -22,10 +23,10 @@ export interface ISaleEstimateDTO {
|
||||
attachments?: AttachmentLinkDTO[];
|
||||
}
|
||||
|
||||
// export interface ISalesEstimatesFilter extends IDynamicListFilterDTO {
|
||||
// stringifiedFilterRoles?: string;
|
||||
// filterQuery?: (q: any) => void;
|
||||
// }
|
||||
export interface ISalesEstimatesFilter extends IDynamicListFilter {
|
||||
stringifiedFilterRoles?: string;
|
||||
filterQuery?: (q: any) => void;
|
||||
}
|
||||
|
||||
export interface ISaleEstimateCreatedPayload {
|
||||
// tenantId: number;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { Knex } from 'knex';
|
||||
import { ISaleInvoice } from '@/interfaces';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
import InventoryService from '@/services/Inventory/Inventory';
|
||||
|
||||
@Service()
|
||||
export class InvoiceInventoryTransactions {
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
@Inject()
|
||||
private inventoryService: InventoryService;
|
||||
|
||||
/**
|
||||
* Records the inventory transactions of the given sale invoice in case
|
||||
* the invoice has inventory entries only.
|
||||
*
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {SaleInvoice} saleInvoice - Sale invoice DTO.
|
||||
* @param {number} saleInvoiceId - Sale invoice id.
|
||||
* @param {boolean} override - Allow to override old transactions.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTranscactions(
|
||||
saleInvoice: ISaleInvoice,
|
||||
override?: boolean,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
tenantId,
|
||||
saleInvoice.entries,
|
||||
trx
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: saleInvoice.id,
|
||||
transactionType: 'SaleInvoice',
|
||||
transactionNumber: saleInvoice.invoiceNo,
|
||||
|
||||
exchangeRate: saleInvoice.exchangeRate,
|
||||
warehouseId: saleInvoice.warehouseId,
|
||||
|
||||
date: saleInvoice.invoiceDate,
|
||||
direction: 'OUT',
|
||||
entries: inventoryEntries,
|
||||
createdAt: saleInvoice.createdAt,
|
||||
};
|
||||
await this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
tenantId,
|
||||
transaction,
|
||||
override,
|
||||
trx
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverting the inventory transactions once the invoice deleted.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertInventoryTransactions(
|
||||
saleInvoiceId: number,
|
||||
trx?: Knex.Transaction
|
||||
): Promise<void> {
|
||||
// Delete the inventory transaction of the given sale invoice.
|
||||
const { oldInventoryTransactions } =
|
||||
await this.inventoryService.deleteInventoryTransactions(
|
||||
tenantId,
|
||||
saleInvoiceId,
|
||||
'SaleInvoice',
|
||||
trx
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { InvoiceInventoryTransactions } from '../commands/inventory/InvoiceInventoryTransactions';
|
||||
import { events } from '@/common/events/events';
|
||||
import {
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
ISaleInvoiceEventDeliveredPayload,
|
||||
} from '../SaleInvoice.types';
|
||||
|
||||
@Injectable()
|
||||
export class SaleInvoiceWriteInventoryTransactionsSubscriber {
|
||||
constructor(
|
||||
private readonly saleInvoiceInventory: InvoiceInventoryTransactions,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles the writing inventory transactions once the invoice created.
|
||||
* @param {ISaleInvoiceCreatedPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleInvoice.onCreated)
|
||||
public async handleWritingInventoryTransactions({
|
||||
saleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceCreatedPayload | ISaleInvoiceEventDeliveredPayload) {
|
||||
// Can't continue if the sale invoice is not delivered yet.
|
||||
if (!saleInvoice.deliveredAt) return null;
|
||||
|
||||
await this.saleInvoiceInventory.recordInventoryTranscactions(
|
||||
saleInvoice,
|
||||
false,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewriting the inventory transactions once the sale invoice be edited.
|
||||
* @param {ISaleInvoiceEditPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleInvoice.onEdited)
|
||||
public async handleRewritingInventoryTransactions({
|
||||
saleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceEditedPayload) {
|
||||
await this.saleInvoiceInventory.recordInventoryTranscactions(
|
||||
saleInvoice,
|
||||
true,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles deleting the inventory transactions once the invoice deleted.
|
||||
* @param {ISaleInvoiceDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleInvoice.onDeleted)
|
||||
public async handleDeletingInventoryTransactions({
|
||||
saleInvoiceId,
|
||||
oldSaleInvoice,
|
||||
trx,
|
||||
}: ISaleInvoiceDeletedPayload) {
|
||||
await this.saleInvoiceInventory.revertInventoryTransactions(
|
||||
saleInvoiceId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,14 @@ import { CloseSaleReceipt } from './commands/CloseSaleReceipt.service';
|
||||
import { DeleteSaleReceipt } from './commands/DeleteSaleReceipt.service';
|
||||
import { GetSaleReceipt } from './queries/GetSaleReceipt.service';
|
||||
import { EditSaleReceipt } from './commands/EditSaleReceipt.service';
|
||||
import { ISaleReceiptDTO, ISaleReceiptState } from './types/SaleReceipts.types';
|
||||
import {
|
||||
ISaleReceiptDTO,
|
||||
ISaleReceiptState,
|
||||
ISalesReceiptsFilter,
|
||||
} from './types/SaleReceipts.types';
|
||||
import { GetSaleReceiptsService } from './queries/GetSaleReceipts.service';
|
||||
import { SaleReceipt } from './models/SaleReceipt';
|
||||
import { IPaginationMeta } from '@/interfaces/Model';
|
||||
|
||||
@Injectable()
|
||||
export class SaleReceiptApplication {
|
||||
@@ -17,7 +24,7 @@ export class SaleReceiptApplication {
|
||||
private editSaleReceiptService: EditSaleReceipt,
|
||||
private getSaleReceiptService: GetSaleReceipt,
|
||||
private deleteSaleReceiptService: DeleteSaleReceipt,
|
||||
// private getSaleReceiptsService: GetSaleReceipts,
|
||||
private getSaleReceiptsService: GetSaleReceiptsService,
|
||||
private closeSaleReceiptService: CloseSaleReceipt,
|
||||
private getSaleReceiptPdfService: SaleReceiptsPdfService,
|
||||
// private saleReceiptNotifyBySmsService: SaleReceiptNotifyBySms,
|
||||
@@ -74,20 +81,16 @@ export class SaleReceiptApplication {
|
||||
|
||||
/**
|
||||
* Retrieve sales receipts paginated and filterable list.
|
||||
* @param {number} tenantId
|
||||
* @param {ISalesReceiptsFilter} filterDTO
|
||||
* @returns
|
||||
*/
|
||||
// public async getSaleReceipts(
|
||||
// tenantId: number,
|
||||
// filterDTO: ISalesReceiptsFilter,
|
||||
// ): Promise<{
|
||||
// data: ISaleReceipt[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// return this.getSaleReceiptsService.getSaleReceipts(tenantId, filterDTO);
|
||||
// }
|
||||
public async getSaleReceipts(filterDTO: ISalesReceiptsFilter): Promise<{
|
||||
data: SaleReceipt[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
return this.getSaleReceiptsService.getSaleReceipts(filterDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the given sale receipt.
|
||||
@@ -106,9 +109,7 @@ export class SaleReceiptApplication {
|
||||
* @returns
|
||||
*/
|
||||
public getSaleReceiptPdf(tenantId: number, saleReceiptId: number) {
|
||||
return this.getSaleReceiptPdfService.saleReceiptPdf(
|
||||
saleReceiptId,
|
||||
);
|
||||
return this.getSaleReceiptPdfService.saleReceiptPdf(saleReceiptId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,8 @@ import { SaleReceiptGLEntriesSubscriber } from './subscribers/SaleReceiptGLEntri
|
||||
import { SaleReceiptGLEntries } from './ledger/SaleReceiptGLEntries';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { SaleReceiptInventoryTransactionsSubscriber } from './inventory/SaleReceiptWriteInventoryTransactions';
|
||||
import { GetSaleReceiptsService } from './queries/GetSaleReceipts.service';
|
||||
|
||||
@Module({
|
||||
controllers: [SaleReceiptsController],
|
||||
@@ -53,7 +55,9 @@ import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
SaleReceiptBrandingTemplate,
|
||||
SaleReceiptIncrement,
|
||||
SaleReceiptGLEntries,
|
||||
SaleReceiptGLEntriesSubscriber
|
||||
SaleReceiptGLEntriesSubscriber,
|
||||
SaleReceiptInventoryTransactionsSubscriber,
|
||||
GetSaleReceiptsService
|
||||
],
|
||||
})
|
||||
export class SaleReceiptsModule {}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import { InventoryService } from '@/modules/InventoryCost/Inventory';
|
||||
import { ItemsEntriesService } from '@/modules/Items/ItemsEntries.service';
|
||||
import { SaleReceipt } from '../models/SaleReceipt';
|
||||
|
||||
@Injectable()
|
||||
export class SaleReceiptInventoryTransactions {
|
||||
constructor(
|
||||
private readonly itemsEntriesService: ItemsEntriesService,
|
||||
private readonly inventoryService: InventoryService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Records the inventory transactions from the given bill input.
|
||||
* @param {Bill} bill - Bill model object.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async recordInventoryTransactions(
|
||||
saleReceipt: SaleReceipt,
|
||||
override?: boolean,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> {
|
||||
// Loads the inventory items entries of the given sale invoice.
|
||||
const inventoryEntries =
|
||||
await this.itemsEntriesService.filterInventoryEntries(
|
||||
saleReceipt.entries,
|
||||
);
|
||||
const transaction = {
|
||||
transactionId: saleReceipt.id,
|
||||
transactionType: 'SaleReceipt',
|
||||
transactionNumber: saleReceipt.receiptNumber,
|
||||
exchangeRate: saleReceipt.exchangeRate,
|
||||
|
||||
date: saleReceipt.receiptDate,
|
||||
direction: 'OUT',
|
||||
entries: inventoryEntries,
|
||||
createdAt: saleReceipt.createdAt,
|
||||
|
||||
warehouseId: saleReceipt.warehouseId,
|
||||
};
|
||||
return this.inventoryService.recordInventoryTransactionsFromItemsEntries(
|
||||
transaction,
|
||||
override,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the inventory transactions of the given bill id.
|
||||
* @param {number} tenantId - Tenant id.
|
||||
* @param {number} billId - Bill id.
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
public async revertInventoryTransactions(
|
||||
receiptId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) {
|
||||
return this.inventoryService.deleteInventoryTransactions(
|
||||
receiptId,
|
||||
'SaleReceipt',
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import {
|
||||
ISaleReceiptCreatedPayload,
|
||||
ISaleReceiptEditedPayload,
|
||||
ISaleReceiptEventDeletedPayload,
|
||||
} from '../types/SaleReceipts.types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { events } from '@/common/events/events';
|
||||
import { SaleReceiptInventoryTransactions } from './SaleReceiptInventoryTransactions';
|
||||
|
||||
@Injectable()
|
||||
export class SaleReceiptInventoryTransactionsSubscriber {
|
||||
constructor(
|
||||
private readonly saleReceiptInventory: SaleReceiptInventoryTransactions
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Handles the writing inventory transactions once the receipt created.
|
||||
* @param {ISaleReceiptCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onCreated)
|
||||
public async handleWritingInventoryTransactions({
|
||||
saleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptCreatedPayload) {
|
||||
// Can't continue if the sale receipt is not closed yet.
|
||||
if (!saleReceipt.closedAt) return null;
|
||||
|
||||
await this.saleReceiptInventory.recordInventoryTransactions(
|
||||
saleReceipt,
|
||||
false,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewriting the inventory transactions once the sale invoice be edited.
|
||||
* @param {ISaleReceiptEditedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onEdited)
|
||||
public async handleRewritingInventoryTransactions({
|
||||
saleReceipt,
|
||||
trx,
|
||||
}: ISaleReceiptEditedPayload) {
|
||||
// Can't continue if the sale receipt is not closed yet.
|
||||
if (!saleReceipt.closedAt) return null;
|
||||
|
||||
await this.saleReceiptInventory.recordInventoryTransactions(
|
||||
saleReceipt,
|
||||
true,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles deleting the inventory transactions once the receipt deleted.
|
||||
* @param {ISaleReceiptEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onDeleted)
|
||||
public async handleDeletingInventoryTransactions({
|
||||
saleReceiptId,
|
||||
trx,
|
||||
}: ISaleReceiptEventDeletedPayload) {
|
||||
await this.saleReceiptInventory.revertInventoryTransactions(
|
||||
saleReceiptId,
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as R from 'ramda';
|
||||
import { SaleReceiptTransformer } from './SaleReceiptTransformer';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
|
||||
import { ISalesReceiptsFilter } from '../types/SaleReceipts.types';
|
||||
import { SaleReceipt } from '../models/SaleReceipt';
|
||||
import { IPaginationMeta } from '@/interfaces/Model';
|
||||
|
||||
interface GetSaleReceiptsSettings {
|
||||
fetchEntriesGraph?: boolean;
|
||||
}
|
||||
@Injectable()
|
||||
export class GetSaleReceiptsService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
private readonly dynamicListService: DynamicListService,
|
||||
|
||||
@Inject(SaleReceipt.name)
|
||||
private readonly saleReceiptModel: typeof SaleReceipt,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve sales receipts paginated and filterable list.
|
||||
* @param {number} tenantId
|
||||
* @param {ISaleReceiptFilter} salesReceiptsFilter
|
||||
*/
|
||||
public async getSaleReceipts(filterDTO: ISalesReceiptsFilter): Promise<{
|
||||
data: SaleReceipt[];
|
||||
pagination: IPaginationMeta;
|
||||
filterMeta: IFilterMeta;
|
||||
}> {
|
||||
// Parses the stringified filter roles.
|
||||
const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// Dynamic list service.
|
||||
const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
SaleReceipt,
|
||||
filter,
|
||||
);
|
||||
const { results, pagination } = await this.saleReceiptModel
|
||||
.query()
|
||||
.onBuild((builder) => {
|
||||
builder.withGraphFetched('depositAccount');
|
||||
builder.withGraphFetched('customer');
|
||||
builder.withGraphFetched('entries.item');
|
||||
|
||||
dynamicFilter.buildQuery()(builder);
|
||||
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
})
|
||||
.pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// Transformes the estimates models to POJO.
|
||||
const salesEstimates = await this.transformer.transform(
|
||||
results,
|
||||
new SaleReceiptTransformer(),
|
||||
);
|
||||
return {
|
||||
data: salesEstimates,
|
||||
pagination,
|
||||
filterMeta: dynamicFilter.getResponseMeta(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the sale invoice list filter DTO.
|
||||
* @param filterDTO
|
||||
* @returns
|
||||
*/
|
||||
private parseListFilterDTO(filterDTO) {
|
||||
return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// import * as R from 'ramda';
|
||||
// import {
|
||||
// IFilterMeta,
|
||||
// IPaginationMeta,
|
||||
// ISaleReceipt,
|
||||
// ISalesReceiptsFilter,
|
||||
// } from '@/interfaces';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import { SaleReceiptTransformer } from './SaleReceiptTransformer';
|
||||
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||
|
||||
// interface GetSaleReceiptsSettings {
|
||||
// fetchEntriesGraph?: boolean;
|
||||
// }
|
||||
// @Service()
|
||||
// export class GetSaleReceipts {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
|
||||
// @Inject()
|
||||
// private transformer: TransformerInjectable;
|
||||
|
||||
// @Inject()
|
||||
// private dynamicListService: DynamicListingService;
|
||||
|
||||
// /**
|
||||
// * Retrieve sales receipts paginated and filterable list.
|
||||
// * @param {number} tenantId
|
||||
// * @param {ISaleReceiptFilter} salesReceiptsFilter
|
||||
// */
|
||||
// public async getSaleReceipts(
|
||||
// tenantId: number,
|
||||
// filterDTO: ISalesReceiptsFilter
|
||||
// ): Promise<{
|
||||
// data: ISaleReceipt[];
|
||||
// pagination: IPaginationMeta;
|
||||
// filterMeta: IFilterMeta;
|
||||
// }> {
|
||||
// const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
// // Parses the stringified filter roles.
|
||||
// const filter = this.parseListFilterDTO(filterDTO);
|
||||
|
||||
// // Dynamic list service.
|
||||
// const dynamicFilter = await this.dynamicListService.dynamicList(
|
||||
// tenantId,
|
||||
// SaleReceipt,
|
||||
// filter
|
||||
// );
|
||||
// const { results, pagination } = await SaleReceipt.query()
|
||||
// .onBuild((builder) => {
|
||||
// builder.withGraphFetched('depositAccount');
|
||||
// builder.withGraphFetched('customer');
|
||||
// builder.withGraphFetched('entries.item');
|
||||
|
||||
// dynamicFilter.buildQuery()(builder);
|
||||
// filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
|
||||
// })
|
||||
// .pagination(filter.page - 1, filter.pageSize);
|
||||
|
||||
// // Transformes the estimates models to POJO.
|
||||
// const salesEstimates = await this.transformer.transform(
|
||||
// tenantId,
|
||||
// results,
|
||||
// new SaleReceiptTransformer()
|
||||
// );
|
||||
// return {
|
||||
// data: salesEstimates,
|
||||
// pagination,
|
||||
// filterMeta: dynamicFilter.getResponseMeta(),
|
||||
// };
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parses the sale invoice list filter DTO.
|
||||
// * @param filterDTO
|
||||
// * @returns
|
||||
// */
|
||||
// private parseListFilterDTO(filterDTO) {
|
||||
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
|
||||
// }
|
||||
// }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user