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-loader": "^9.4.3",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3" "typescript": "^5.1.3",
"mustache": "^3.0.3"
}, },
"jest": { "jest": {
"moduleFileExtensions": [ "moduleFileExtensions": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,10 +13,10 @@ export class GetAccountsService {
constructor( constructor(
private readonly dynamicListService: DynamicListService, private readonly dynamicListService: DynamicListService,
private readonly transformerService: TransformerInjectable, private readonly transformerService: TransformerInjectable,
private readonly accountRepository: AccountRepository,
@Inject(Account.name) @Inject(Account.name)
private readonly accountModel: typeof Account, 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 { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
import { Model } from 'objection'; import { Model } from 'objection';
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem'; import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
// import AccountSettings from './Account.Settings'; // import AccountSettings from './Account.Settings';
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants'; // import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder'; // import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder';
// import { flatToNestedArray } from 'utils'; // 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 name!: string;
public slug!: string; public slug!: string;
public code!: string; public code!: string;
public index!: number; public index!: number;
public accountType!: string; public accountType!: string;
public parentAccountId!: number | null;
public predefined!: boolean; public predefined!: boolean;
public currencyCode!: string; public currencyCode!: string;
public active!: boolean; public active!: boolean;
public bankBalance!: number; public bankBalance!: number;
public lastFeedsUpdatedAt!: string | null; public lastFeedsUpdatedAt!: string | Date | null;
public amount!: number; public amount!: number;
public plaidItemId!: number; public plaidItemId!: number;
public plaidAccountId!: string | null;
public isFeedsActive!: boolean;
public plaidItem!: PlaidItem; public plaidItem!: PlaidItem;
/** /**
@@ -73,11 +70,11 @@ export class Account extends TenantModel {
/** /**
* Account normal. * Account normal.
*/ */
get accountNormal() { get accountNormal(): string {
return AccountTypesUtils.getType(this.accountType, 'normal'); return AccountTypesUtils.getType(this.accountType, 'normal');
} }
get accountNormalFormatted() { get accountNormalFormatted(): string {
const paris = { const paris = {
credit: 'Credit', credit: 'Credit',
debit: 'Debit', debit: 'Debit',
@@ -88,35 +85,35 @@ export class Account extends TenantModel {
/** /**
* Retrieve account type label. * Retrieve account type label.
*/ */
get accountTypeLabel() { get accountTypeLabel(): string {
return AccountTypesUtils.getType(this.accountType, 'label'); return AccountTypesUtils.getType(this.accountType, 'label');
} }
/** /**
* Retrieve account parent type. * Retrieve account parent type.
*/ */
get accountParentType() { get accountParentType(): string {
return AccountTypesUtils.getType(this.accountType, 'parentType'); return AccountTypesUtils.getType(this.accountType, 'parentType');
} }
/** /**
* Retrieve account root type. * Retrieve account root type.
*/ */
get accountRootType() { get accountRootType(): string {
return AccountTypesUtils.getType(this.accountType, 'rootType'); return AccountTypesUtils.getType(this.accountType, 'rootType');
} }
/** /**
* Retrieve whether the account is balance sheet account. * Retrieve whether the account is balance sheet account.
*/ */
get isBalanceSheetAccount() { get isBalanceSheetAccount(): boolean {
return this.isBalanceSheet(); return this.isBalanceSheet();
} }
/** /**
* Retrieve whether the account is profit/loss sheet account. * Retrieve whether the account is profit/loss sheet account.
*/ */
get isPLSheet() { get isPLSheet(): boolean {
return this.isProfitLossSheet(); return this.isProfitLossSheet();
} }
/** /**

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
/* eslint-disable global-require */ /* eslint-disable global-require */
import { mixin, Model } from 'objection'; import { Model } from 'objection';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import { BaseModel } from '@/models/Model';
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils'; import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem'; 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 name!: string;
public slug!: string; public slug!: string;
public code!: string; public code!: string;

View File

@@ -48,7 +48,9 @@ export class CashflowAccountTransformer extends Transformer {
* @returns {string} * @returns {string}
*/ */
protected lastFeedsUpdatedAtFormatted(account: Account): 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} * @returns {string}
*/ */
protected lastFeedsUpdatedFromNow(account: Account): 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. // Dynamic list service.
const dynamicList = await this.dynamicListService.dynamicList( const dynamicList = await this.dynamicListService.dynamicList(
BankAccount, this.bankAccountModel,
filter, filter,
); );
// Retrieve accounts model based on the given query. // Retrieve accounts model based on the given query.

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,12 @@ import { DiscountType } from '@/common/types/Discount';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { Branch } from '@/modules/Branches/models/Branch.model'; import { Branch } from '@/modules/Branches/models/Branch.model';
import { Customer } from '@/modules/Customers/models/Customer'; import { Customer } from '@/modules/Customers/models/Customer';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model'; import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { mixin, Model, raw } from 'objection'; 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 amount: number;
public exchangeRate: number; public exchangeRate: number;
public openedAt: Date; public openedAt: Date;

View File

@@ -39,7 +39,7 @@ export class GetCreditNotesService {
// Dynamic list service. // Dynamic list service.
const dynamicFilter = await this.dynamicListService.dynamicList( const dynamicFilter = await this.dynamicListService.dynamicList(
CreditNote, this.creditNoteModel,
filter, filter,
); );
const { results, pagination } = await this.creditNoteModel const { results, pagination } = await this.creditNoteModel
@@ -47,8 +47,9 @@ export class GetCreditNotesService {
.onBuild((builder) => { .onBuild((builder) => {
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
builder.withGraphFetched('customer'); builder.withGraphFetched('customer');
dynamicFilter.buildQuery()(builder); dynamicFilter.buildQuery()(builder);
creditNotesQuery?.filterQuery && creditNotesQuery?.filterQuery(builder); creditNotesQuery?.filterQuery?.(builder as any);
}) })
.pagination(filter.page - 1, filter.pageSize); .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 { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types';
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model'; import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types'; import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
export interface ICreditNoteEntryNewDTO extends IItemEntryDTO {} export interface ICreditNoteEntryNewDTO extends IItemEntryDTO {}
@@ -97,6 +98,7 @@ export interface ICreditNotesQueryDTO extends IDynamicListFilter {
page: number; page: number;
pageSize: number; pageSize: number;
searchKeyword?: string; searchKeyword?: string;
filterQuery?: (builder: Knex.QueryBuilder) => void;
} }
export interface ICreditNoteRefundDTO { export interface ICreditNoteRefundDTO {
@@ -109,22 +111,22 @@ export interface ICreditNoteRefundDTO {
branchId?: number; branchId?: number;
} }
// export type ICreditNoteGLCommonEntry = Pick< export type ICreditNoteGLCommonEntry = Pick<
// ILedgerEntry, ILedgerEntry,
// | 'date' | 'date'
// | 'userId' | 'userId'
// | 'currencyCode' | 'currencyCode'
// | 'exchangeRate' | 'exchangeRate'
// | 'transactionType' | 'transactionType'
// | 'transactionId' | 'transactionId'
// | 'transactionNumber' | 'transactionNumber'
// | 'referenceNumber' | 'referenceNumber'
// | 'createdAt' | 'createdAt'
// | 'indexGroup' | 'indexGroup'
// | 'credit' | 'credit'
// | 'debit' | 'debit'
// | 'branchId' | 'branchId'
// >; >;
export interface GetCreditNotesResponse { export interface GetCreditNotesResponse {
creditNotes: CreditNote[]; 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; 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 { class CustomViewBaseModel extends Model {
/** /**
* Retrieve the default custom views, roles and columns. * 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; return this.defaultViews.find((view) => view.slug === viewSlug) || null;
} }
/**
* Retrieve the default views.
* @returns {IView[]}
*/
static getDefaultViews() { static getDefaultViews() {
return this.defaultViews; return this.defaultViews;
} }

View File

@@ -1,5 +1,4 @@
import { Model, mixin } from 'objection'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { BaseModel } from '@/models/Model';
// import TenantModel from 'models/TenantModel'; // import TenantModel from 'models/TenantModel';
// import PaginationQueryBuilder from './Pagination'; // import PaginationQueryBuilder from './Pagination';
// import ModelSetting from './ModelSetting'; // 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; contactService: string;
contactType: string; contactType: string;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,10 @@
import { IFilterRole } from './DynamicFilter.types'; import { IFilterRole } from './DynamicFilter.types';
import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles'; import { DynamicFilterFilterRoles } from './DynamicFilterFilterRoles';
export interface IDynamicFilterSearchResponseMeta {
searchKeyword: string;
}
export class DynamicFilterSearch extends DynamicFilterFilterRoles { export class DynamicFilterSearch extends DynamicFilterFilterRoles {
private searchKeyword: string; private searchKeyword: string;
@@ -37,11 +41,21 @@ export class DynamicFilterSearch extends DynamicFilterFilterRoles {
} }
/** /**
* * Sets the response meta.
*/ */
setResponseMeta() { setResponseMeta() {
this.responseMeta = { this.responseMeta = {
searchKeyword: this.searchKeyword, 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 { FIELD_TYPE } from './constants';
import { DynamicFilterAbstractor } from './DynamicFilterAbstractor';
import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor'; import { DynamicFilterRoleAbstractor } from './DynamicFilterRoleAbstractor';
interface ISortRole { interface ISortRole {
@@ -8,7 +7,10 @@ interface ISortRole {
} }
export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor { export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
private sortRole: ISortRole = {}; private sortRole: ISortRole = {
fieldKey: '',
order: '',
};
/** /**
* Constructor method. * Constructor method.
@@ -22,7 +24,6 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
fieldKey: sortByFieldKey, fieldKey: sortByFieldKey,
order: sortDirection, order: sortDirection,
}; };
this.setResponseMeta();
} }
/** /**
@@ -54,7 +55,7 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
* @param {IModel} field * @param {IModel} field
* @returns {string} * @returns {string}
*/ */
private getFieldComparatorColumn = (field): string => { getFieldComparatorColumn = (field) => {
return field.fieldType === FIELD_TYPE.RELATION return field.fieldType === FIELD_TYPE.RELATION
? this.getFieldComparatorRelationColumn(field) ? this.getFieldComparatorRelationColumn(field)
: `${this.tableName}.${field.column}`; : `${this.tableName}.${field.column}`;
@@ -84,10 +85,10 @@ export class DynamicFilterSortBy extends DynamicFilterRoleAbstractor {
/** /**
* Sets response meta. * Sets response meta.
*/ */
public setResponseMeta() { public getResponseMeta(): ISortRole {
this.responseMeta = { return {
sortOrder: this.sortRole.fieldKey, fieldKey: this.sortRole.fieldKey,
sortBy: this.sortRole.order, order: this.sortRole.order,
}; };
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,10 @@
import { Model, mixin, raw } from 'objection'; import { Model, 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 moment from 'moment'; import moment from 'moment';
import { BaseModel } from '@/models/Model';
import { ExpenseCategory } from './ExpenseCategory.model'; import { ExpenseCategory } from './ExpenseCategory.model';
import { Account } from '@/modules/Accounts/models/Account.model'; import { Account } from '@/modules/Accounts/models/Account.model';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class Expense extends BaseModel { export class Expense extends TenantBaseModel {
// ModelSetting,
// CustomViewBaseModel,
// ModelSearchable,
// ]) {
totalAmount!: number; totalAmount!: number;
currencyCode!: string; currencyCode!: string;
exchangeRate!: number; 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 { InventoryAdjustment } from './models/InventoryAdjustment';
import { PublicRoute } from '../Auth/Jwt.guard'; import { PublicRoute } from '../Auth/Jwt.guard';
import { IPaginationMeta } from '@/interfaces/Model'; import { IPaginationMeta } from '@/interfaces/Model';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
@Controller('inventory-adjustments') @Controller('inventory-adjustments')
@ApiTags('inventory-adjustments')
@PublicRoute() @PublicRoute()
export class InventoryAdjustmentsController { export class InventoryAdjustmentsController {
constructor( constructor(
@@ -25,6 +27,7 @@ export class InventoryAdjustmentsController {
) {} ) {}
@Post('quick') @Post('quick')
@ApiOperation({ summary: 'Create a quick inventory adjustment.' })
public async createQuickInventoryAdjustment( public async createQuickInventoryAdjustment(
@Body() quickAdjustmentDTO: IQuickInventoryAdjustmentDTO, @Body() quickAdjustmentDTO: IQuickInventoryAdjustmentDTO,
): Promise<InventoryAdjustment> { ): Promise<InventoryAdjustment> {
@@ -34,6 +37,7 @@ export class InventoryAdjustmentsController {
} }
@Delete(':id') @Delete(':id')
@ApiOperation({ summary: 'Delete the given inventory adjustment.' })
public async deleteInventoryAdjustment( public async deleteInventoryAdjustment(
@Param('id') inventoryAdjustmentId: number, @Param('id') inventoryAdjustmentId: number,
): Promise<void> { ): Promise<void> {
@@ -43,6 +47,7 @@ export class InventoryAdjustmentsController {
} }
@Get() @Get()
@ApiOperation({ summary: 'Retrieves the inventory adjustments.' })
public async getInventoryAdjustments( public async getInventoryAdjustments(
@Query() filterDTO: IInventoryAdjustmentsFilter, @Query() filterDTO: IInventoryAdjustmentsFilter,
): Promise<{ ): Promise<{
@@ -55,6 +60,7 @@ export class InventoryAdjustmentsController {
} }
@Get(':id') @Get(':id')
@ApiOperation({ summary: 'Retrieves the inventory adjustment details.' })
public async getInventoryAdjustment( public async getInventoryAdjustment(
@Param('id') inventoryAdjustmentId: number, @Param('id') inventoryAdjustmentId: number,
): Promise<InventoryAdjustment> { ): Promise<InventoryAdjustment> {
@@ -64,6 +70,7 @@ export class InventoryAdjustmentsController {
} }
@Put(':id/publish') @Put(':id/publish')
@ApiOperation({ summary: 'Publish the given inventory adjustment.' })
public async publishInventoryAdjustment( public async publishInventoryAdjustment(
@Param('id') inventoryAdjustmentId: number, @Param('id') inventoryAdjustmentId: number,
): Promise<void> { ): Promise<void> {

View File

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

View File

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

View File

@@ -27,12 +27,8 @@ import InventoryCostMethod from './InventoryCostMethod';
export class InventoryService { export class InventoryService {
constructor( constructor(
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
private readonly itemsEntriesService: ItemsEntriesService,
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
@Inject(Item.name)
private readonly itemModel: typeof Item,
@Inject(InventoryTransaction.name) @Inject(InventoryTransaction.name)
private readonly inventoryTransactionModel: typeof InventoryTransaction, private readonly inventoryTransactionModel: typeof InventoryTransaction,

View File

@@ -1,9 +1,11 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { InventoryCostGLBeforeWriteSubscriber } from './subscribers/InventoryCostGLBeforeWriteSubscriber';
import { InventoryCostGLStorage } from './InventoryCostGLStorage.service'; import { InventoryCostGLStorage } from './InventoryCostGLStorage.service';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module'; import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { InventoryCostLotTracker } from './models/InventoryCostLotTracker'; import { InventoryCostLotTracker } from './models/InventoryCostLotTracker';
import { InventoryTransaction } from './models/InventoryTransaction'; import { InventoryTransaction } from './models/InventoryTransaction';
import { InventoryCostGLBeforeWriteSubscriber } from './subscribers/InventoryCostGLBeforeWriteSubscriber';
import { InventoryItemsQuantitySyncService } from './InventoryItemsQuantitySync.service';
import { InventoryCostMethod } from './InventoryCostMethod';
const models = [ const models = [
RegisterTenancyModel(InventoryCostLotTracker), RegisterTenancyModel(InventoryCostLotTracker),
@@ -15,6 +17,8 @@ const models = [
...models, ...models,
InventoryCostGLBeforeWriteSubscriber, InventoryCostGLBeforeWriteSubscriber,
InventoryCostGLStorage, InventoryCostGLStorage,
InventoryItemsQuantitySyncService,
InventoryCostMethod,
], ],
exports: [...models], exports: [...models],
}) })

View File

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

View File

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

View File

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

View File

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

View File

@@ -106,10 +106,12 @@ export class InventoryTransaction extends BaseModel {
* Relationship mapping. * Relationship mapping.
*/ */
static get relationMappings() { static get relationMappings() {
const Item = require('models/Item'); const { Item } = require('../../Items/models/Item');
const ItemEntry = require('models/ItemEntry'); const {
const InventoryTransactionMeta = require('models/InventoryTransactionMeta'); ItemEntry,
const InventoryCostLots = require('models/InventoryCostLotTracker'); } = require('../../TransactionItemEntry/models/ItemEntry');
const { InventoryTransactionMeta } = require('./InventoryTransactionMeta');
const { InventoryCostLotTracker } = require('./InventoryCostLotTracker');
return { return {
// Transaction meta. // Transaction meta.
@@ -124,7 +126,7 @@ export class InventoryTransaction extends BaseModel {
// Item cost aggregated. // Item cost aggregated.
itemCostAggregated: { itemCostAggregated: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: InventoryCostLots.default, modelClass: InventoryCostLotTracker,
join: { join: {
from: 'inventory_transactions.itemId', from: 'inventory_transactions.itemId',
to: 'inventory_cost_lot_tracker.itemId', to: 'inventory_cost_lot_tracker.itemId',
@@ -138,7 +140,7 @@ export class InventoryTransaction extends BaseModel {
}, },
costLotAggregated: { costLotAggregated: {
relation: Model.HasOneRelation, relation: Model.HasOneRelation,
modelClass: InventoryCostLots.default, modelClass: InventoryCostLotTracker,
join: { join: {
from: 'inventory_transactions.id', from: 'inventory_transactions.id',
to: 'inventory_cost_lot_tracker.inventoryTransactionId', to: 'inventory_cost_lot_tracker.inventoryTransactionId',
@@ -151,7 +153,7 @@ export class InventoryTransaction extends BaseModel {
}, },
item: { item: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Item.default, modelClass: Item,
join: { join: {
from: 'inventory_transactions.itemId', from: 'inventory_transactions.itemId',
to: 'items.id', to: 'items.id',
@@ -159,7 +161,7 @@ export class InventoryTransaction extends BaseModel {
}, },
itemEntry: { itemEntry: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: ItemEntry.default, modelClass: ItemEntry,
join: { join: {
from: 'inventory_transactions.entryId', from: 'inventory_transactions.entryId',
to: 'items_entries.id', 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 { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { Model, mixin } from 'objection'; import { Model } from 'objection';
// import TenantModel from 'models/TenantModel';
// import ModelSetting from './ModelSetting';
// import ItemCategorySettings from './ItemCategory.Settings';
export class ItemCategory extends BaseModel { export class ItemCategory extends TenantBaseModel {
name!: string; name!: string;
description!: 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 { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel';
import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableModel';
import { MetadataModelMixin } from '@/modules/DynamicListing/models/MetadataModel';
const ExtendedItem = R.pipe( export class Item extends TenantBaseModel{
CustomViewBaseModelMixin,
SearchableBaseModelMixin,
ResourceableModelMixin,
MetadataModelMixin
)(BaseModel);
export class Item extends ExtendedItem {
public readonly quantityOnHand: number; public readonly quantityOnHand: number;
public readonly name: string; public readonly name: string;
public readonly active: boolean; public readonly active: boolean;

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
import { castArray, isEmpty } from 'lodash'; import { castArray, isEmpty } from 'lodash';
import { ServiceError } from '@/exceptions';
import { CommonMailOptions } from '@/interfaces';
import { ERRORS } from './constants'; import { ERRORS } from './constants';
import { CommonMailOptions } from './MailNotification.types';
import { ServiceError } from '../Items/ServiceError';
/** /**
* Merges the mail options with incoming options. * 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 { DEFAULT_VIEWS } from '@/services/ManualJournals/constants';
// import ModelSearchable from './ModelSearchable'; // import ModelSearchable from './ModelSearchable';
import { ManualJournalEntry } from './ManualJournalEntry'; import { ManualJournalEntry } from './ManualJournalEntry';
import { BaseModel } from '@/models/Model';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document'; 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; date: Date;
journalNumber: string; journalNumber: string;
journalType: string; journalType: string;

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,8 @@
import { Model, mixin } from 'objection'; import { Model } 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 { PaymentReceivedEntry } from './PaymentReceivedEntry'; import { PaymentReceivedEntry } from './PaymentReceivedEntry';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
export class PaymentReceived extends BaseModel { export class PaymentReceived extends TenantBaseModel {
customerId: number; customerId: number;
paymentDate: string; paymentDate: string;
amount: number; amount: number;
@@ -69,7 +63,9 @@ export class PaymentReceived extends BaseModel {
*/ */
static get relationMappings() { static get relationMappings() {
const { PaymentReceivedEntry } = require('./PaymentReceivedEntry'); 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 { Customer } = require('../../Customers/models/Customer');
const { Account } = require('../../Accounts/models/Account.model'); const { Account } = require('../../Accounts/models/Account.model');
const { Branch } = require('../../Branches/models/Branch.model'); const { Branch } = require('../../Branches/models/Branch.model');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
// @ts-nocheck
import { GetSaleInvoice } from '../queries/GetSaleInvoice.service'; import { GetSaleInvoice } from '../queries/GetSaleInvoice.service';
import { import {
DEFAULT_INVOICE_MAIL_CONTENT, DEFAULT_INVOICE_MAIL_CONTENT,
@@ -8,6 +9,7 @@ import { GenerateShareLink } from './GenerateInvoicePaymentLink.service';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { SaleInvoice } from '../models/SaleInvoice'; import { SaleInvoice } from '../models/SaleInvoice';
import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification'; import { ContactMailNotification } from '@/modules/MailNotification/ContactMailNotification';
import { SaleInvoiceMailOptions } from '../SaleInvoice.types';
@Injectable() @Injectable()
export class SendSaleInvoiceMailCommon { export class SendSaleInvoiceMailCommon {
@@ -15,7 +17,7 @@ export class SendSaleInvoiceMailCommon {
private getSaleInvoiceService: GetSaleInvoice, private getSaleInvoiceService: GetSaleInvoice,
private contactMailNotification: ContactMailNotification, private contactMailNotification: ContactMailNotification,
private getInvoicePaymentMail: GetInvoicePaymentMail, private getInvoicePaymentMail: GetInvoicePaymentMail,
private generatePaymentLinkService: GenerateShareLink, private generatePaymentLinkService: GenerateShareLink,
@Inject(SaleInvoice.name) @Inject(SaleInvoice.name)
private readonly saleInvoiceModel: typeof SaleInvoice, private readonly saleInvoiceModel: typeof SaleInvoice,
@@ -91,17 +93,17 @@ export class SendSaleInvoiceMailCommon {
/** /**
* Retrieves the formatted text of the given sale invoice. * Retrieves the formatted text of the given sale invoice.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Sale invoice id. * @param {number} invoiceId - Sale invoice id.
* @param {string} text - The given text. * @param {string} text - The given text.
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
// @ts-nocheck
public getInvoiceFormatterArgs = async ( public getInvoiceFormatterArgs = async (
invoiceId: number, invoiceId: number,
): Promise<Record<string, string | number>> => { ): Promise<Record<string, string | number>> => {
const invoice = await this.getSaleInvoiceService.getSaleInvoice(invoiceId); const invoice = await this.getSaleInvoiceService.getSaleInvoice(invoiceId);
const commonArgs = const commonArgs = await this.contactMailNotification.getCommonFormatArgs();
await this.contactMailNotification.getCommonFormatArgs(tenantId);
return { return {
...commonArgs, ...commonArgs,
'Customer Name': invoice.customer.displayName, '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 { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils'; import { mergeAndValidateMailOptions } from '@/modules/MailNotification/utils';
import { SendInvoiceMailDTO } from '../SaleInvoice.types'; import { SaleInvoiceMailOptions, SendInvoiceMailDTO } from '../SaleInvoice.types';
import { ISaleInvoiceMailSend } from '../SaleInvoice.types'; import { ISaleInvoiceMailSend } from '../SaleInvoice.types';
import { Mail } from '@/modules/Mail/Mail'; import { Mail } from '@/modules/Mail/Mail';
import { MailTransporter } from '@/modules/Mail/MailTransporter.service'; 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 * as R from 'ramda';
import { MomentInput, unitOfTime } from 'moment'; import { MomentInput, unitOfTime } from 'moment';
import { defaultTo } from 'ramda'; 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 { TaxRateTransaction } from '@/modules/TaxRates/models/TaxRateTransaction.model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document'; import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
import { DiscountType } from '@/common/types/Discount'; import { DiscountType } from '@/common/types/Discount';
import { Account } from '@/modules/Accounts/models/Account.model'; 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 taxAmountWithheld: number;
public balance: number; public balance: number;
public paymentAmount: number; public paymentAmount: number;
@@ -749,11 +744,11 @@ export class SaleInvoice extends BaseModel {
/** /**
* Model search attributes. * Model search attributes.
*/ */
static get searchRoles() { static get searchRoles(): ISearchRole[] {
return [ return [
{ fieldKey: 'invoice_no', comparator: 'contains' }, { fieldKey: 'invoice_no', comparator: 'contains' },
{ condition: 'or', fieldKey: 'reference_no', comparator: 'contains' }, // { condition: 'or', fieldKey: 'reference_no', comparator: 'contains' },
{ condition: 'or', fieldKey: 'amount', comparator: 'equals' }, // { condition: 'or', fieldKey: 'amount', comparator: 'equals' },
]; ];
} }

View File

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

View File

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

View File

@@ -1,11 +1,6 @@
import { Model, mixin } from 'objection'; import { Model } from 'objection';
import { defaultTo } from 'ramda'; import { defaultTo } from 'lodash';
// import TenantModel from 'models/TenantModel'; import * as R from 'ramda';
// 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 { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Customer } from '@/modules/Customers/models/Customer'; 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 { Branch } from '@/modules/Branches/models/Branch.model';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model'; import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { DiscountType } from '@/common/types/Discount'; 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 { const ExtendedModel = R.pipe(
amount: number; CustomViewBaseModelMixin,
exchangeRate: number; SearchableBaseModelMixin,
currencyCode: string; ResourceableModelMixin,
depositAccountId: number; MetadataModelMixin,
customerId: number; )(BaseModel);
receiptDate: Date;
receiptNumber: string;
referenceNo: string;
sendToEmail: string;
receiptMessage: string;
statement: string;
closedAt: Date | string;
discountType: DiscountType; export class SaleReceipt extends ExtendedModel {
discount: number; public amount!: number;
adjustment: 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; public branchId!: number;
warehouseId: number; public warehouseId!: number;
userId: number; public userId!: number;
createdAt: Date; public createdAt!: Date;
updatedAt: Date | null; public updatedAt!: Date | null;
customer!: Customer; public customer!: Customer;
entries!: ItemEntry[]; public entries!: ItemEntry[];
transactions!: AccountTransaction[]; public transactions!: AccountTransaction[];
branch!: Branch; public branch!: Branch;
warehouse!: Warehouse; public warehouse!: Warehouse;
/** /**
* Table name * Table name
@@ -142,7 +147,7 @@ export class SaleReceipt extends BaseModel {
* Receipt total. * Receipt total.
* @returns {number} * @returns {number}
*/ */
get total() { get total(): number {
const adjustmentAmount = defaultTo(this.adjustment, 0); const adjustmentAmount = defaultTo(this.adjustment, 0);
return this.subtotal - this.discountAmount + adjustmentAmount; return this.subtotal - this.discountAmount + adjustmentAmount;
@@ -256,6 +261,9 @@ export class SaleReceipt extends BaseModel {
const { Warehouse } = require('../../Warehouses/models/Warehouse.model'); const { Warehouse } = require('../../Warehouses/models/Warehouse.model');
return { return {
/**
* Sale receipt may has a customer.
*/
customer: { customer: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Customer, modelClass: Customer,
@@ -268,6 +276,9 @@ export class SaleReceipt extends BaseModel {
}, },
}, },
/**
* Sale receipt may has a deposit account.
*/
depositAccount: { depositAccount: {
relation: Model.BelongsToOneRelation, relation: Model.BelongsToOneRelation,
modelClass: Account, modelClass: Account,
@@ -277,6 +288,9 @@ export class SaleReceipt extends BaseModel {
}, },
}, },
/**
* Sale receipt may has many items entries.
*/
entries: { entries: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: ItemEntry, modelClass: ItemEntry,
@@ -290,6 +304,9 @@ export class SaleReceipt extends BaseModel {
}, },
}, },
/**
* Sale receipt may has many transactions.
*/
transactions: { transactions: {
relation: Model.HasManyRelation, relation: Model.HasManyRelation,
modelClass: AccountTransaction, 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 * @param {string} date
* @returns {string} * @returns {string}
*/ */
protected formatDateFromNow(date: string) { protected formatDateFromNow(date: moment.MomentInput) {
return date ? moment(date).fromNow(true) : ''; return date ? moment(date).fromNow(true) : '';
} }

View File

@@ -1,20 +1,12 @@
import { Model, raw, mixin } from 'objection'; import { Model, raw } 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 { Vendor } from '@/modules/Vendors/models/Vendor'; import { Vendor } from '@/modules/Vendors/models/Vendor';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model'; import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { Branch } from '@/modules/Branches/models/Branch.model'; import { Branch } from '@/modules/Branches/models/Branch.model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { BaseModel } from '@/models/Model';
import { DiscountType } from '@/common/types/Discount'; 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; vendorId: number;
amount: number; amount: number;
currencyCode: string; currencyCode: string;

View File

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

View File

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

View File

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

4
pnpm-lock.yaml generated
View File

@@ -713,6 +713,9 @@ importers:
jest: jest:
specifier: ^29.5.0 specifier: ^29.5.0
version: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2) version: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2)
mustache:
specifier: ^3.0.3
version: 3.2.1
prettier: prettier:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.3.3 version: 3.3.3
@@ -25664,7 +25667,6 @@ packages:
resolution: {integrity: sha512-RERvMFdLpaFfSRIEe632yDm5nsd0SDKn8hGmcUwswnyiE5mtdZLDybtHAz6hjJhawokF0hXvGLtx9mrQfm6FkA==} resolution: {integrity: sha512-RERvMFdLpaFfSRIEe632yDm5nsd0SDKn8hGmcUwswnyiE5mtdZLDybtHAz6hjJhawokF0hXvGLtx9mrQfm6FkA==}
engines: {npm: '>=1.4.0'} engines: {npm: '>=1.4.0'}
hasBin: true hasBin: true
dev: false
/mustache@4.2.0: /mustache@4.2.0:
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}