refactor: dynamic list to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-01-14 22:57:54 +02:00
parent 081fdebee0
commit e7e7a95aa1
81 changed files with 596 additions and 742 deletions

View File

@@ -105,7 +105,8 @@
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
"typescript": "^5.1.3",
"mustache": "^3.0.3"
},
"jest": {
"moduleFileExtensions": [

View File

@@ -39,6 +39,8 @@ export interface IModelMetaFieldCommon {
order?: number;
unique?: number;
dataTransferObjectKey?: string;
filterCustomQuery?: Function;
sortCustomQuery?: Function;
}
export interface IModelMetaFieldText {

View File

@@ -7,6 +7,10 @@ const OperationType = {
};
export class Lexer {
public currentIndex: number;
public input: string;
public tokenList: string[];
// operation table
static get optable() {
return {

View File

@@ -26,7 +26,10 @@ export const OPERATION = {
// grouped?: boolean;
// };
export default class Parser {
export class Parser {
public index: number;
public blockLevel: number;
public token: string[];
constructor(token) {
this.index = -1;

View File

@@ -1,6 +1,9 @@
import { OPERATION } from './Parser';
export default class QueryParser {
export class QueryParser {
public tree: any;
public queries: any;
public query: any;
constructor(tree, queries) {
this.tree = tree;

View File

@@ -1,30 +1,30 @@
export const OtherExpensesAccount = {
name: 'Other Expenses',
slug: 'other-expenses',
account_type: 'other-expense',
accountType: 'other-expense',
code: '40011',
description: '',
active: 1,
active: true,
index: 1,
predefined: 1,
predefined: true,
};
export const TaxPayableAccount = {
name: 'Tax Payable',
slug: 'tax-payable',
account_type: 'tax-payable',
accountType: 'tax-payable',
code: '20006',
description: '',
active: 1,
active: true,
index: 1,
predefined: 1,
predefined: true,
};
export const UnearnedRevenueAccount = {
name: 'Unearned Revenue',
slug: 'unearned-revenue',
account_type: 'other-current-liability',
parent_account_id: null,
accountType: 'other-current-liability',
parentAccountId: null,
code: '50005',
active: true,
index: 1,
@@ -34,8 +34,8 @@ export const UnearnedRevenueAccount = {
export const PrepardExpenses = {
name: 'Prepaid Expenses',
slug: 'prepaid-expenses',
account_type: 'other-current-asset',
parent_account_id: null,
accountType: 'other-current-asset',
parentAccountId: null,
code: '100010',
active: true,
index: 1,
@@ -45,8 +45,8 @@ export const PrepardExpenses = {
export const StripeClearingAccount = {
name: 'Stripe Clearing',
slug: 'stripe-clearing',
account_type: 'other-current-asset',
parent_account_id: null,
accountType: 'other-current-asset',
parentAccountId: null,
code: '100020',
active: true,
index: 1,
@@ -56,7 +56,7 @@ export const StripeClearingAccount = {
export const DiscountExpenseAccount = {
name: 'Discount',
slug: 'discount',
account_type: 'other-income',
accountType: 'other-income',
code: '40008',
active: true,
index: 1,
@@ -66,7 +66,7 @@ export const DiscountExpenseAccount = {
export const PurchaseDiscountAccount = {
name: 'Purchase Discount',
slug: 'purchase-discount',
account_type: 'other-expense',
accountType: 'other-expense',
code: '40009',
active: true,
index: 1,
@@ -76,7 +76,7 @@ export const PurchaseDiscountAccount = {
export const OtherChargesAccount = {
name: 'Other Charges',
slug: 'other-charges',
account_type: 'other-income',
accountType: 'other-income',
code: '40010',
active: true,
index: 1,
@@ -87,7 +87,7 @@ export const SeedAccounts = [
{
name: 'Bank Account',
slug: 'bank-account',
account_type: 'bank',
accountType: 'bank',
code: '10001',
description: '',
active: 1,

View File

@@ -41,7 +41,7 @@ export class DeleteAccount {
await this.accountModel
.query(trx)
.whereIn('parent_account_id', accountsIds)
.patch({ parent_account_id: null });
.patch({ parentAccountId: null });
}
/**

View File

@@ -13,10 +13,10 @@ export class GetAccountsService {
constructor(
private readonly dynamicListService: DynamicListService,
private readonly transformerService: TransformerInjectable,
private readonly accountRepository: AccountRepository,
@Inject(Account.name)
private readonly accountModel: typeof Account,
private readonly accountRepository: AccountRepository,
) {}
/**

View File

@@ -13,32 +13,29 @@ import { TenantModel } from '@/modules/System/models/TenantModel';
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
import { Model } from 'objection';
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
// import AccountSettings from './Account.Settings';
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder';
// import { flatToNestedArray } from 'utils';
// @ts-expect-error
// export class Account extends mixin(TenantModel, [
// ModelSettings,
// CustomViewBaseModel,
// SearchableModel,
// ]) {
export class Account extends TenantModel {
export class Account extends TenantBaseModel {
public name!: string;
public slug!: string;
public code!: string;
public index!: number;
public accountType!: string;
public parentAccountId!: number | null;
public predefined!: boolean;
public currencyCode!: string;
public active!: boolean;
public bankBalance!: number;
public lastFeedsUpdatedAt!: string | null;
public lastFeedsUpdatedAt!: string | Date | null;
public amount!: number;
public plaidItemId!: number;
public plaidAccountId!: string | null;
public isFeedsActive!: boolean;
public plaidItem!: PlaidItem;
/**
@@ -73,11 +70,11 @@ export class Account extends TenantModel {
/**
* Account normal.
*/
get accountNormal() {
get accountNormal(): string {
return AccountTypesUtils.getType(this.accountType, 'normal');
}
get accountNormalFormatted() {
get accountNormalFormatted(): string {
const paris = {
credit: 'Credit',
debit: 'Debit',
@@ -88,35 +85,35 @@ export class Account extends TenantModel {
/**
* Retrieve account type label.
*/
get accountTypeLabel() {
get accountTypeLabel(): string {
return AccountTypesUtils.getType(this.accountType, 'label');
}
/**
* Retrieve account parent type.
*/
get accountParentType() {
get accountParentType(): string {
return AccountTypesUtils.getType(this.accountType, 'parentType');
}
/**
* Retrieve account root type.
*/
get accountRootType() {
get accountRootType(): string {
return AccountTypesUtils.getType(this.accountType, 'rootType');
}
/**
* Retrieve whether the account is balance sheet account.
*/
get isBalanceSheetAccount() {
get isBalanceSheetAccount(): boolean {
return this.isBalanceSheet();
}
/**
* Retrieve whether the account is profit/loss sheet account.
*/
get isPLSheet() {
get isPLSheet(): boolean {
return this.isProfitLossSheet();
}
/**

View File

@@ -147,7 +147,7 @@ export class AccountRepository extends TenantRepository {
}),
accountType: 'accounts-receivable',
currencyCode,
active: 1,
active: true,
...extraAttrs,
});
}
@@ -199,7 +199,7 @@ export class AccountRepository extends TenantRepository {
}),
accountType: 'accounts-payable',
currencyCode,
active: 1,
active: true,
...extraAttrs,
});
}

View File

@@ -47,7 +47,6 @@ export class PlaidSyncDb {
/**
* Syncs the Plaid bank account.
* @param {number} tenantId
* @param {IAccountCreateDTO} createBankAccountDTO
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
@@ -70,7 +69,6 @@ export class PlaidSyncDb {
/**
* Syncs the plaid accounts to the system accounts.
* @param {number} tenantId Tenant ID.
* @param {PlaidAccount[]} plaidAccounts
* @returns {Promise<void>}
*/
@@ -94,7 +92,6 @@ export class PlaidSyncDb {
/**
* Synsc the Plaid transactions to the system GL entries.
* @param {number} tenantId - Tenant ID.
* @param {number} plaidAccountId - Plaid account ID.
* @param {PlaidTransaction[]} plaidTranasctions - Plaid transactions
* @return {Promise<void>}
@@ -136,7 +133,6 @@ export class PlaidSyncDb {
/**
* Syncs the accounts transactions in paraller under controlled concurrency.
* @param {number} tenantId
* @param {PlaidTransaction[]} plaidTransactions
* @return {Promise<void>}
*/
@@ -188,7 +184,6 @@ export class PlaidSyncDb {
/**
* Syncs the Plaid item last transaction cursor.
* @param {number} tenantId - Tenant ID.
* @param {string} itemId - Plaid item ID.
* @param {string} lastCursor - Last transaction cursor.
* @return {Promise<void>}
@@ -206,8 +201,7 @@ export class PlaidSyncDb {
/**
* Updates the last feeds updated at of the given Plaid accounts ids.
* @param {number} tenantId
* @param {string[]} plaidAccountIds
* @param {string[]} plaidAccountIds - Plaid accounts ids.
* @return {Promise<void>}
*/
public async updateLastFeedsUpdatedAt(
@@ -224,9 +218,8 @@ export class PlaidSyncDb {
/**
* Updates the accounts feed active status of the given Plaid accounts ids.
* @param {number} tenantId
* @param {number[]} plaidAccountIds
* @param {boolean} isFeedsActive
* @param {number[]} plaidAccountIds - Plaid accounts ids.
* @param {boolean} isFeedsActive - Feeds active status.
* @returns {Promise<void>}
*/
public async updateAccountsFeedsActive(

View File

@@ -1,11 +1,11 @@
/* eslint-disable global-require */
import { mixin, Model } from 'objection';
import { 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';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class BankAccount extends BaseModel {
export class BankAccount extends TenantBaseModel {
public name!: string;
public slug!: string;
public code!: string;

View File

@@ -48,7 +48,9 @@ export class CashflowAccountTransformer extends Transformer {
* @returns {string}
*/
protected lastFeedsUpdatedAtFormatted(account: Account): string {
return this.formatDate(account.lastFeedsUpdatedAt);
return account.lastFeedsUpdatedAt
? this.formatDate(account.lastFeedsUpdatedAt)
: '';
}
/**
@@ -57,6 +59,8 @@ export class CashflowAccountTransformer extends Transformer {
* @returns {string}
*/
protected lastFeedsUpdatedFromNow(account: Account): string {
return this.formatDateFromNow(account.lastFeedsUpdatedAt);
return account.lastFeedsUpdatedAt
? this.formatDateFromNow(account.lastFeedsUpdatedAt)
: '';
}
}

View File

@@ -29,7 +29,7 @@ export class GetBankAccountsService {
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
BankAccount,
this.bankAccountModel,
filter,
);
// Retrieve accounts model based on the given query.

View File

@@ -164,7 +164,7 @@ export class Bill extends BaseModel {
* Adjustment amount in local currency.
* @returns {number | null}
*/
get adjustmentLocal() {
get adjustmentLocal(): number | null {
return this.adjustment ? this.adjustment * this.exchangeRate : null;
}
@@ -172,7 +172,7 @@ export class Bill extends BaseModel {
* Invoice total. (Tax included)
* @returns {number}
*/
get total() {
get total(): number {
const adjustmentAmount = defaultTo(this.adjustment, 0);
return R.compose(
@@ -186,7 +186,7 @@ export class Bill extends BaseModel {
* Invoice total in local currency. (Tax included)
* @returns {number}
*/
get totalLocal() {
get totalLocal(): number {
return this.total * this.exchangeRate;
}

View File

@@ -30,7 +30,7 @@ export class GetBillsService {
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
Bill,
this.billModel,
filter,
);
const { results, pagination } = await this.billModel

View File

@@ -15,8 +15,10 @@ import {
ICreditNotesQueryDTO,
} from './types/CreditNotes.types';
import { PublicRoute } from '../Auth/Jwt.guard';
import { ApiTags } from '@nestjs/swagger';
@Controller('credit-notes')
@ApiTags('credit-notes')
@PublicRoute()
export class CreditNotesController {
/**

View File

@@ -2,18 +2,12 @@ import { DiscountType } from '@/common/types/Discount';
import { BaseModel } from '@/models/Model';
import { Branch } from '@/modules/Branches/models/Branch.model';
import { Customer } from '@/modules/Customers/models/Customer';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { mixin, Model, raw } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/CreditNotes/constants';
// import ModelSearchable from './ModelSearchable';
// import CreditNoteMeta from './CreditNote.Meta';
// import { DiscountType } from '@/interfaces';
export class CreditNote extends BaseModel {
export class CreditNote extends TenantBaseModel {
public amount: number;
public exchangeRate: number;
public openedAt: Date;

View File

@@ -39,7 +39,7 @@ export class GetCreditNotesService {
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
CreditNote,
this.creditNoteModel,
filter,
);
const { results, pagination } = await this.creditNoteModel
@@ -47,8 +47,9 @@ export class GetCreditNotesService {
.onBuild((builder) => {
builder.withGraphFetched('entries.item');
builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder);
creditNotesQuery?.filterQuery && creditNotesQuery?.filterQuery(builder);
creditNotesQuery?.filterQuery?.(builder as any);
})
.pagination(filter.page - 1, filter.pageSize);

View File

@@ -5,6 +5,7 @@ import { AttachmentLinkDTO } from '@/modules/Attachments/Attachments.types';
import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types';
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
export interface ICreditNoteEntryNewDTO extends IItemEntryDTO {}
@@ -97,6 +98,7 @@ export interface ICreditNotesQueryDTO extends IDynamicListFilter {
page: number;
pageSize: number;
searchKeyword?: string;
filterQuery?: (builder: Knex.QueryBuilder) => void;
}
export interface ICreditNoteRefundDTO {
@@ -109,22 +111,22 @@ export interface ICreditNoteRefundDTO {
branchId?: number;
}
// export type ICreditNoteGLCommonEntry = Pick<
// ILedgerEntry,
// | 'date'
// | 'userId'
// | 'currencyCode'
// | 'exchangeRate'
// | 'transactionType'
// | 'transactionId'
// | 'transactionNumber'
// | 'referenceNumber'
// | 'createdAt'
// | 'indexGroup'
// | 'credit'
// | 'debit'
// | 'branchId'
// >;
export type ICreditNoteGLCommonEntry = Pick<
ILedgerEntry,
| 'date'
| 'userId'
| 'currencyCode'
| 'exchangeRate'
| 'transactionType'
| 'transactionId'
| 'transactionNumber'
| 'referenceNumber'
| 'createdAt'
| 'indexGroup'
| 'credit'
| 'debit'
| 'branchId'
>;
export interface GetCreditNotesResponse {
creditNotes: CreditNote[];

View File

@@ -1,8 +1,17 @@
import { BaseModel } from "@/models/Model";
;
import { BaseModel } from '@/models/Model';
import { IView } from '../Views/Views.types';
type GConstructor<T = {}> = new (...args: any[]) => T;
export const CustomViewBaseModelMixin = <T extends GConstructor<BaseModel>>(Model: T) =>
export interface ICustomViewBaseModel {
defaultViews: IView[];
getDefaultViewBySlug(viewSlug: string): IView | null;
getDefaultViews(): IView[];
}
export const CustomViewBaseModelMixin = <T extends GConstructor<BaseModel>>(
Model: T,
) =>
class CustomViewBaseModel extends Model {
/**
* Retrieve the default custom views, roles and columns.
@@ -18,6 +27,10 @@ export const CustomViewBaseModelMixin = <T extends GConstructor<BaseModel>>(Mode
return this.defaultViews.find((view) => view.slug === viewSlug) || null;
}
/**
* Retrieve the default views.
* @returns {IView[]}
*/
static getDefaultViews() {
return this.defaultViews;
}

View File

@@ -1,5 +1,4 @@
import { Model, mixin } from 'objection';
import { BaseModel } from '@/models/Model';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
// import TenantModel from 'models/TenantModel';
// import PaginationQueryBuilder from './Pagination';
// import ModelSetting from './ModelSetting';
@@ -20,7 +19,7 @@ import { BaseModel } from '@/models/Model';
// }
// }
export class Customer extends BaseModel{
export class Customer extends TenantBaseModel{
contactService: string;
contactType: string;

View File

@@ -38,7 +38,7 @@ export class GetCustomers {
const filter = this.parseCustomersListFilterDTO(filterDTO);
const dynamicList = await this.dynamicListService.dynamicList(
Customer,
this.customerModel,
filter,
);
const { results, pagination } = await this.customerModel

View File

@@ -1,15 +1,18 @@
import { forEach } from 'lodash';
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
import { IDynamicFilter, IFilterRole } from './DynamicFilter.types';
import { BaseModel } from '@/models/Model';
import { IFilterRole } from './DynamicFilter.types';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
import { MetableModel } from '../types/DynamicList.types';
export class DynamicFilter<R extends {}> extends DynamicFilterAbstractor {
public model: MetableModel;
public dynamicFilters: DynamicFilterRoleAbstractor[];
export class DynamicFilter extends DynamicFilterAbstractor {
/**
* Constructor.
* @param {String} tableName -
* @param {MetableModel} model - Metable model.
*/
constructor(model: typeof BaseModel) {
constructor(model: MetableModel) {
super();
this.model = model;
@@ -29,7 +32,7 @@ export class DynamicFilter extends DynamicFilterAbstractor {
/**
* Retrieve dynamic filter build queries.
* @returns
* @returns {Function[]}
*/
private dynamicFiltersBuildQuery = () => {
return this.dynamicFilters.map((filter) => {
@@ -72,16 +75,16 @@ export class DynamicFilter extends DynamicFilterAbstractor {
/**
* Retrieve response metadata from all filters adapters.
*/
public getResponseMeta = () => {
public getResponseMeta = (): R => {
const responseMeta = {};
this.dynamicFilters.forEach((filter) => {
const { responseMeta: filterMeta } = filter;
const filterMeta = filter.getResponseMeta();
forEach(filterMeta, (value, key) => {
responseMeta[key] = value;
});
});
return responseMeta;
return responseMeta as R;
};
}

View File

@@ -19,7 +19,7 @@ export interface IDynamicListFilter {
customViewId?: number;
filterRoles?: IFilterRole[];
columnSortBy: ISortOrder;
sortOrder: string;
sortOrder: ISortOrder;
stringifiedFilterRoles?: string;
searchKeyword?: string;
viewSlug?: string;

View File

@@ -1,13 +1,13 @@
import { BaseModel } from '@/models/Model';
import { IDynamicFilter } from './DynamicFilter.types';
import { MetableModel } from '../types/DynamicList.types';
export class DynamicFilterAbstractor {
public model: typeof BaseModel;
public model: MetableModel;
public dynamicFilters: IDynamicFilter[];
/**
* Extract relation table name from relation.
* @param {String} column -
* @param {String} column - Column name
* @return {String} - join relation table.
*/
protected getTableFromRelationColumn = (column: string) => {

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
import { OPERATION } from '@/libs/logic-evaluation/Parser';
export default class QueryParser {
export class DynamicFilterQueryParser {
constructor(tree, queries) {
this.tree = tree;
this.queries = queries;

View File

@@ -1,27 +1,26 @@
import moment from 'moment';
import * as R from 'ramda';
import { IFilterRole, IDynamicFilter } from './DynamicFilter.types';
import Parser from '@/libs/logic-evaluation/Parser';
import { Parser } from '@/libs/logic-evaluation/Parser';
import { Lexer } from '@/libs/logic-evaluation/Lexer';
import DynamicFilterQueryParser from './DynamicFilterQueryParser';
import { DynamicFilterQueryParser } from './DynamicFilterQueryParser';
import { COMPARATOR_TYPE, FIELD_TYPE } from './constants';
import { BaseModel } from '@/models/Model';
import { IMetadataModel } from '../models/MetadataModel';
type MetadataModel = typeof BaseModel & IMetadataModel;
import { MetableModel } from '../types/DynamicList.types';
import { Knex } from 'knex';
export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
protected filterRoles: IFilterRole[] = [];
protected tableName: string;
protected model: MetadataModel;
protected responseMeta: { [key: string]: any } = {};
public filterRoles: IFilterRole[] = [];
public tableName: string;
public model: MetableModel;
public responseMeta: { [key: string]: any } = {};
public relationFields = [];
/**
* Sets model the dynamic filter service.
* @param {IModel} model
*/
public setModel(model: MetadataModel) {
public setModel(model: MetableModel) {
this.model = model;
this.tableName = model.tableName;
}
@@ -118,7 +117,7 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
* @param {IModel} model -
* @param {} -
*/
private getFieldComparatorColumn = (field) => {
protected getFieldComparatorColumn = (field) => {
return field.fieldType === FIELD_TYPE.RELATION
? this.getFieldComparatorRelationColumn(field)
: `${this.tableName}.${field.column}`;
@@ -129,7 +128,7 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
* @param {IModel} model -
* @param {Object} role -
*/
protected buildRoleQuery = (model: MetadataModel, role: IFilterRole) => {
protected buildRoleQuery = (model: MetableModel, role: IFilterRole) => {
const field = model.getField(role.fieldKey);
const comparatorColumn = this.getFieldComparatorColumn(field);
@@ -384,10 +383,17 @@ export abstract class DynamicFilterRoleAbstractor implements IDynamicFilter {
*/
onInitialize() {}
buildQuery(): void {
/**
* Builds the query.
*/
buildQuery(): (builder: Knex.QueryBuilder) => void {
throw new Error('Method not implemented.');
}
/**
* Retrieves the response meta.
*/
getResponseMeta() {
throw new Error('Method not implemented.');
}
}
}

View File

@@ -1,6 +1,10 @@
import { IFilterRole } from './DynamicFilter.types';
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
export interface IDynamicFilterSearchResponseMeta {
searchKeyword: string;
}
export class DynamicFilterSearch extends DynamicFilterFilterRoles {
private searchKeyword: string;
@@ -23,7 +27,7 @@ export class DynamicFilterSearch extends DynamicFilterFilterRoles {
/**
* Retrieve the filter roles from model search roles.
* @param {string} searchKeyword
* @param {string} searchKeyword
* @returns {IFilterRole[]}
*/
private getModelSearchFilterRoles(searchKeyword: string): IFilterRole[] {
@@ -37,11 +41,21 @@ export class DynamicFilterSearch extends DynamicFilterFilterRoles {
}
/**
*
* Sets the response meta.
*/
setResponseMeta() {
this.responseMeta = {
searchKeyword: this.searchKeyword,
};
}
/**
* Retrieves the response meta.
* @returns {IDynamicFilterSearchResponseMeta}
*/
public getResponseMeta(): IDynamicFilterSearchResponseMeta {
return {
searchKeyword: this.searchKeyword,
};
}
}

View File

@@ -1,5 +1,4 @@
import { FIELD_TYPE } from './constants';
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
interface ISortRole {
@@ -8,7 +7,10 @@ interface ISortRole {
}
export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
private sortRole: ISortRole = {};
private sortRole: ISortRole = {
fieldKey: '',
order: '',
};
/**
* Constructor method.
@@ -22,7 +24,6 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
fieldKey: sortByFieldKey,
order: sortDirection,
};
this.setResponseMeta();
}
/**
@@ -54,7 +55,7 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
* @param {IModel} field
* @returns {string}
*/
private getFieldComparatorColumn = (field): string => {
getFieldComparatorColumn = (field) => {
return field.fieldType === FIELD_TYPE.RELATION
? this.getFieldComparatorRelationColumn(field)
: `${this.tableName}.${field.column}`;
@@ -84,10 +85,10 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
/**
* Sets response meta.
*/
public setResponseMeta() {
this.responseMeta = {
sortOrder: this.sortRole.fieldKey,
sortBy: this.sortRole.order,
public getResponseMeta(): ISortRole {
return {
fieldKey: this.sortRole.fieldKey,
order: this.sortRole.order,
};
}
}

View File

@@ -1,5 +1,6 @@
import { omit } from 'lodash';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
import { IView } from '@/modules/Views/Views.types';
export class DynamicFilterViews extends DynamicFilterRoleAbstractor {
private viewSlug: string;

View File

@@ -6,7 +6,8 @@ import { DynamicListCustomView } from './DynamicListCustomView.service';
import { Injectable } from '@nestjs/common';
import { DynamicListFilterRoles } from './DynamicListFilterRoles.service';
import { DynamicFilter } from './DynamicFilter';
import { BaseModel } from '@/models/Model';
import { MetableModel } from './types/DynamicList.types';
import { IFilterMeta } from '@/interfaces/Model';
@Injectable()
export class DynamicListService {
@@ -19,10 +20,13 @@ export class DynamicListService {
/**
* Parses filter DTO.
* @param {IMode} model -
* @param {} filterDTO -
* @param {MetableModel} model - Metable model.
* @param {IDynamicListFilter} filterDTO - Dynamic list filter DTO.
*/
private parseFilterObject = (model, filterDTO) => {
private parseFilterObject = (
model: MetableModel,
filterDTO: IDynamicListFilter,
) => {
return {
// Merges the default properties with filter object.
...(model.defaultSort
@@ -37,15 +41,14 @@ export class DynamicListService {
/**
* Dynamic listing.
* @param {number} tenantId - Tenant id.
* @param {IModel} model - Model.
* @param {IModel} model - Metable model.
* @param {IDynamicListFilter} filter - Dynamic filter DTO.
*/
public dynamicList = async (
model: typeof BaseModel,
model: MetableModel,
filter: IDynamicListFilter,
) => {
const dynamicFilter = new DynamicFilter(model);
const dynamicFilter = new DynamicFilter<IFilterMeta>(model);
// Parses the filter object.
const parsedFilter = this.parseFilterObject(model, filter);
@@ -99,5 +102,5 @@ export class DynamicListService {
? castArray(JSON.parse(filterRoles.stringifiedFilterRoles))
: [],
};
};
}
}

View File

@@ -2,21 +2,22 @@ import { Injectable } from '@nestjs/common';
import { ERRORS } from './constants';
import { DynamicFilterViews } from './DynamicFilter';
import { ServiceError } from '../Items/ServiceError';
import { BaseModel } from '@/models/Model';
import { DynamicListServiceAbstract } from './DynamicListServiceAbstract';
import { IView } from '../Views/Views.types';
import { MetableModel } from './types/DynamicList.types';
@Injectable()
export class DynamicListCustomView extends DynamicListServiceAbstract {
/**
* Retreive custom view or throws error not found.
* @param {number} tenantId
* @param {number} viewId
* @param {string} viewSlug - View slug.
* @param {MetableModel} model - Metable model.
* @return {Promise<IView>}
*/
private getCustomViewOrThrowError = async (
private async getCustomViewOrThrowError(
viewSlug: string,
model: BaseModel,
) => {
model: MetableModel,
): Promise<IView> {
// Finds the default view by the given view slug.
const defaultView = model.getDefaultViewBySlug(viewSlug);
@@ -24,12 +25,12 @@ export class DynamicListCustomView extends DynamicListServiceAbstract {
throw new ServiceError(ERRORS.VIEW_NOT_FOUND);
}
return defaultView;
};
}
/**
* Dynamic list custom view.
* @param {IModel} model
* @param {number} customViewId
* @param {DynamicFilter} dynamicFilter - Dynamic filter.
* @param {string} customViewSlug - Custom view slug.
* @returns {DynamicFilterRoleAbstractor}
*/
public dynamicListCustomView = async (

View File

@@ -3,10 +3,10 @@ import { Injectable } from '@nestjs/common';
import validator from 'is-my-json-valid';
import { IFilterRole } from './DynamicFilter/DynamicFilter.types';
import { DynamicFilterAdvancedFilter } from './DynamicFilter/DynamicFilterAdvancedFilter';
import { ERRORS } from './constants';
import { ServiceError } from '../Items/ServiceError';
import { BaseModel } from '@/models/Model';
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
import { MetableModel } from './types/DynamicList.types';
import { ServiceError } from '../Items/ServiceError';
import { ERRORS } from './constants';
@Injectable()
export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
@@ -34,12 +34,12 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
/**
* Retrieve filter roles fields key that not exists on the given model.
* @param {BaseModel} model
* @param {MetableModel} model
* @param {IFilterRole} filterRoles
* @returns {string[]}
*/
private getFilterRolesFieldsNotExist = (
model: BaseModel,
model: MetableModel,
filterRoles: IFilterRole[],
): string[] => {
return filterRoles
@@ -49,12 +49,12 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
/**
* Validates existance the fields of filter roles.
* @param {BaseModel} model
* @param {MetableModel} model
* @param {IFilterRole[]} filterRoles
* @throws {ServiceError}
*/
private validateFilterRolesFieldsExistance = (
model: BaseModel,
model: MetableModel,
filterRoles: IFilterRole[],
) => {
const invalidFieldsKeys = this.getFilterRolesFieldsNotExist(
@@ -82,12 +82,12 @@ export class DynamicListFilterRoles extends DynamicFilterRoleAbstractor {
/**
* Dynamic list filter roles.
* @param {BaseModel} model
* @param {IFilterRole[]} filterRoles
* @param {MetableModel} model - Metable model.
* @param {IFilterRole[]} filterRoles - Filter roles.
* @returns {DynamicFilterFilterRoles}
*/
public dynamicList = (
model: BaseModel,
model: MetableModel,
filterRoles: IFilterRole[],
): DynamicFilterAdvancedFilter => {
const filterRolesParsed = R.compose(this.incrementFilterRolesIndex)(

View File

@@ -4,10 +4,11 @@ import { ERRORS } from './constants';
import { DynamicFilterSortBy } from './DynamicFilter';
import { ServiceError } from '../Items/ServiceError';
import { BaseModel } from '@/models/Model';
import { DynamicFilterRoleAbstractor } from './DynamicFilter/DynamicFilterRoleAbstractor';
import { DynamicFilterAbstractor } from './DynamicFilter/DynamicFilterAbstractor';
import { MetableModel } from './types/DynamicList.types';
@Injectable()
export class DynamicListSortBy extends DynamicFilterRoleAbstractor {
export class DynamicListSortBy extends DynamicFilterAbstractor {
/**
* Dynamic list sort by.
* @param {BaseModel} model
@@ -16,7 +17,7 @@ export class DynamicListSortBy extends DynamicFilterRoleAbstractor {
* @returns {DynamicFilterSortBy}
*/
public dynamicSortBy(
model: BaseModel,
model: MetableModel,
columnSortBy: string,
sortOrder: ISortOrder,
) {

View File

@@ -11,7 +11,7 @@ const defaultModelMeta = {
fields2: {},
};
export interface IMetadataModel extends BaseModel {
export interface IMetadataModel {
meta: IModelMeta;
parsedMeta: IModelMeta;
fields: { [key: string]: IModelMetaField };

View File

@@ -1,9 +1,13 @@
import { BaseModel } from '@/models/Model';
import { IModelMeta } from '@/interfaces/Model';
import { ISearchRole } from '../DynamicFilter.types';
import { ISearchRole } from '../DynamicFilter/DynamicFilter.types';
type GConstructor<T = {}> = new (...args: any[]) => T;
export interface ISearchableBaseModel {
searchRoles: ISearchRole[];
}
export const SearchableBaseModelMixin = <T extends GConstructor<BaseModel>>(
Model: T,
) =>
@@ -11,7 +15,7 @@ export const SearchableBaseModelMixin = <T extends GConstructor<BaseModel>>(
/**
* Searchable model.
*/
static get searchable(): IModelMeta {
static get searchable(): boolean {
throw true;
}

View File

@@ -1,5 +1,9 @@
import { ISortOrder } from '@/interfaces/Model';
import { BaseModel } from '@/models/Model';
import { ICustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
import { IFilterRole } from '../DynamicFilter/DynamicFilter.types';
import { IMetadataModel } from '../models/MetadataModel';
import { ISearchableBaseModel } from '../models/SearchableBaseModel';
export interface IDynamicListFilter {
customViewId?: number;
@@ -8,4 +12,9 @@ export interface IDynamicListFilter {
sortOrder: string;
stringifiedFilterRoles: string;
searchKeyword?: string;
}
}
export type MetableModel = typeof BaseModel &
IMetadataModel &
ISearchableBaseModel &
ICustomViewBaseModel;

View File

@@ -1,22 +1,10 @@
import { Model, mixin, raw } from 'objection';
// import TenantModel from 'models/TenantModel';
// import { viewRolesBuilder } from '@/lib/ViewRolesBuilder';
// import ModelSetting from './ModelSetting';
// import ExpenseSettings from './Expense.Settings';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Expenses/constants';
// import ModelSearchable from './ModelSearchable';
import { Model, raw } from 'objection';
import moment from 'moment';
import { BaseModel } from '@/models/Model';
import { ExpenseCategory } from './ExpenseCategory.model';
import { Account } from '@/modules/Accounts/models/Account.model';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class Expense extends BaseModel {
// ModelSetting,
// CustomViewBaseModel,
// ModelSearchable,
// ]) {
export class Expense extends TenantBaseModel {
totalAmount!: number;
currencyCode!: string;
exchangeRate!: number;

View File

@@ -1,154 +0,0 @@
// import { Inject, Service } from 'typedi';
// import { omit } from 'lodash';
// import moment from 'moment';
// import * as R from 'ramda';
// import { Knex } from 'knex';
// import { ServiceError } from '@/exceptions';
// import {
// IInventoryAdjustment,
// IPaginationMeta,
// IInventoryAdjustmentsFilter,
// IInventoryTransaction,
// IInventoryAdjustmentEventPublishedPayload,
// IInventoryAdjustmentEventDeletedPayload,
// IInventoryAdjustmentDeletingPayload,
// IInventoryAdjustmentPublishingPayload,
// } from '@/interfaces';
// import events from '@/subscribers/events';
// import DynamicListingService from '@/services/DynamicListing/DynamicListService';
// import HasTenancyService from '@/services/Tenancy/TenancyService';
// import InventoryService from './Inventory';
// import UnitOfWork from '@/services/UnitOfWork';
// import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
// import InventoryAdjustmentTransformer from './InventoryAdjustmentTransformer';
// import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
// import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
// import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
// const ERRORS = {
// INVENTORY_ADJUSTMENT_NOT_FOUND: 'INVENTORY_ADJUSTMENT_NOT_FOUND',
// ITEM_SHOULD_BE_INVENTORY_TYPE: 'ITEM_SHOULD_BE_INVENTORY_TYPE',
// INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED:
// 'INVENTORY_ADJUSTMENT_ALREADY_PUBLISHED',
// };
// @Service()
// export default class InventoryAdjustmentService {
// @Inject()
// private tenancy: HasTenancyService;
// @Inject()
// private eventPublisher: EventPublisher;
// @Inject()
// private inventoryService: InventoryService;
// @Inject()
// private dynamicListService: DynamicListingService;
// @Inject()
// private uow: UnitOfWork;
// @Inject()
// private branchDTOTransform: BranchTransactionDTOTransform;
// @Inject()
// private warehouseDTOTransform: WarehouseTransactionDTOTransform;
// @Inject()
// private transfromer: TransformerInjectable;
// /**
// * Retrieve the inventory adjustment or throw not found service error.
// * @param {number} tenantId -
// * @param {number} adjustmentId -
// */
// async getInventoryAdjustmentOrThrowError(
// tenantId: number,
// adjustmentId: number
// ) {
// const { InventoryAdjustment } = this.tenancy.models(tenantId);
// const inventoryAdjustment = await InventoryAdjustment.query()
// .findById(adjustmentId)
// .withGraphFetched('entries');
// if (!inventoryAdjustment) {
// throw new ServiceError(ERRORS.INVENTORY_ADJUSTMENT_NOT_FOUND);
// }
// return inventoryAdjustment;
// }
// /**
// * Parses inventory adjustments list filter DTO.
// * @param filterDTO -
// */
// private parseListFilterDTO(filterDTO) {
// return R.compose(this.dynamicListService.parseStringifiedFilter)(filterDTO);
// }
// /**
// * 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(
// tenantId: number,
// inventoryAdjustment: IInventoryAdjustment,
// 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(
// tenantId,
// inventoryTransactions,
// override,
// trx
// );
// }
// /**
// * Reverts the inventory transactions from the inventory adjustment transaction.
// * @param {number} tenantId
// * @param {number} inventoryAdjustmentId
// */
// async revertInventoryTransactions(
// tenantId: number,
// inventoryAdjustmentId: number,
// trx?: Knex.Transaction
// ): Promise<{ oldInventoryTransactions: IInventoryTransaction[] }> {
// return this.inventoryService.deleteInventoryTransactions(
// tenantId,
// inventoryAdjustmentId,
// 'InventoryAdjustment',
// trx
// );
// }
// }

View File

@@ -16,8 +16,10 @@ import {
import { InventoryAdjustment } from './models/InventoryAdjustment';
import { PublicRoute } from '../Auth/Jwt.guard';
import { IPaginationMeta } from '@/interfaces/Model';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller('inventory-adjustments')
@ApiTags('inventory-adjustments')
@PublicRoute()
export class InventoryAdjustmentsController {
constructor(
@@ -25,6 +27,7 @@ export class InventoryAdjustmentsController {
) {}
@Post('quick')
@ApiOperation({ summary: 'Create a quick inventory adjustment.' })
public async createQuickInventoryAdjustment(
@Body() quickAdjustmentDTO: IQuickInventoryAdjustmentDTO,
): Promise<InventoryAdjustment> {
@@ -34,6 +37,7 @@ export class InventoryAdjustmentsController {
}
@Delete(':id')
@ApiOperation({ summary: 'Delete the given inventory adjustment.' })
public async deleteInventoryAdjustment(
@Param('id') inventoryAdjustmentId: number,
): Promise<void> {
@@ -43,6 +47,7 @@ export class InventoryAdjustmentsController {
}
@Get()
@ApiOperation({ summary: 'Retrieves the inventory adjustments.' })
public async getInventoryAdjustments(
@Query() filterDTO: IInventoryAdjustmentsFilter,
): Promise<{
@@ -55,6 +60,7 @@ export class InventoryAdjustmentsController {
}
@Get(':id')
@ApiOperation({ summary: 'Retrieves the inventory adjustment details.' })
public async getInventoryAdjustment(
@Param('id') inventoryAdjustmentId: number,
): Promise<InventoryAdjustment> {
@@ -64,6 +70,7 @@ export class InventoryAdjustmentsController {
}
@Put(':id/publish')
@ApiOperation({ summary: 'Publish the given inventory adjustment.' })
public async publishInventoryAdjustment(
@Param('id') inventoryAdjustmentId: number,
): Promise<void> {

View File

@@ -1,11 +1,8 @@
import { Model } from 'objection';
// import TenantModel from 'models/TenantModel';
// import InventoryAdjustmentSettings from './InventoryAdjustment.Settings';
// import ModelSetting from './ModelSetting';
import { BaseModel } from '@/models/Model';
import { InventoryAdjustmentEntry } from './InventoryAdjustmentEntry';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class InventoryAdjustment extends BaseModel {
export class InventoryAdjustment extends TenantBaseModel {
date!: string;
type!: string;
adjustmentAccountId!: number;
@@ -32,28 +29,28 @@ export class InventoryAdjustment extends BaseModel {
/**
* Timestamps columns.
*/
get timestamps() {
get timestamps(): Array<string> {
return ['created_at'];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
static get virtualAttributes(): Array<string> {
return ['formattedType', 'inventoryDirection', 'isPublished'];
}
/**
* Retrieve formatted adjustment type.
*/
get formattedType() {
get formattedType(): string {
return InventoryAdjustment.getFormattedType(this.type);
}
/**
* Retrieve formatted reference type.
*/
get inventoryDirection() {
get inventoryDirection(): string {
return InventoryAdjustment.getInventoryDirection(this.type);
}
@@ -61,7 +58,7 @@ export class InventoryAdjustment extends BaseModel {
* Detarmines whether the adjustment is published.
* @return {boolean}
*/
get isPublished() {
get isPublished(): boolean {
return !!this.publishedAt;
}

View File

@@ -31,7 +31,7 @@ export class GetInventoryAdjustmentsService {
// Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList(
InventoryAdjustment,
this.inventoryAdjustmentModel,
filter,
);
const { results, pagination } = await this.inventoryAdjustmentModel

View File

@@ -27,12 +27,8 @@ import InventoryCostMethod from './InventoryCostMethod';
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,
@@ -340,7 +336,7 @@ export class InventoryService {
/**
* Mark item cost computing is running.
* @param {boolean} isRunning -
* @param {boolean} isRunning -
*/
async markItemsCostComputeRunning(isRunning: boolean = true) {
this.settings.set({

View File

@@ -1,9 +1,11 @@
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';
import { InventoryCostGLBeforeWriteSubscriber } from './subscribers/InventoryCostGLBeforeWriteSubscriber';
import { InventoryItemsQuantitySyncService } from './InventoryItemsQuantitySync.service';
import { InventoryCostMethod } from './InventoryCostMethod';
const models = [
RegisterTenancyModel(InventoryCostLotTracker),
@@ -15,6 +17,8 @@ const models = [
...models,
InventoryCostGLBeforeWriteSubscriber,
InventoryCostGLStorage,
InventoryItemsQuantitySyncService,
InventoryCostMethod,
],
exports: [...models],
})

View File

@@ -1,17 +1,17 @@
import { IInventoryItemCostMeta } from '@/interfaces';
import { Service, Inject } from 'typedi';
import { Injectable } from '@nestjs/common';
import { InventoryItemCostService } from './InventoryCosts.service';
import { IInventoryItemCostMeta } from './types/InventoryCost.types';
@Service()
@Injectable()
export class InventoryCostApplication {
@Inject()
inventoryCost: InventoryItemCostService;
constructor(
private readonly inventoryCost: InventoryItemCostService,
) {}
/**
* Retrieves the items inventory valuation list.
* @param {number} tenantId
* @param {number[]} itemsId
* @param {Date} date
* @param {number[]} itemsId
* @param {Date} date
* @returns {Promise<IInventoryItemCostMeta[]>}
*/
public getItemsInventoryValuationList = async (
@@ -19,7 +19,6 @@ export class InventoryCostApplication {
date: Date
): Promise<IInventoryItemCostMeta[]> => {
const itemsMap = await this.inventoryCost.getItemsInventoryValuation(
tenantId,
itemsId,
date
);

View File

@@ -1,21 +1,14 @@
import { omit } from 'lodash';
import { Container } from 'typedi';
import TenancyService from '@/services/Tenancy/TenancyService';
import { IInventoryLotCost } from '@/interfaces';
import { InventoryCostLotTracker } from './models/InventoryCostLotTracker';
import { Inject } from '@nestjs/common';
import { Knex } from 'knex';
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);
}
export class InventoryCostMethod {
constructor(
@Inject(InventoryCostLotTracker.name)
private readonly inventoryCostLotTracker: typeof InventoryCostLotTracker
) {}
/**
* Stores the inventory lots costs transactions in bulk.
@@ -23,19 +16,19 @@ export default class InventoryCostMethod {
* @return {Promise[]}
*/
public storeInventoryLotsCost(
costLotsTransactions: IInventoryLotCost[]
costLotsTransactions: InventoryCostLotTracker[],
trx: Knex.Transaction
): Promise<object> {
const { InventoryCostLotTracker } = this.tenantModels;
const opers: any = [];
costLotsTransactions.forEach((transaction: any) => {
if (transaction.lotTransId && transaction.decrement) {
const decrementOper = InventoryCostLotTracker.query(this.trx)
const decrementOper = this.inventoryCostLotTracker.query(trx)
.where('id', transaction.lotTransId)
.decrement('remaining', transaction.decrement);
opers.push(decrementOper);
} else if (!transaction.lotTransId) {
const operation = InventoryCostLotTracker.query(this.trx).insert({
const operation = this.inventoryCostLotTracker.query(trx).insert({
...omit(transaction, ['decrement', 'invTransId', 'lotTransId']),
});
opers.push(operation);

View File

@@ -1,9 +1,10 @@
import { toSafeInteger } from 'lodash';
import { IInventoryTransaction, IItemsQuantityChanges } from '@/interfaces';
import { IItemsQuantityChanges } from './types/InventoryCost.types';
import { Knex } from 'knex';
import { Inject } from '@nestjs/common';
import { Item } from '../Items/models/Item';
import { Injectable } from '@nestjs/common';
import { InventoryTransaction } from './models/InventoryTransaction';
/**
* Syncs the inventory transactions with inventory items quantity.
@@ -11,8 +12,7 @@ import { Injectable } from '@nestjs/common';
@Injectable()
export class InventoryItemsQuantitySyncService {
constructor(
@Inject(Item.name)
private readonly itemModel: typeof Item,
@Inject(Item.name) private readonly itemModel: typeof Item,
) {}
/**
@@ -21,12 +21,14 @@ export class InventoryItemsQuantitySyncService {
* @return {IInventoryTransaction[]}
*/
public reverseInventoryTransactions(
inventroyTransactions: IInventoryTransaction[],
): IInventoryTransaction[] {
return inventroyTransactions.map((transaction) => ({
...transaction,
direction: transaction.direction === 'OUT' ? 'IN' : 'OUT',
}));
inventroyTransactions: InventoryTransaction[],
): InventoryTransaction[] {
return inventroyTransactions.map((transaction) => {
const cloned = transaction.$clone();
cloned.direction = cloned.direction === 'OUT' ? 'IN' : 'OUT';
return cloned;
});
}
/**
@@ -35,7 +37,7 @@ export class InventoryItemsQuantitySyncService {
* @return {IItemsQuantityChanges[]}
*/
public getReverseItemsQuantityChanges(
inventroyTransactions: IInventoryTransaction[],
inventroyTransactions: InventoryTransaction[],
): IItemsQuantityChanges[] {
const reversedTransactions = this.reverseInventoryTransactions(
inventroyTransactions,
@@ -49,12 +51,12 @@ export class InventoryItemsQuantitySyncService {
* @return {IItemsQuantityChanges[]}
*/
public getItemsQuantityChanges(
inventroyTransactions: IInventoryTransaction[],
inventroyTransactions: InventoryTransaction[],
): IItemsQuantityChanges[] {
const balanceMap: { [itemId: number]: number } = {};
inventroyTransactions.forEach(
(inventoryTransaction: IInventoryTransaction) => {
(inventoryTransaction: InventoryTransaction) => {
const { itemId, direction, quantity } = inventoryTransaction;
if (!balanceMap[itemId]) {

View File

@@ -55,7 +55,16 @@ export class InventoryCostLotTracker extends BaseModel {
query.groupBy('date');
query.groupBy('item_id');
},
filterDateRange(query, startDate, endDate, type: unitOfTime.StartOf = 'day') {
/**
* Filters transactions by the given date range.
*/
filterDateRange(
query,
startDate,
endDate,
type: unitOfTime.StartOf = 'day',
) {
const dateFormat = 'YYYY-MM-DD';
const fromDate = moment(startDate).startOf(type).format(dateFormat);
const toDate = moment(endDate).endOf(type).format(dateFormat);
@@ -71,7 +80,7 @@ export class InventoryCostLotTracker extends BaseModel {
/**
* Filters transactions by the given branches.
*/
filterByBranches(query, branchesIds) {
filterByBranches(query, branchesIds: number | Array<number>) {
const formattedBranchesIds = castArray(branchesIds);
query.whereIn('branchId', formattedBranchesIds);
@@ -80,7 +89,7 @@ export class InventoryCostLotTracker extends BaseModel {
/**
* Filters transactions by the given warehosues.
*/
filterByWarehouses(query, branchesIds) {
filterByWarehouses(query, branchesIds: number | Array<number>) {
const formattedWarehousesIds = castArray(branchesIds);
query.whereIn('warehouseId', formattedWarehousesIds);
@@ -92,15 +101,17 @@ export class InventoryCostLotTracker extends BaseModel {
* 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');
const { Item } = require('../../Items/models/Item');
const { SaleInvoice } = require('../../SaleInvoices/models/SaleInvoice');
const {
ItemEntry,
} = require('../../TransactionItemEntry/models/ItemEntry');
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
return {
item: {
relation: Model.BelongsToOneRelation,
modelClass: Item.default,
modelClass: Item,
join: {
from: 'inventory_cost_lot_tracker.itemId',
to: 'items.id',
@@ -108,7 +119,7 @@ export class InventoryCostLotTracker extends BaseModel {
},
invoice: {
relation: Model.BelongsToOneRelation,
modelClass: SaleInvoice.default,
modelClass: SaleInvoice,
join: {
from: 'inventory_cost_lot_tracker.transactionId',
to: 'sales_invoices.id',
@@ -116,7 +127,7 @@ export class InventoryCostLotTracker extends BaseModel {
},
itemEntry: {
relation: Model.BelongsToOneRelation,
modelClass: ItemEntry.default,
modelClass: ItemEntry,
join: {
from: 'inventory_cost_lot_tracker.entryId',
to: 'items_entries.id',
@@ -124,7 +135,7 @@ export class InventoryCostLotTracker extends BaseModel {
},
receipt: {
relation: Model.BelongsToOneRelation,
modelClass: SaleReceipt.default,
modelClass: SaleReceipt,
join: {
from: 'inventory_cost_lot_tracker.transactionId',
to: 'sales_receipts.id',

View File

@@ -106,10 +106,12 @@ export class InventoryTransaction extends BaseModel {
* 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');
const { Item } = require('../../Items/models/Item');
const {
ItemEntry,
} = require('../../TransactionItemEntry/models/ItemEntry');
const { InventoryTransactionMeta } = require('./InventoryTransactionMeta');
const { InventoryCostLotTracker } = require('./InventoryCostLotTracker');
return {
// Transaction meta.
@@ -124,7 +126,7 @@ export class InventoryTransaction extends BaseModel {
// Item cost aggregated.
itemCostAggregated: {
relation: Model.HasOneRelation,
modelClass: InventoryCostLots.default,
modelClass: InventoryCostLotTracker,
join: {
from: 'inventory_transactions.itemId',
to: 'inventory_cost_lot_tracker.itemId',
@@ -138,7 +140,7 @@ export class InventoryTransaction extends BaseModel {
},
costLotAggregated: {
relation: Model.HasOneRelation,
modelClass: InventoryCostLots.default,
modelClass: InventoryCostLotTracker,
join: {
from: 'inventory_transactions.id',
to: 'inventory_cost_lot_tracker.inventoryTransactionId',
@@ -151,7 +153,7 @@ export class InventoryTransaction extends BaseModel {
},
item: {
relation: Model.BelongsToOneRelation,
modelClass: Item.default,
modelClass: Item,
join: {
from: 'inventory_transactions.itemId',
to: 'items.id',
@@ -159,7 +161,7 @@ export class InventoryTransaction extends BaseModel {
},
itemEntry: {
relation: Model.BelongsToOneRelation,
modelClass: ItemEntry.default,
modelClass: ItemEntry,
join: {
from: 'inventory_transactions.entryId',
to: 'items_entries.id',

View File

@@ -0,0 +1,29 @@
import { BaseModel } from '@/models/Model';
import { Model, raw } from 'objection';
export class InventoryTransactionMeta extends BaseModel {
/**
* Table name
*/
static get tableName() {
return 'inventory_transaction_meta';
}
/**
* Relationship mapping.
*/
static get relationMappings() {
const { InventoryTransaction } = require('./InventoryTransaction');
return {
inventoryTransaction: {
relation: Model.BelongsToOneRelation,
modelClass: InventoryTransaction,
join: {
from: 'inventory_transaction_meta.inventoryTransactionId',
to: 'inventory_transactions.inventoryTransactionId'
}
}
};
}
}

View File

@@ -1,10 +1,7 @@
import { BaseModel } from '@/models/Model';
import { Model, mixin } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import ItemCategorySettings from './ItemCategory.Settings';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { Model } from 'objection';
export class ItemCategory extends BaseModel {
export class ItemCategory extends TenantBaseModel {
name!: string;
description!: string;

View File

@@ -1,19 +1,7 @@
import * as R from 'ramda';
import { BaseModel } from '@/models/Model';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
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';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
const ExtendedItem = R.pipe(
CustomViewBaseModelMixin,
SearchableBaseModelMixin,
ResourceableModelMixin,
MetadataModelMixin
)(BaseModel);
export class Item extends ExtendedItem {
export class Item extends TenantBaseModel{
public readonly quantityOnHand: number;
public readonly name: string;
public readonly active: boolean;

View File

@@ -1,8 +1,9 @@
import { Transporter } from 'nodemailer';
import { Mail } from './Mail';
import { Inject } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { MAIL_TRANSPORTER_PROVIDER } from './Mail.constants';
@Injectable()
export class MailTransporter {
constructor(
@Inject(MAIL_TRANSPORTER_PROVIDER)

View File

@@ -4,6 +4,7 @@ import { MailTenancy } from '../MailTenancy/MailTenancy.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { Customer } from '../Customers/models/Customer';
import { CommonMailOptions } from './MailNotification.types';
import { formatMessage } from '@/utils/format-message';
@Injectable()
export class ContactMailNotification {
@@ -56,8 +57,8 @@ export class ContactMailNotification {
...commonFormatArgs,
...formatterArgs,
};
const subjectFormatted = formatSmsMessage(mailOptions?.subject, formatArgs);
const messageFormatted = formatSmsMessage(mailOptions?.message, formatArgs);
const subjectFormatted = formatMessage(mailOptions?.subject, formatArgs);
const messageFormatted = formatMessage(mailOptions?.message, formatArgs);
return {
...mailOptions,

View File

@@ -1,7 +1,7 @@
import { castArray, isEmpty } from 'lodash';
import { ServiceError } from '@/exceptions';
import { CommonMailOptions } from '@/interfaces';
import { ERRORS } from './constants';
import { CommonMailOptions } from './MailNotification.types';
import { ServiceError } from '../Items/ServiceError';
/**
* Merges the mail options with incoming options.

View File

@@ -7,10 +7,10 @@ import { Model, mixin } from 'objection';
// import { DEFAULT_VIEWS } from '@/services/ManualJournals/constants';
// import ModelSearchable from './ModelSearchable';
import { ManualJournalEntry } from './ManualJournalEntry';
import { BaseModel } from '@/models/Model';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class ManualJournal extends BaseModel {
export class ManualJournal extends TenantBaseModel {
date: Date;
journalNumber: string;
journalType: string;

View File

@@ -41,7 +41,7 @@ export class GetManualJournals {
// Dynamic service.
const dynamicService = await this.dynamicListService.dynamicList(
ManualJournal,
this.manualJournalModel,
filter,
);
const { results, pagination } = await this.manualJournalModel

View File

@@ -13,10 +13,13 @@ import { PaymentReceivesApplication } from './PaymentReceived.application';
import {
IPaymentReceivedCreateDTO,
IPaymentReceivedEditDTO,
IPaymentsReceivedFilter,
} from './types/PaymentReceived.types';
import { PublicRoute } from '../Auth/Jwt.guard';
import { ApiTags } from '@nestjs/swagger';
@Controller('payments-received')
@ApiTags('payments-received')
@PublicRoute()
export class PaymentReceivesController {
constructor(private paymentReceivesApplication: PaymentReceivesApplication) {}

View File

@@ -3,7 +3,7 @@ import {
DEFAULT_PAYMENT_MAIL_CONTENT,
DEFAULT_PAYMENT_MAIL_SUBJECT,
} from '../constants';
import { transformPaymentReceivedToMailDataArgs } from './utils';
import { transformPaymentReceivedToMailDataArgs } from '../utils';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification';
@@ -14,16 +14,19 @@ import { PaymentReceiveMailOptsDTO } from '../types/PaymentReceived.types';
import { PaymentReceiveMailOpts } from '../types/PaymentReceived.types';
import { PaymentReceiveMailPresendEvent } from '../types/PaymentReceived.types';
import { SendInvoiceMailDTO } from '@/modules/SaleInvoices/SaleInvoice.types';
import { Mail } from '@/modules/Mail/Mail';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
@Injectable()
export class SendPaymentReceiveMailNotification {
constructor(
private getPaymentService: GetPaymentReceivedService,
private contactMailNotification: ContactMailNotification,
private eventEmitter: EventEmitter2,
private readonly getPaymentService: GetPaymentReceivedService,
private readonly contactMailNotification: ContactMailNotification,
private readonly eventEmitter: EventEmitter2,
private readonly mailTransport: MailTransporter,
@Inject(PaymentReceived.name)
private paymentReceiveModel: typeof PaymentReceived,
private readonly paymentReceiveModel: typeof PaymentReceived,
) {}
/**
@@ -148,7 +151,7 @@ export class SendPaymentReceiveMailNotification {
events.paymentReceive.onMailSend,
eventPayload,
);
await mail.send();
await this.mailTransport.send(mail);
// Triggers `onPaymentReceiveMailSent` event.
await this.eventEmitter.emitAsync(

View File

@@ -1,14 +1,8 @@
import { Model, mixin } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import PaymentReceiveSettings from './PaymentReceive.Settings';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Sales/PaymentReceived/constants';
// import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model';
import { Model } from 'objection';
import { PaymentReceivedEntry } from './PaymentReceivedEntry';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class PaymentReceived extends BaseModel {
export class PaymentReceived extends TenantBaseModel {
customerId: number;
paymentDate: string;
amount: number;
@@ -69,7 +63,9 @@ export class PaymentReceived extends BaseModel {
*/
static get relationMappings() {
const { PaymentReceivedEntry } = require('./PaymentReceivedEntry');
const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
const {
AccountTransaction,
} = require('../../Accounts/models/AccountTransaction.model');
const { Customer } = require('../../Customers/models/Customer');
const { Account } = require('../../Accounts/models/Account.model');
const { Branch } = require('../../Branches/models/Branch.model');

View File

@@ -31,7 +31,7 @@ export class GetPaymentsReceivedService {
// Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList(
PaymentReceive,
PaymentReceived,
filter,
);
const { results, pagination } = await this.paymentReceivedModel

View File

@@ -1,15 +1,10 @@
import { BaseModel } from '@/models/Model';
import moment from 'moment';
import { Model } from 'objection';
import { ItemEntry } from '../../TransactionItemEntry/models/ItemEntry';
import { Customer } from '../../Customers/models/Customer';
import { Branch } from '../../Branches/models/Branch.model';
import { Warehouse } from '../../Warehouses/models/Warehouse.model';
import { Document } from '../../ChromiumlyTenancy/models/Document';
import { Injectable } from '@nestjs/common';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
@Injectable()
export class SaleEstimate extends BaseModel {
export class SaleEstimate extends TenantBaseModel {
exchangeRate!: number;
amount!: number;
@@ -207,12 +202,16 @@ export class SaleEstimate extends BaseModel {
* Relationship mapping.
*/
static get relationMappings() {
const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry');
const {
ItemEntry,
} = require('../../TransactionItemEntry/models/ItemEntry');
const { Customer } = require('../../Customers/models/Customer');
const { Branch } = require('../../Branches/models/Branch.model');
const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
const { Document } = require('../../ChromiumlyTenancy/models/Document');
const { PdfTemplateModel } = require('../../PdfTemplate/models/PdfTemplate');
const {
PdfTemplateModel,
} = require('../../PdfTemplate/models/PdfTemplate');
return {
customer: {

View File

@@ -3,7 +3,10 @@ import { IItemEntryDTO } from '../TransactionItemEntry/ItemEntry.types';
import { AttachmentLinkDTO } from '../Attachments/Attachments.types';
import { SaleInvoice } from './models/SaleInvoice';
import { IDynamicListFilter } from '../DynamicListing/DynamicFilter/DynamicFilter.types';
import { CommonMailOptionsDTO } from '../MailNotification/MailNotification.types';
import {
CommonMailOptions,
CommonMailOptionsDTO,
} from '../MailNotification/MailNotification.types';
// import SaleInvoice from './models/SaleInvoice';
// import { SystemUser } from '../System/models/SystemUser';
// import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
@@ -185,34 +188,34 @@ export enum SaleInvoiceAction {
NotifyBySms = 'NotifyBySms',
}
// export interface SaleInvoiceMailOptions extends CommonMailOptions {
// attachInvoice?: boolean;
// formatArgs?: Record<string, any>;
// }
export interface SaleInvoiceMailOptions extends CommonMailOptions {
attachInvoice?: boolean;
formatArgs?: Record<string, any>;
}
// export interface SaleInvoiceMailState extends SaleInvoiceMailOptions {
// invoiceNo: string;
export interface SaleInvoiceMailState extends SaleInvoiceMailOptions {
invoiceNo: string;
// invoiceDate: string;
// invoiceDateFormatted: string;
invoiceDate: string;
invoiceDateFormatted: string;
// dueDate: string;
// dueDateFormatted: string;
dueDate: string;
dueDateFormatted: string;
// total: number;
// totalFormatted: string;
total: number;
totalFormatted: string;
// subtotal: number;
// subtotalFormatted: number;
subtotal: number;
subtotalFormatted: number;
// companyName: string;
// companyLogoUri: string;
companyName: string;
companyLogoUri: string;
// customerName: string;
customerName: string;
// // # Invoice entries
// entries?: Array<{ label: string; total: string; quantity: string | number }>;
// }
// # Invoice entries
entries?: Array<{ label: string; total: string; quantity: string | number }>;
}
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
attachInvoice?: boolean;

View File

@@ -3,7 +3,6 @@ import { CreateSaleInvoice } from './commands/CreateSaleInvoice.service';
import { DeleteSaleInvoice } from './commands/DeleteSaleInvoice.service';
import { GetSaleInvoice } from './queries/GetSaleInvoice.service';
import { EditSaleInvoice } from './commands/EditSaleInvoice.service';
// import { GetSaleInvoices } from './queries/GetSaleInvoices';
import { DeliverSaleInvoice } from './commands/DeliverSaleInvoice.service';
import { GetSaleInvoicesPayable } from './queries/GetSaleInvoicesPayable.service';
import { WriteoffSaleInvoice } from './commands/WriteoffSaleInvoice.service';
@@ -11,16 +10,18 @@ import { SaleInvoicePdf } from './queries/SaleInvoicePdf.service';
import { GetInvoicePaymentsService } from './queries/GetInvoicePayments.service';
// import { SaleInvoiceNotifyBySms } from './SaleInvoiceNotifyBySms';
// import { SendInvoiceMailReminder } from './commands/SendSaleInvoiceMailReminder';
// import { SendSaleInvoiceMail } from './commands/SendSaleInvoiceMail';
import { GetSaleInvoiceState } from './queries/GetSaleInvoiceState.service';
// import { GetSaleInvoiceMailState } from './queries/GetSaleInvoiceMailState.service';
import { GetSaleInvoiceMailState } from './queries/GetSaleInvoiceMailState.service';
import {
ISaleInvoiceCreateDTO,
ISaleInvoiceEditDTO,
ISaleInvoiceWriteoffDTO,
ISalesInvoicesFilter,
SaleInvoiceMailState,
SendInvoiceMailDTO,
} from './SaleInvoice.types';
import { GetSaleInvoicesService } from './queries/GetSaleInvoices';
import { SendSaleInvoiceMail } from './commands/SendSaleInvoiceMail';
@Injectable()
export class SaleInvoiceApplication {
@@ -36,10 +37,9 @@ export class SaleInvoiceApplication {
private getInvoicePaymentsService: GetInvoicePaymentsService,
private pdfSaleInvoiceService: SaleInvoicePdf,
private getSaleInvoiceStateService: GetSaleInvoiceState,
private sendSaleInvoiceMailService: SendSaleInvoiceMail,
private getSaleInvoiceMailStateService: GetSaleInvoiceMailState,
// private invoiceSms: SaleInvoiceNotifyBySms,
private sendInvoiceReminderService: SendInvoiceMailReminder,
// private sendSaleInvoiceMailService: SendSaleInvoiceMail,
// private getSaleInvoiceMailStateService: GetSaleInvoiceMailState,
) {}
/**
@@ -175,6 +175,22 @@ export class SaleInvoiceApplication {
return this.pdfSaleInvoiceService.getSaleInvoiceHtml(saleInvoiceId);
}
/**
* Sends the invoice mail of the given sale invoice.
* @param {number} saleInvoiceId - Sale invoice id.
* @param {SendInvoiceMailDTO} messageDTO - Message data.
* @returns {Promise<void>}
*/
public sendSaleInvoiceMail(
saleInvoiceId: number,
messageDTO: SendInvoiceMailDTO,
) {
return this.sendSaleInvoiceMailService.triggerMail(
saleInvoiceId,
messageDTO,
);
}
/**
*
* @param {number} tenantId
@@ -223,53 +239,16 @@ export class SaleInvoiceApplication {
// );
// }
/**
* Sends reminder of the given invoice to the invoice's customer.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @returns {}
*/
// public sendSaleInvoiceMailReminder(
// tenantId: number,
// saleInvoiceId: number,
// messageDTO: SendInvoiceMailDTO,
// ) {
// return this.sendInvoiceReminderService.triggerMail(
// tenantId,
// saleInvoiceId,
// messageDTO,
// );
// }
/**
* Sends the invoice mail of the given sale invoice.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {SendInvoiceMailDTO} messageDTO
* @returns {Promise<void>}
*/
// public sendSaleInvoiceMail(
// tenantId: number,
// saleInvoiceId: number,
// messageDTO: SendInvoiceMailDTO,
// ) {
// return this.sendSaleInvoiceMailService.triggerMail(
// tenantId,
// saleInvoiceId,
// messageDTO,
// );
// }
/**
* Retrieves the default mail options of the given sale invoice.
* @param {number} saleInvoiceid
* @returns {Promise<SaleInvoiceMailState>}
*/
// public getSaleInvoiceMailState(
// saleInvoiceid: number,
// ): Promise<SaleInvoiceMailState> {
// return this.getSaleInvoiceMailStateService.getInvoiceMailState(
// saleInvoiceid,
// );
// }
public getSaleInvoiceMailState(
saleInvoiceid: number,
): Promise<SaleInvoiceMailState> {
return this.getSaleInvoiceMailStateService.getInvoiceMailState(
saleInvoiceid,
);
}
}

View File

@@ -37,7 +37,6 @@ import SaleInvoiceWriteoffSubscriber from './subscribers/SaleInvoiceWriteoffSubs
import { SaleInvoiceWriteoffGLStorage } from './commands/writeoff/SaleInvoiceWriteoffGLStorage';
import { InvoiceInventoryTransactions } from './commands/inventory/InvoiceInventoryTransactions';
import { SendSaleEstimateMail } from '../SaleEstimates/commands/SendSaleEstimateMail';
import { SendInvoiceMailReminder } from './commands/SendSaleInvoiceMailReminder';
import { MailModule } from '../Mail/Mail.module';
@Module({
@@ -84,7 +83,6 @@ import { MailModule } from '../Mail/Mail.module';
SaleInvoiceWriteoffSubscriber,
InvoiceInventoryTransactions,
SendSaleEstimateMail,
SendInvoiceMailReminder,
],
})
export class SaleInvoicesModule {}

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import { GetSaleInvoice } from '../queries/GetSaleInvoice.service';
import {
DEFAULT_INVOICE_MAIL_CONTENT,
@@ -8,6 +9,7 @@ import { GenerateShareLink } from './GenerateInvoicePaymentLink.service';
import { Inject, Injectable } from '@nestjs/common';
import { SaleInvoice } from '../models/SaleInvoice';
import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification';
import { SaleInvoiceMailOptions } from '../SaleInvoice.types';
@Injectable()
export class SendSaleInvoiceMailCommon {
@@ -15,7 +17,7 @@ export class SendSaleInvoiceMailCommon {
private getSaleInvoiceService: GetSaleInvoice,
private contactMailNotification: ContactMailNotification,
private getInvoicePaymentMail: GetInvoicePaymentMail,
private generatePaymentLinkService: GenerateShareLink,
private generatePaymentLinkService: GenerateShareLink,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice,
@@ -91,17 +93,17 @@ export class SendSaleInvoiceMailCommon {
/**
* Retrieves the formatted text of the given sale invoice.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Sale invoice id.
* @param {string} text - The given text.
* @returns {Promise<string>}
*/
// @ts-nocheck
public getInvoiceFormatterArgs = async (
invoiceId: number,
): Promise<Record<string, string | number>> => {
const invoice = await this.getSaleInvoiceService.getSaleInvoice(invoiceId);
const commonArgs =
await this.contactMailNotification.getCommonFormatArgs(tenantId);
const commonArgs = await this.contactMailNotification.getCommonFormatArgs();
return {
...commonArgs,
'Customer Name': invoice.customer.displayName,
@@ -114,4 +116,3 @@ export class SendSaleInvoiceMailCommon {
};
};
}

View File

@@ -4,7 +4,7 @@ import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon.servic
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils';
import { SendInvoiceMailDTO } from '../SaleInvoice.types';
import { SaleInvoiceMailOptions, SendInvoiceMailDTO } from '../SaleInvoice.types';
import { ISaleInvoiceMailSend } from '../SaleInvoice.types';
import { Mail } from '@/modules/Mail/Mail';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';

View File

@@ -1,96 +0,0 @@
import { Injectable } from '@nestjs/common';
import {
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
} from '../constants';
import { SaleInvoicePdf } from '../queries/SaleInvoicePdf.service';
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon.service';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { ISaleInvoiceMailSend, ISaleInvoiceMailSent, SendInvoiceMailDTO } from '../SaleInvoice.types';
import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
import { Mail } from '@/modules/Mail/Mail';
@Injectable()
export class SendInvoiceMailReminder {
constructor(
private readonly invoicePdf: SaleInvoicePdf,
private readonly invoiceCommonMail: SendSaleInvoiceMailCommon,
private readonly eventEmitter: EventEmitter2,
private readonly mailTransporter: MailTransporter,
) {}
/**
* Triggers the reminder mail of the given sale invoice.
* @param {number} saleInvoiceId
*/
public async triggerMail(
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO,
) {
const payload = {
saleInvoiceId,
messageOptions,
};
// await this.agenda.now('sale-invoice-reminder-mail-send', payload);
}
/**
* Retrieves the mail options of the given sale invoice.
* @param {number} saleInvoiceId - The sale invocie id.
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOption(saleInvoiceId: number) {
return this.invoiceCommonMail.getMailOption(
saleInvoiceId,
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
);
}
/**
* Triggers the mail invoice.
* @param {number} saleInvoiceId - Sale invoice id.
* @param {SendInvoiceMailDTO} messageOptions - The message options.
* @returns {Promise<void>}
*/
public async sendMail(
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO,
) {
const localMessageOpts = await this.getMailOption(saleInvoiceId);
const messageOpts = mergeAndValidateMailOptions(
localMessageOpts,
messageOptions,
);
const mail = new Mail()
.setSubject(messageOpts.subject)
.setTo(messageOpts.to)
.setContent(messageOpts.body);
if (messageOpts.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
const [invoicePdfBuffer, filename] = await this.invoicePdf.getSaleInvoicePdf(
saleInvoiceId,
);
mail.setAttachments([
{ filename, content: invoicePdfBuffer },
]);
}
// Triggers the event `onSaleInvoiceSend`.
await this.eventEmitter.emitAsync(events.saleInvoice.onMailReminderSend, {
saleInvoiceId,
messageOptions,
} as ISaleInvoiceMailSend);
await this.mailTransporter.send(mail);
// Triggers the event `onSaleInvoiceSent`.
await this.eventEmitter.emitAsync(events.saleInvoice.onMailReminderSent, {
saleInvoiceId,
messageOptions,
} as ISaleInvoiceMailSent);
}
}

View File

@@ -4,20 +4,15 @@ import * as moment from 'moment';
import * as R from 'ramda';
import { MomentInput, unitOfTime } from 'moment';
import { defaultTo } from 'ramda';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import SaleInvoiceMeta from './SaleInvoice.Settings';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Sales/Invoices/constants';
// import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model';
import { TaxRateTransaction } from '@/modules/TaxRates/models/TaxRateTransaction.model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
import { DiscountType } from '@/common/types/Discount';
import { Account } from '@/modules/Accounts/models/Account.model';
import { ISearchRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class SaleInvoice extends BaseModel {
export class SaleInvoice extends TenantBaseModel{
public taxAmountWithheld: number;
public balance: number;
public paymentAmount: number;
@@ -749,11 +744,11 @@ export class SaleInvoice extends BaseModel {
/**
* Model search attributes.
*/
static get searchRoles() {
static get searchRoles(): ISearchRole[] {
return [
{ fieldKey: 'invoice_no', comparator: 'contains' },
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' },
// { condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
// { condition: 'or', fieldKey: 'amount', comparator: 'equals' },
];
}

View File

@@ -1,46 +1,48 @@
// import { Inject, Injectable } from '@nestjs/common';
// import { GetSaleInvoiceMailStateTransformer } from './GetSaleInvoiceMailState.transformer';
// import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
// import { SaleInvoice } from '../models/SaleInvoice';
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { GetSaleInvoiceMailStateTransformer } from './GetSaleInvoiceMailState.transformer';
import { SendSaleInvoiceMailCommon } from '../commands/SendInvoiceInvoiceMailCommon.service';
import { SaleInvoice } from '../models/SaleInvoice';
import { SaleInvoiceMailState } from '../SaleInvoice.types';
// @Injectable()
// export class GetSaleInvoiceMailState {
// constructor(
// private transformer: TransformerInjectable,
// // private invoiceMail: SendSaleInvoiceMailCommon,
@Injectable()
export class GetSaleInvoiceMailState {
constructor(
private transformer: TransformerInjectable,
private invoiceMail: SendSaleInvoiceMailCommon,
// @Inject(SaleInvoice.name)
// private saleInvoiceModel: typeof SaleInvoice,
// ) {}
@Inject(SaleInvoice.name)
private saleInvoiceModel: typeof SaleInvoice,
) {}
// /**
// * Retrieves the invoice mail state of the given sale invoice.
// * Invoice mail state includes the mail options, branding attributes and the invoice details.
// * @param {number} saleInvoiceId - Sale invoice id.
// * @returns {Promise<SaleInvoiceMailState>}
// */
// async getInvoiceMailState(
// saleInvoiceId: number,
// ): Promise<SaleInvoiceMailState> {
// const saleInvoice = await this.saleInvoiceModel
// .query()
// .findById(saleInvoiceId)
// .withGraphFetched('customer')
// .withGraphFetched('entries.item')
// .withGraphFetched('pdfTemplate')
// .throwIfNotFound();
/**
* Retrieves the invoice mail state of the given sale invoice.
* Invoice mail state includes the mail options, branding attributes and the invoice details.
* @param {number} saleInvoiceId - Sale invoice id.
* @returns {Promise<SaleInvoiceMailState>}
*/
public async getInvoiceMailState(
saleInvoiceId: number,
): Promise<SaleInvoiceMailState> {
const saleInvoice = await this.saleInvoiceModel
.query()
.findById(saleInvoiceId)
.withGraphFetched('customer')
.withGraphFetched('entries.item')
.withGraphFetched('pdfTemplate')
.throwIfNotFound();
// const mailOptions =
// await this.invoiceMail.getInvoiceMailOptions(saleInvoiceId);
const mailOptions =
await this.invoiceMail.getInvoiceMailOptions(saleInvoiceId);
// // Transforms the sale invoice mail state.
// const transformed = await this.transformer.transform(
// saleInvoice,
// new GetSaleInvoiceMailStateTransformer(),
// {
// mailOptions,
// },
// );
// return transformed;
// }
// }
// Transforms the sale invoice mail state.
const transformed = await this.transformer.transform(
saleInvoice,
new GetSaleInvoiceMailStateTransformer(),
{
mailOptions,
},
);
return transformed;
}
}

View File

@@ -6,6 +6,7 @@ import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { SaleInvoice } from '../models/SaleInvoice';
import { ISalesInvoicesFilter } from '../SaleInvoice.types';
import { Knex } from 'knex';
@Injectable()
export class GetSaleInvoicesService {
@@ -36,8 +37,9 @@ export class GetSaleInvoicesService {
.onBuild((builder) => {
builder.withGraphFetched('entries.item');
builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder);
filterDTO?.filterQuery && filterDTO?.filterQuery(builder);
filterDTO?.filterQuery?.(builder as any);
})
.pagination(filter.page - 1, filter.pageSize);

View File

@@ -1,11 +1,6 @@
import { Model, mixin } from 'objection';
import { defaultTo } from 'ramda';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import SaleReceiptSettings from './SaleReceipt.Settings';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Sales/Receipts/constants';
// import ModelSearchable from './ModelSearchable';
import { Model } from 'objection';
import { defaultTo } from 'lodash';
import * as R from 'ramda';
import { BaseModel } from '@/models/Model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Customer } from '@/modules/Customers/models/Customer';
@@ -13,38 +8,48 @@ import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction
import { Branch } from '@/modules/Branches/models/Branch.model';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { DiscountType } from '@/common/types/Discount';
import { MetadataModelMixin } from '@/modules/DynamicListing/models/MetadataModel';
import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableModel';
import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel';
import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel';
export class SaleReceipt extends BaseModel {
amount: number;
exchangeRate: number;
currencyCode: string;
depositAccountId: number;
customerId: number;
receiptDate: Date;
receiptNumber: string;
referenceNo: string;
sendToEmail: string;
receiptMessage: string;
statement: string;
closedAt: Date | string;
const ExtendedModel = R.pipe(
CustomViewBaseModelMixin,
SearchableBaseModelMixin,
ResourceableModelMixin,
MetadataModelMixin,
)(BaseModel);
discountType: DiscountType;
discount: number;
adjustment: number;
export class SaleReceipt extends ExtendedModel {
public amount!: number;
public exchangeRate!: number;
public currencyCode!: string;
public depositAccountId!: number;
public customerId!: number;
public receiptDate!: Date;
public receiptNumber!: string;
public referenceNo!: string;
public sendToEmail!: string;
public receiptMessage!: string;
public statement!: string;
public closedAt!: Date | string;
public discountType!: DiscountType;
public discount!: number;
public adjustment!: number;
branchId: number;
warehouseId: number;
public branchId!: number;
public warehouseId!: number;
userId: number;
public userId!: number;
createdAt: Date;
updatedAt: Date | null;
public createdAt!: Date;
public updatedAt!: Date | null;
customer!: Customer;
entries!: ItemEntry[];
transactions!: AccountTransaction[];
branch!: Branch;
warehouse!: Warehouse;
public customer!: Customer;
public entries!: ItemEntry[];
public transactions!: AccountTransaction[];
public branch!: Branch;
public warehouse!: Warehouse;
/**
* Table name
@@ -142,7 +147,7 @@ export class SaleReceipt extends BaseModel {
* Receipt total.
* @returns {number}
*/
get total() {
get total(): number {
const adjustmentAmount = defaultTo(this.adjustment, 0);
return this.subtotal - this.discountAmount + adjustmentAmount;
@@ -256,6 +261,9 @@ export class SaleReceipt extends BaseModel {
const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
return {
/**
* Sale receipt may has a customer.
*/
customer: {
relation: Model.BelongsToOneRelation,
modelClass: Customer,
@@ -268,6 +276,9 @@ export class SaleReceipt extends BaseModel {
},
},
/**
* Sale receipt may has a deposit account.
*/
depositAccount: {
relation: Model.BelongsToOneRelation,
modelClass: Account,
@@ -277,6 +288,9 @@ export class SaleReceipt extends BaseModel {
},
},
/**
* Sale receipt may has many items entries.
*/
entries: {
relation: Model.HasManyRelation,
modelClass: ItemEntry,
@@ -290,6 +304,9 @@ export class SaleReceipt extends BaseModel {
},
},
/**
* Sale receipt may has many transactions.
*/
transactions: {
relation: Model.HasManyRelation,
modelClass: AccountTransaction,

View File

@@ -0,0 +1,15 @@
import * as R from 'ramda';
import { BaseModel } from '@/models/Model';
import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel';
import { MetadataModelMixin } from '@/modules/DynamicListing/models/MetadataModel';
import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel';
import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableModel';
const ExtendedItem = R.pipe(
CustomViewBaseModelMixin,
SearchableBaseModelMixin,
ResourceableModelMixin,
MetadataModelMixin,
)(BaseModel);
export class TenantBaseModel extends ExtendedItem {}

View File

@@ -180,7 +180,7 @@ export class Transformer<T = {}, ExtraContext = {}> {
* @param {string} date
* @returns {string}
*/
protected formatDateFromNow(date: string) {
protected formatDateFromNow(date: moment.MomentInput) {
return date ? moment(date).fromNow(true) : '';
}

View File

@@ -1,20 +1,12 @@
import { Model, raw, mixin } from 'objection';
// import TenantModel from 'models/TenantModel';
// import BillSettings from './Bill.Settings';
// import ModelSetting from './ModelSetting';
// import CustomViewBaseModel from './CustomViewBaseModel';
// import { DEFAULT_VIEWS } from '@/services/Purchases/VendorCredits/constants';
// import ModelSearchable from './ModelSearchable';
// import VendorCreditMeta from './VendorCredit.Meta';
// import { DiscountType } from '@/interfaces';
import { Model, raw } from 'objection';
import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { Branch } from '@/modules/Branches/models/Branch.model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { BaseModel } from '@/models/Model';
import { DiscountType } from '@/common/types/Discount';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class VendorCredit extends BaseModel {
export class VendorCredit extends TenantBaseModel {
vendorId: number;
amount: number;
currencyCode: string;

View File

@@ -1,8 +1,10 @@
import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common';
import { VendorCreditApplyBillsApplicationService } from './VendorCreditApplyBillsApplication.service';
import { IVendorCreditApplyToInvoicesDTO } from './types/VendorCreditApplyBills.types';
import { ApiTags } from '@nestjs/swagger';
@Controller('vendor-credits')
@ApiTags('vendor-credits-apply-bills')
export class VendorCreditApplyBillsController {
constructor(
private readonly vendorCreditApplyBillsApplication: VendorCreditApplyBillsApplicationService,

View File

@@ -7,6 +7,7 @@ import { Model, mixin } from 'objection';
// import { DEFAULT_VIEWS } from '@/services/Contacts/Vendors/constants';
// import ModelSearchable from './ModelSearchable';
import { BaseModel } from '@/models/Model';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
// class VendorQueryBuilder extends PaginationQueryBuilder {
// constructor(...args) {
@@ -20,7 +21,7 @@ import { BaseModel } from '@/models/Model';
// }
// }
export class Vendor extends BaseModel {
export class Vendor extends TenantBaseModel {
contactService: string;
contactType: string;

View File

@@ -10,8 +10,10 @@ import {
import { WarehousesApplication } from './WarehousesApplication.service';
import { ICreateWarehouseDTO, IEditWarehouseDTO } from './Warehouse.types';
import { PublicRoute } from '../Auth/Jwt.guard';
import { ApiTags } from '@nestjs/swagger';
@Controller('warehouses')
@ApiTags('warehouses')
@PublicRoute()
export class WarehousesController {
constructor(private warehousesApplication: WarehousesApplication) {}

View File

@@ -0,0 +1,16 @@
import { defaultTo } from 'lodash';
export const formatMessage = (message: string, args: Record<string, any>) => {
let formattedMessage = message;
Object.keys(args).forEach((key) => {
const variable = `{${key}}`;
const value = defaultTo(args[key], '');
formattedMessage = formattedMessage.replace(
new RegExp(variable, 'g'),
value
);
});
return formattedMessage;
};