Merge pull request #806 from bigcapitalhq/refactor-export-module

refactor(nestjs): export module
This commit is contained in:
Ahmed Bouhuolia
2025-04-10 23:35:27 +02:00
committed by GitHub
111 changed files with 3901 additions and 1204 deletions

View File

@@ -0,0 +1,8 @@
export const ACCEPT_TYPE = {
APPLICATION_PDF: 'application/pdf',
APPLICATION_JSON: 'application/json',
APPLICATION_JSON_TABLE: 'application/json+table',
APPLICATION_XLSX: 'application/xlsx',
APPLICATION_CSV: 'application/csv',
APPLICATION_TEXT_HTML: 'application/json+html',
};

View File

@@ -164,6 +164,7 @@ export interface IModelMetaRelationField2 {
} }
export type IModelMetaField2 = IModelMetaFieldCommon2 & export type IModelMetaField2 = IModelMetaFieldCommon2 &
IModelMetaFieldWithFields &
( (
| IModelMetaFieldText | IModelMetaFieldText
| IModelMetaFieldNumber | IModelMetaFieldNumber

View File

@@ -17,7 +17,7 @@ import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { BankAccount } from '../BankingTransactions/models/BankAccount'; import { BankAccount } from '../BankingTransactions/models/BankAccount';
import { GetAccountsService } from './GetAccounts.service'; import { GetAccountsService } from './GetAccounts.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
// import { GetAccountsService } from './GetAccounts.service'; import { AccountsExportable } from './AccountsExportable.service';
const models = [RegisterTenancyModel(BankAccount)]; const models = [RegisterTenancyModel(BankAccount)];
@@ -38,7 +38,13 @@ const models = [RegisterTenancyModel(BankAccount)];
GetAccountTypesService, GetAccountTypesService,
GetAccountTransactionsService, GetAccountTransactionsService,
GetAccountsService, GetAccountsService,
AccountsExportable,
],
exports: [
AccountRepository,
CreateAccountService,
...models,
AccountsExportable,
], ],
exports: [AccountRepository, CreateAccountService, ...models],
}) })
export class AccountsModule {} export class AccountsModule {}

View File

@@ -2,8 +2,17 @@ import { AccountsApplication } from './AccountsApplication.service';
import { Exportable } from '../Export/Exportable'; import { Exportable } from '../Export/Exportable';
import { EXPORT_SIZE_LIMIT } from '../Export/constants'; import { EXPORT_SIZE_LIMIT } from '../Export/constants';
import { IAccountsFilter, IAccountsStructureType } from './Accounts.types'; import { IAccountsFilter, IAccountsStructureType } from './Accounts.types';
import { Global, Injectable } from '@nestjs/common';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { Account } from './models/Account.model';
@Injectable()
@ExportableService({ name: Account.name })
@Global()
export class AccountsExportable extends Exportable { export class AccountsExportable extends Exportable {
/**
* @param {AccountsApplication} accountsApplication
*/
constructor(private readonly accountsApplication: AccountsApplication) { constructor(private readonly accountsApplication: AccountsApplication) {
super(); super();
} }

View File

@@ -1,45 +1,43 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { Injectable } from '@nestjs/common';
// import { IAccountCreateDTO } from '@/interfaces'; import { Importable } from '../Import/Importable';
// import { CreateAccount } from './CreateAccount.service'; import { AccountsSampleData } from './AccountsImportable.SampleData';
// import { Importable } from '../Import/Importable'; import { CreateAccountDTO } from './CreateAccount.dto';
// import { AccountsSampleData } from './AccountsImportable.SampleData'; import { CreateAccountService } from './CreateAccount.service';
// @Service() @Injectable()
// export class AccountsImportable extends Importable { export class AccountsImportable extends Importable {
// @Inject() constructor(private readonly createAccountService: CreateAccountService) {
// private createAccountService: CreateAccount; super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {CreateAccountDTO} createAccountDTO - Create account dto.
// * @param {IAccountCreateDTO} createAccountDTO * @returns
// * @returns */
// */ public importable(
// public importable( createAccountDTO: CreateAccountDTO,
// tenantId: number, trx?: Knex.Transaction,
// createAccountDTO: IAccountCreateDTO, ) {
// trx?: Knex.Transaction return this.createAccountService.createAccount(
// ) { createAccountDTO,
// return this.createAccountService.createAccount( trx,
// tenantId, );
// createAccountDTO, }
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return AccountsSampleData; return AccountsSampleData;
// } }
// } }

View File

@@ -0,0 +1,199 @@
import { ACCOUNT_TYPES } from "../Accounts.constants";
export const AccountMeta = {
defaultFilterField: 'name',
defaultSort: {
sortOrder: 'DESC',
sortField: 'name',
},
importable: true,
exportable: true,
print: {
pageTitle: 'Chart of Accounts',
},
fields: {
name: {
name: 'account.field.name',
column: 'name',
fieldType: 'text',
},
description: {
name: 'account.field.description',
column: 'description',
fieldType: 'text',
},
slug: {
name: 'account.field.slug',
column: 'slug',
fieldType: 'text',
columnable: false,
filterable: false,
},
code: {
name: 'account.field.code',
column: 'code',
fieldType: 'text',
},
root_type: {
name: 'account.field.root_type',
fieldType: 'enumeration',
options: [
{ key: 'asset', label: 'Asset' },
{ key: 'liability', label: 'Liability' },
{ key: 'equity', label: 'Equity' },
{ key: 'Income', label: 'Income' },
{ key: 'expense', label: 'Expense' },
],
filterCustomQuery: RootTypeFieldFilterQuery,
sortable: false,
},
normal: {
name: 'account.field.normal',
fieldType: 'enumeration',
options: [
{ key: 'debit', label: 'account.field.normal.debit' },
{ key: 'credit', label: 'account.field.normal.credit' },
],
filterCustomQuery: NormalTypeFieldFilterQuery,
sortable: false,
},
type: {
name: 'account.field.type',
column: 'account_type',
fieldType: 'enumeration',
options: ACCOUNT_TYPES.map((accountType) => ({
label: accountType.label,
key: accountType.key,
})),
},
active: {
name: 'account.field.active',
column: 'active',
fieldType: 'boolean',
filterable: false,
},
balance: {
name: 'account.field.balance',
column: 'amount',
fieldType: 'number',
},
currency: {
name: 'account.field.currency',
column: 'currency_code',
fieldType: 'text',
filterable: false,
},
created_at: {
name: 'account.field.created_at',
column: 'created_at',
fieldType: 'date',
},
},
columns: {
name: {
name: 'account.field.name',
type: 'text',
},
code: {
name: 'account.field.code',
type: 'text',
},
rootType: {
name: 'account.field.root_type',
type: 'text',
accessor: 'accountRootType',
},
accountType: {
name: 'account.field.type',
accessor: 'accountTypeLabel',
type: 'text',
},
accountNormal: {
name: 'account.field.normal',
accessor: 'accountNormalFormatted',
},
currencyCode: {
name: 'account.field.currency',
type: 'text',
},
bankBalance: {
name: 'account.field.bank_balance',
accessor: 'bankBalanceFormatted',
type: 'text',
exportable: true,
},
balance: {
name: 'account.field.balance',
accessor: 'formattedAmount',
},
description: {
name: 'account.field.description',
type: 'text',
},
active: {
name: 'account.field.active',
type: 'boolean',
},
createdAt: {
name: 'account.field.created_at',
printable: false,
},
},
fields2: {
name: {
name: 'account.field.name',
fieldType: 'text',
unique: true,
required: true,
},
description: {
name: 'account.field.description',
fieldType: 'text',
},
code: {
name: 'account.field.code',
fieldType: 'text',
minLength: 3,
maxLength: 6,
unique: true,
importHint: 'Unique number to identify the account.',
},
accountType: {
name: 'account.field.type',
fieldType: 'enumeration',
options: ACCOUNT_TYPES.map((accountType) => ({
label: accountType.label,
key: accountType.key,
})),
required: true,
},
active: {
name: 'account.field.active',
fieldType: 'boolean',
},
currencyCode: {
name: 'account.field.currency',
fieldType: 'text',
},
parentAccountId: {
name: 'account.field.parent_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
},
},
};
/**
* Filter query of root type field .
*/
function RootTypeFieldFilterQuery(query, role) {
query.modify('filterByRootType', role.value);
}
/**
* Filter query of normal field .
*/
function NormalTypeFieldFilterQuery(query, role) {
query.modify('filterByAccountNormal', role.value);
}

View File

@@ -1,26 +1,21 @@
/* eslint-disable global-require */ /* eslint-disable global-require */
// import { mixin, Model } from 'objection';
import { castArray } from 'lodash'; import { castArray } from 'lodash';
import { Model } from 'objection';
import DependencyGraph from '@/libs/dependency-graph'; import DependencyGraph from '@/libs/dependency-graph';
import { import {
ACCOUNT_TYPES, ACCOUNT_TYPES,
getAccountsSupportsMultiCurrency, getAccountsSupportsMultiCurrency,
} from '@/constants/accounts'; } from '@/constants/accounts';
import { TenantModel } from '@/modules/System/models/TenantModel';
// import { SearchableModel } from '@/modules/Search/SearchableMdel';
// import { CustomViewBaseModel } from '@/modules/CustomViews/CustomViewBaseModel';
// import { ModelSettings } from '@/modules/Settings/ModelSettings';
import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils'; import { AccountTypesUtils } from '@/libs/accounts-utils/AccountTypesUtils';
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 { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { flatToNestedArray } from '@/utils/flat-to-nested-array'; import { flatToNestedArray } from '@/utils/flat-to-nested-array';
// import AccountSettings from './Account.Settings'; import { ExportableModel } from '../../Export/decorators/ExportableModel.decorator';
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants'; import { AccountMeta } from './Account.meta';
// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder'; import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
// import { flatToNestedArray } from 'utils';
@ExportableModel()
@InjectModelMeta(AccountMeta)
export class Account extends TenantBaseModel { export class Account extends TenantBaseModel {
public name!: string; public name!: string;
public slug!: string; public slug!: string;

View File

@@ -79,6 +79,8 @@ import { TenancyModule } from '../Tenancy/Tenancy.module';
import { LoopsModule } from '../Loops/Loops.module'; import { LoopsModule } from '../Loops/Loops.module';
import { AttachmentsModule } from '../Attachments/Attachment.module'; import { AttachmentsModule } from '../Attachments/Attachment.module';
import { S3Module } from '../S3/S3.module'; import { S3Module } from '../S3/S3.module';
import { ExportModule } from '../Export/Export.module';
import { ImportModule } from '../Import/Import.module';
@Module({ @Module({
imports: [ imports: [
@@ -192,7 +194,9 @@ import { S3Module } from '../S3/S3.module';
PaymentServicesModule, PaymentServicesModule,
LoopsModule, LoopsModule,
AttachmentsModule, AttachmentsModule,
S3Module S3Module,
ExportModule,
ImportModule
], ],
controllers: [AppController], controllers: [AppController],
providers: [ providers: [

View File

@@ -3,12 +3,14 @@ import { CreateUncategorizedTransactionService } from './commands/CreateUncatego
import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense'; import { CategorizeTransactionAsExpense } from './commands/CategorizeTransactionAsExpense';
import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module'; import { BankingTransactionsModule } from '../BankingTransactions/BankingTransactions.module';
import { ExpensesModule } from '../Expenses/Expenses.module'; import { ExpensesModule } from '../Expenses/Expenses.module';
import { UncategorizedTransactionsImportable } from './commands/UncategorizedTransactionsImportable';
@Module({ @Module({
imports: [BankingTransactionsModule, ExpensesModule], imports: [BankingTransactionsModule, ExpensesModule],
providers: [ providers: [
CreateUncategorizedTransactionService, CreateUncategorizedTransactionService,
CategorizeTransactionAsExpense, CategorizeTransactionAsExpense,
UncategorizedTransactionsImportable
], ],
exports: [ exports: [
CreateUncategorizedTransactionService, CreateUncategorizedTransactionService,

View File

@@ -16,6 +16,8 @@ import { BillPaymentGLEntries } from './commands/BillPaymentGLEntries';
import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntriesSubscriber'; import { BillPaymentGLEntriesSubscriber } from './subscribers/BillPaymentGLEntriesSubscriber';
import { LedgerModule } from '../Ledger/Ledger.module'; import { LedgerModule } from '../Ledger/Ledger.module';
import { AccountsModule } from '../Accounts/Accounts.module'; import { AccountsModule } from '../Accounts/Accounts.module';
import { BillPaymentsExportable } from './queries/BillPaymentsExportable';
import { GetBillPayments } from '../Bills/queries/GetBillPayments';
@Module({ @Module({
imports: [LedgerModule, AccountsModule], imports: [LedgerModule, AccountsModule],
@@ -34,6 +36,8 @@ import { AccountsModule } from '../Accounts/Accounts.module';
TenancyContext, TenancyContext,
BillPaymentGLEntries, BillPaymentGLEntries,
BillPaymentGLEntriesSubscriber, BillPaymentGLEntriesSubscriber,
GetBillPayments,
BillPaymentsExportable
], ],
exports: [BillPaymentValidators, CreateBillPaymentService], exports: [BillPaymentValidators, CreateBillPaymentService],
controllers: [BillPaymentsController], controllers: [BillPaymentsController],

View File

@@ -20,12 +20,12 @@ export class BillPaymentsApplication {
private deleteBillPaymentService: DeleteBillPayment, private deleteBillPaymentService: DeleteBillPayment,
private getBillPaymentService: GetBillPayment, private getBillPaymentService: GetBillPayment,
private getPaymentBillsService: GetPaymentBills, private getPaymentBillsService: GetPaymentBills,
// private getBillPaymentsService: GetBillPayments, private getBillPaymentsService: GetBillPayments,
) {} ) {}
/** /**
* Creates a bill payment with associated GL entries. * Creates a bill payment with associated GL entries.
* @param {IBillPaymentDTO} billPaymentDTO * @param {IBillPaymentDTO} billPaymentDTO - Create bill payment dto.
* @returns {Promise<IBillPayment>} * @returns {Promise<IBillPayment>}
*/ */
public createBillPayment(billPaymentDTO: CreateBillPaymentDto) { public createBillPayment(billPaymentDTO: CreateBillPaymentDto) {
@@ -34,7 +34,7 @@ export class BillPaymentsApplication {
/** /**
* Delets the given bill payment with associated GL entries. * Delets the given bill payment with associated GL entries.
* @param {number} billPaymentId * @param {number} billPaymentId - Bill payment id.
*/ */
public deleteBillPayment(billPaymentId: number) { public deleteBillPayment(billPaymentId: number) {
return this.deleteBillPaymentService.deleteBillPayment(billPaymentId); return this.deleteBillPaymentService.deleteBillPayment(billPaymentId);
@@ -58,13 +58,10 @@ export class BillPaymentsApplication {
/** /**
* Retrieves bill payments list. * Retrieves bill payments list.
* @param {number} tenantId
* @param filterDTO
* @returns
*/ */
// public getBillPayments(filterDTO: IBillPaymentsFilter) { public getBillPayments() {
// return this.getBillPaymentsService.getBillPayments(filterDTO); // return this.getBillPaymentsService.getBillPayments(filterDTO);
// } }
/** /**
* Retrieve specific bill payment. * Retrieve specific bill payment.

View File

@@ -1,34 +0,0 @@
// import { Inject, Service } from 'typedi';
// import { Exportable } from '@/services/Export/Exportable';
// import { BillPaymentsApplication } from './BillPaymentsApplication';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants';
// @Service()
// export class BillPaymentExportable extends Exportable {
// @Inject()
// private billPaymentsApplication: BillPaymentsApplication;
// /**
// * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId
// * @returns
// */
// public exportable(tenantId: number, query: any) {
// const filterQuery = (builder) => {
// builder.withGraphFetched('entries.bill');
// builder.withGraphFetched('branch');
// };
// const parsedQuery = {
// sortOrder: 'desc',
// columnSortBy: 'created_at',
// ...query,
// page: 1,
// pageSize: EXPORT_SIZE_LIMIT,
// filterQuery
// } as any;
// return this.billPaymentsApplication
// .getBillPayments(tenantId, parsedQuery)
// .then((output) => output.billPayments);
// }
// }

View File

@@ -0,0 +1,42 @@
import { Injectable } from "@nestjs/common";
import { BillPaymentsApplication } from "../BillPaymentsApplication.service";
import { Exportable } from "@/modules/Export/Exportable";
import { EXPORT_SIZE_LIMIT } from "@/modules/Export/constants";
import { ExportableService } from "@/modules/Export/decorators/ExportableModel.decorator";
import { BillPayment } from "../models/BillPayment";
@Injectable()
@ExportableService({ name: BillPayment.name })
export class BillPaymentsExportable extends Exportable {
constructor(
private readonly billPaymentsApplication: BillPaymentsApplication
) {
super();
}
/**
* Retrieves the accounts data to exportable sheet.
* @param {number} tenantId
* @returns
*/
public async exportable(query: any) {
const filterQuery = (builder) => {
builder.withGraphFetched('entries.bill');
builder.withGraphFetched('branch');
};
const parsedQuery = {
sortOrder: 'desc',
columnSortBy: 'created_at',
...query,
page: 1,
pageSize: EXPORT_SIZE_LIMIT,
filterQuery
} as any;
return [];
// return this.billPaymentsApplication
// .billPayments(tenantId, parsedQuery)
// .then((output) => output.billPayments);
}
}

View File

@@ -26,6 +26,8 @@ import { BillInventoryTransactions } from './commands/BillInventoryTransactions'
import { GetBillsService } from './queries/GetBills.service'; import { GetBillsService } from './queries/GetBills.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module'; import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
import { BillsExportable } from './commands/BillsExportable';
import { BillsImportable } from './commands/BillsImportable';
@Module({ @Module({
imports: [ imports: [
@@ -57,7 +59,10 @@ import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
BillGLEntriesSubscriber, BillGLEntriesSubscriber,
BillInventoryTransactions, BillInventoryTransactions,
BillWriteInventoryTransactionsSubscriber, BillWriteInventoryTransactionsSubscriber,
BillsExportable,
BillsImportable
], ],
controllers: [BillsController], controllers: [BillsController],
exports: [BillsExportable, BillsImportable],
}) })
export class BillsModule {} export class BillsModule {}

View File

@@ -1,37 +1,38 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { BillsApplication } from '../Bills.application';
// import { IBillsFilter } from '@/interfaces'; import { Injectable } from '@nestjs/common';
// import { Exportable } from '@/services/Export/Exportable'; import { Exportable } from '@/modules/Export/Exportable';
// import { BillsApplication } from '../Bills.application'; import { IBillsFilter } from '../Bills.types';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { EXPORT_SIZE_LIMIT } from '@/modules/Export/constants';
// import Objection from 'objection'; import { ExportableService } from '@/modules/Export/decorators/ExportableModel.decorator';
import { Bill } from '../models/Bill';
// @Service() @Injectable()
// export class BillsExportable extends Exportable { @ExportableService({ name: Bill.name })
// @Inject() export class BillsExportable extends Exportable {
// private billsApplication: BillsApplication; constructor(private readonly billsApplication: BillsApplication) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId */
// * @returns public exportable(query: IBillsFilter) {
// */ const filterQuery = (query) => {
// public exportable(tenantId: number, query: IBillsFilter) { query.withGraphFetched('branch');
// const filterQuery = (query) => { query.withGraphFetched('warehouse');
// query.withGraphFetched('branch'); };
// query.withGraphFetched('warehouse'); const parsedQuery = {
// }; sortOrder: 'desc',
// const parsedQuery = { columnSortBy: 'created_at',
// sortOrder: 'desc', ...query,
// columnSortBy: 'created_at', page: 1,
// ...query, pageSize: EXPORT_SIZE_LIMIT,
// page: 1, filterQuery,
// pageSize: EXPORT_SIZE_LIMIT, } as IBillsFilter;
// filterQuery,
// } as IBillsFilter;
// return this.billsApplication return this.billsApplication
// .getBills(tenantId, parsedQuery) .getBills(parsedQuery)
// .then((output) => output.bills); .then((output) => output.bills);
// } }
// } }

View File

@@ -1,46 +1,46 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { CreateBill } from './CreateBill.service';
// import { Importable } from '@/services/Import/Importable'; import { BillsSampleData } from '../Bills.constants';
// import { CreateBill } from './CreateBill.service'; import { Injectable } from '@nestjs/common';
// import { IBillDTO } from '@/interfaces'; import { Importable } from '@/modules/Import/Importable';
// import { BillsSampleData } from '../Bills.constants'; import { CreateBillDto } from '../dtos/Bill.dto';
// @Service() @Injectable()
// export class BillsImportable extends Importable { export class BillsImportable extends Importable {
// @Inject() constructor(
// private createBillService: CreateBill; private readonly createBillService: CreateBill,
) {
super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO * @param {IAccountCreateDTO} createAccountDTO
// * @returns * @returns
// */ */
// public importable( public importable(
// tenantId: number, createBillDto: CreateBillDto,
// createAccountDTO: IBillDTO, trx?: Knex.Transaction
// trx?: Knex.Transaction ) {
// ) { return this.createBillService.createBill(
// return this.createBillService.createBill( createBillDto,
// tenantId, trx
// createAccountDTO, );
// {}, }
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return BillsSampleData; return BillsSampleData;
// } }
// } }

View File

@@ -0,0 +1,286 @@
import { Features } from "@/common/types/Features";
export const BillMeta = {
defaultFilterField: 'vendor',
defaultSort: {
sortOrder: 'DESC',
sortField: 'bill_date',
},
importable: true,
exportFlattenOn: 'entries',
exportable: true,
importAggregator: 'group',
importAggregateOn: 'entries',
importAggregateBy: 'billNumber',
print: {
pageTitle: 'Bills',
},
fields: {
vendor: {
name: 'bill.field.vendor',
column: 'vendor_id',
fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'vendor',
relationEntityLabel: 'display_name',
relationEntityKey: 'id',
},
bill_number: {
name: 'bill.field.bill_number',
column: 'bill_number',
columnable: true,
fieldType: 'text',
},
bill_date: {
name: 'bill.field.bill_date',
column: 'bill_date',
columnable: true,
fieldType: 'date',
},
due_date: {
name: 'bill.field.due_date',
column: 'due_date',
columnable: true,
fieldType: 'date',
},
reference_no: {
name: 'bill.field.reference_no',
column: 'reference_no',
columnable: true,
fieldType: 'text',
},
status: {
name: 'bill.field.status',
fieldType: 'enumeration',
columnable: true,
options: [
{ label: 'bill.field.status.paid', key: 'paid' },
{ label: 'bill.field.status.partially-paid', key: 'partially-paid' },
{ label: 'bill.field.status.overdue', key: 'overdue' },
{ label: 'bill.field.status.unpaid', key: 'unpaid' },
{ label: 'bill.field.status.opened', key: 'opened' },
{ label: 'bill.field.status.draft', key: 'draft' },
],
filterCustomQuery: StatusFieldFilterQuery,
sortCustomQuery: StatusFieldSortQuery,
},
amount: {
name: 'bill.field.amount',
column: 'amount',
fieldType: 'number',
},
payment_amount: {
name: 'bill.field.payment_amount',
column: 'payment_amount',
fieldType: 'number',
},
note: {
name: 'bill.field.note',
column: 'note',
fieldType: 'text',
},
created_at: {
name: 'bill.field.created_at',
column: 'created_at',
fieldType: 'date',
},
},
columns: {
billDate: {
name: 'Date',
accessor: 'formattedBillDate',
},
billNumber: {
name: 'Bill No.',
type: 'text',
},
referenceNo: {
name: 'Reference No.',
type: 'text',
},
dueDate: {
name: 'Due Date',
type: 'date',
accessor: 'formattedDueDate',
},
vendorId: {
name: 'Vendor',
accessor: 'vendor.displayName',
type: 'text',
},
amount: {
name: 'Amount',
accessor: 'formattedAmount',
},
exchangeRate: {
name: 'Exchange Rate',
type: 'number',
printable: false,
},
currencyCode: {
name: 'Currency Code',
type: 'text',
printable: false,
},
dueAmount: {
name: 'Due Amount',
accessor: 'formattedDueAmount',
},
paidAmount: {
name: 'Paid Amount',
accessor: 'formattedPaymentAmount',
},
note: {
name: 'Note',
type: 'text',
printable: false,
},
open: {
name: 'Open',
type: 'boolean',
printable: false,
},
entries: {
name: 'Entries',
accessor: 'entries',
type: 'collection',
collectionOf: 'object',
columns: {
itemName: {
name: 'Item Name',
accessor: 'item.name',
},
rate: {
name: 'Item Rate',
accessor: 'rateFormatted',
},
quantity: {
name: 'Item Quantity',
accessor: 'quantityFormatted',
},
description: {
name: 'Item Description',
},
amount: {
name: 'Item Amount',
accessor: 'totalFormatted',
},
},
},
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
},
fields2: {
billNumber: {
name: 'Bill No.',
fieldType: 'text',
required: true,
},
referenceNo: {
name: 'Reference No.',
fieldType: 'text',
},
billDate: {
name: 'Date',
fieldType: 'date',
required: true,
},
dueDate: {
name: 'Due Date',
fieldType: 'date',
required: true,
},
vendorId: {
name: 'Vendor',
fieldType: 'relation',
relationModel: 'Contact',
relationImportMatch: 'displayName',
required: true,
},
exchangeRate: {
name: 'Exchange Rate',
fieldType: 'number',
},
note: {
name: 'Note',
fieldType: 'text',
},
open: {
name: 'Open',
fieldType: 'boolean',
},
entries: {
name: 'Entries',
fieldType: 'collection',
collectionOf: 'object',
collectionMinLength: 1,
required: true,
fields: {
itemId: {
name: 'Item',
fieldType: 'relation',
relationModel: 'Item',
relationImportMatch: ['name', 'code'],
required: true,
importHint: 'Matches the item name or code.',
},
rate: {
name: 'Rate',
fieldType: 'number',
required: true,
},
quantity: {
name: 'Quantity',
fieldType: 'number',
required: true,
},
description: {
name: 'Line Description',
fieldType: 'text',
},
},
},
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
},
};
/**
* Status field filter custom query.
*/
function StatusFieldFilterQuery(query, role) {
query.modify('statusFilter', role.value);
}
/**
* Status field sort custom query.
*/
function StatusFieldSortQuery(query, role) {
query.modify('sortByStatus', role.order);
}

View File

@@ -1,4 +1,5 @@
import { Model, raw, mixin } from 'objection'; import type { Knex } from 'knex';
import { Model, raw } from 'objection';
import { castArray, difference, defaultTo } from 'lodash'; import { castArray, difference, defaultTo } from 'lodash';
import * as moment from 'moment'; import * as moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
@@ -12,9 +13,13 @@ import { BaseModel, PaginationQueryBuilderType } from '@/models/Model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry'; import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
import { DiscountType } from '@/common/types/Discount'; import { DiscountType } from '@/common/types/Discount';
import type { Knex, QueryBuilder } from 'knex';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { BillMeta } from './Bill.meta';
@ExportableModel()
@InjectModelMeta(BillMeta)
export class Bill extends TenantBaseModel { export class Bill extends TenantBaseModel {
public amount: number; public amount: number;
public paymentAmount: number; public paymentAmount: number;

View File

@@ -13,8 +13,7 @@ export class GetBillPayments {
/** /**
* Retrieve the specific bill associated payment transactions. * Retrieve the specific bill associated payment transactions.
* @param {number} billId * @param {number} billId - Bill id.
* @returns {}
*/ */
public getBillPayments = async (billId: number) => { public getBillPayments = async (billId: number) => {
const billsEntries = await this.billPaymentEntryModel const billsEntries = await this.billPaymentEntryModel

View File

@@ -0,0 +1,11 @@
export const BranchMeta = {
fields2: {
name: {
name: 'Name',
fieldType: 'text',
required: true,
},
},
columns: {},
fields: {}
};

View File

@@ -3,7 +3,10 @@
// import BranchMetadata from './Branch.settings'; // import BranchMetadata from './Branch.settings';
// import ModelSetting from './ModelSetting'; // import ModelSetting from './ModelSetting';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { BranchMeta } from './Branch.meta';
@InjectModelMeta(BranchMeta)
export class Branch extends BaseModel{ export class Branch extends BaseModel{
name!: string; name!: string;
code!: string; code!: string;

View File

@@ -24,6 +24,7 @@ import { LedgerModule } from '../Ledger/Ledger.module';
import { AccountsModule } from '../Accounts/Accounts.module'; import { AccountsModule } from '../Accounts/Accounts.module';
import { GetCreditNotesService } from './queries/GetCreditNotes.service'; import { GetCreditNotesService } from './queries/GetCreditNotes.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { CreditNotesExportable } from './commands/CreditNotesExportable';
@Module({ @Module({
imports: [ imports: [
@@ -36,7 +37,7 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
AutoIncrementOrdersModule, AutoIncrementOrdersModule,
LedgerModule, LedgerModule,
AccountsModule, AccountsModule,
DynamicListModule DynamicListModule,
], ],
providers: [ providers: [
CreateCreditNoteService, CreateCreditNoteService,
@@ -53,6 +54,7 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
CreditNoteBrandingTemplate, CreditNoteBrandingTemplate,
CreditNoteGLEntries, CreditNoteGLEntries,
CreditNoteGLEntriesSubscriber, CreditNoteGLEntriesSubscriber,
CreditNotesExportable,
], ],
exports: [ exports: [
CreateCreditNoteService, CreateCreditNoteService,
@@ -66,6 +68,7 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
GetCreditNoteState, GetCreditNoteState,
CreditNoteApplication, CreditNoteApplication,
CreditNoteBrandingTemplate, CreditNoteBrandingTemplate,
CreditNotesExportable,
], ],
controllers: [CreditNotesController], controllers: [CreditNotesController],
}) })

View File

@@ -1,35 +1,37 @@
// import { Inject, Service } from 'typedi'; import { Exportable } from '@/modules/Export/Exportable';
// import { ICreditNotesQueryDTO } from '@/interfaces'; import { CreditNoteApplication } from '../CreditNoteApplication.service';
// import { Exportable } from '@/services/Export/Exportable'; import { Injectable } from '@nestjs/common';
// import ListCreditNotes from '../ListCreditNotes'; import { ICreditNotesQueryDTO } from '../types/CreditNotes.types';
import { ExportableService } from '@/modules/Export/decorators/ExportableModel.decorator';
import { CreditNote } from '../models/CreditNote';
// @Service() @Injectable()
// export class CreditNotesExportable extends Exportable { @ExportableService({ name: CreditNote.name })
// @Inject() export class CreditNotesExportable extends Exportable {
// private getCreditNotes: ListCreditNotes; constructor(private readonly creditNotesApp: CreditNoteApplication) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId - * @param {IVendorCreditsQueryDTO} query -
// * @param {IVendorCreditsQueryDTO} query - */
// * @returns {} public exportable(query: ICreditNotesQueryDTO) {
// */ const filterQuery = (query) => {
// public exportable(tenantId: number, query: ICreditNotesQueryDTO) { query.withGraphFetched('branch');
// const filterQuery = (query) => { query.withGraphFetched('warehouse');
// query.withGraphFetched('branch'); };
// query.withGraphFetched('warehouse'); const parsedQuery = {
// }; sortOrder: 'desc',
// const parsedQuery = { columnSortBy: 'created_at',
// sortOrder: 'desc', ...query,
// columnSortBy: 'created_at', page: 1,
// ...query, pageSize: 12000,
// page: 1, filterQuery,
// pageSize: 12000, } as ICreditNotesQueryDTO;
// filterQuery,
// } as ICreditNotesQueryDTO;
// return this.getCreditNotes return this.creditNotesApp
// .getCreditNotesList(tenantId, parsedQuery) .getCreditNotes(parsedQuery)
// .then((output) => output.creditNotes); .then((output) => output.creditNotes);
// } }
// } }

View File

@@ -2,11 +2,13 @@ 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 { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; 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';
@ExportableModel()
export class CreditNote extends TenantBaseModel { export class CreditNote extends TenantBaseModel {
public amount: number; public amount: number;
public exchangeRate: number; public exchangeRate: number;

View File

@@ -1,3 +1,4 @@
import { Inject, Injectable } from '@nestjs/common';
import * as R from 'ramda'; import * as R from 'ramda';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service';
@@ -7,8 +8,6 @@ import {
} from '../types/CreditNotes.types'; } from '../types/CreditNotes.types';
import { CreditNote } from '../models/CreditNote'; import { CreditNote } from '../models/CreditNote';
import { CreditNoteTransformer } from './CreditNoteTransformer'; import { CreditNoteTransformer } from './CreditNoteTransformer';
import { Inject } from '@nestjs/common';
import { Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()

View File

@@ -12,9 +12,13 @@ import { CreateEditCustomerDTO } from './commands/CreateEditCustomerDTO.service'
import { CustomersController } from './Customers.controller'; import { CustomersController } from './Customers.controller';
import { CustomersApplication } from './CustomersApplication.service'; import { CustomersApplication } from './CustomersApplication.service';
import { DeleteCustomer } from './commands/DeleteCustomer.service'; import { DeleteCustomer } from './commands/DeleteCustomer.service';
import { CustomersExportable } from './CustomersExportable';
import { CustomersImportable } from './CustomersImportable';
import { GetCustomers } from './queries/GetCustomers.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
@Module({ @Module({
imports: [TenancyDatabaseModule], imports: [TenancyDatabaseModule, DynamicListModule],
controllers: [CustomersController], controllers: [CustomersController],
providers: [ providers: [
ActivateCustomer, ActivateCustomer,
@@ -29,7 +33,10 @@ import { DeleteCustomer } from './commands/DeleteCustomer.service';
DeleteCustomer, DeleteCustomer,
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
GetCustomerService GetCustomerService,
CustomersExportable,
CustomersImportable,
GetCustomers
], ],
}) })
export class CustomersModule {} export class CustomersModule {}

View File

@@ -4,9 +4,10 @@ import { CreateCustomer } from './commands/CreateCustomer.service';
import { EditCustomer } from './commands/EditCustomer.service'; import { EditCustomer } from './commands/EditCustomer.service';
import { DeleteCustomer } from './commands/DeleteCustomer.service'; import { DeleteCustomer } from './commands/DeleteCustomer.service';
import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service'; import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service';
import { ICustomerOpeningBalanceEditDTO } from './types/Customers.types'; import { ICustomerOpeningBalanceEditDTO, ICustomersFilter } from './types/Customers.types';
import { CreateCustomerDto } from './dtos/CreateCustomer.dto'; import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
import { EditCustomerDto } from './dtos/EditCustomer.dto'; import { EditCustomerDto } from './dtos/EditCustomer.dto';
import { GetCustomers } from './queries/GetCustomers.service';
@Injectable() @Injectable()
export class CustomersApplication { export class CustomersApplication {
@@ -16,13 +17,12 @@ export class CustomersApplication {
private editCustomerService: EditCustomer, private editCustomerService: EditCustomer,
private deleteCustomerService: DeleteCustomer, private deleteCustomerService: DeleteCustomer,
private editOpeningBalanceService: EditOpeningBalanceCustomer, private editOpeningBalanceService: EditOpeningBalanceCustomer,
// private getCustomersService: GetCustomers, private getCustomersService: GetCustomers,
) {} ) {}
/** /**
* Retrieves the given customer details. * Retrieves the given customer details.
* @param {number} tenantId * @param {number} customerId - Customer id.
* @param {number} customerId
*/ */
public getCustomer = (customerId: number) => { public getCustomer = (customerId: number) => {
return this.getCustomerService.getCustomer(customerId); return this.getCustomerService.getCustomer(customerId);
@@ -30,7 +30,7 @@ export class CustomersApplication {
/** /**
* Creates a new customer. * Creates a new customer.
* @param {ICustomerNewDTO} customerDTO * @param {ICustomerNewDTO} customerDTO - Create customer dto.
* @returns {Promise<ICustomer>} * @returns {Promise<ICustomer>}
*/ */
public createCustomer = (customerDTO: CreateCustomerDto) => { public createCustomer = (customerDTO: CreateCustomerDto) => {
@@ -49,9 +49,7 @@ export class CustomersApplication {
/** /**
* Deletes the given customer and associated transactions. * Deletes the given customer and associated transactions.
* @param {number} tenantId * @param {number} customerId - Customer id.
* @param {number} customerId
* @param {ISystemUser} authorizedUser
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public deleteCustomer = (customerId: number) => { public deleteCustomer = (customerId: number) => {
@@ -60,9 +58,8 @@ export class CustomersApplication {
/** /**
* Changes the opening balance of the given customer. * Changes the opening balance of the given customer.
* @param {number} tenantId * @param {number} customerId - Customer id.
* @param {number} customerId * @param {Date|string} openingBalanceEditDTO - Opening balance edit dto.
* @param {Date|string} openingBalanceEditDTO
* @returns {Promise<ICustomer>} * @returns {Promise<ICustomer>}
*/ */
public editOpeningBalance = ( public editOpeningBalance = (
@@ -77,10 +74,9 @@ export class CustomersApplication {
/** /**
* Retrieve customers paginated list. * Retrieve customers paginated list.
* @param {number} tenantId - Tenant id.
* @param {ICustomersFilter} filter - Cusotmers filter. * @param {ICustomersFilter} filter - Cusotmers filter.
*/ */
// public getCustomers = (filterDTO: ICustomersFilter) => { public getCustomers = (filterDTO: ICustomersFilter) => {
// return this.getCustomersService.getCustomersList(filterDTO); return this.getCustomersService.getCustomersList(filterDTO);
// }; };
} }

View File

@@ -1,30 +1,34 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import { IItemsFilter } from '@/interfaces'; import { CustomersApplication } from './CustomersApplication.service';
// import { CustomersApplication } from './CustomersApplication'; import { IItemsFilter } from '../Items/types/Items.types';
// import { Exportable } from '@/services/Export/Exportable'; import { EXPORT_SIZE_LIMIT } from '../Export/constants';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { Exportable } from '../Export/Exportable';
import { ICustomersFilter } from './types/Customers.types';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { Customer } from './models/Customer';
// @Service() @Injectable()
// export class CustomersExportable extends Exportable { @ExportableService({ name: Customer.name })
// @Inject() export class CustomersExportable extends Exportable {
// private customersApplication: CustomersApplication; constructor(private readonly customersApplication: CustomersApplication) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId * @param {ICustomersFilter} query - Customers query.
// * @returns */
// */ public exportable(query: ICustomersFilter) {
// public exportable(tenantId: number, query: IItemsFilter) { const parsedQuery = {
// const parsedQuery = { sortOrder: 'DESC',
// sortOrder: 'DESC', columnSortBy: 'created_at',
// columnSortBy: 'created_at', ...query,
// ...query, page: 1,
// page: 1, pageSize: EXPORT_SIZE_LIMIT,
// pageSize: EXPORT_SIZE_LIMIT, } as IItemsFilter;
// } as IItemsFilter;
// return this.customersApplication return this.customersApplication
// .getCustomers(tenantId, parsedQuery) .getCustomers(parsedQuery)
// .then((output) => output.customers); .then((output) => output.customers);
// } }
// } }

View File

@@ -1,34 +1,34 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Importable } from '@/services/Import/Importable'; import { CustomersSampleData } from './_SampleData';
// import { CreateCustomer } from './CRUD/CreateCustomer'; import { Injectable } from '@nestjs/common';
// import { Knex } from 'knex'; import { Importable } from '../Import/Importable';
// import { ICustomer, ICustomerNewDTO } from '@/interfaces'; import { CreateCustomer } from './commands/CreateCustomer.service';
// import { CustomersSampleData } from './_SampleData'; import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
// @Service() @Injectable()
// export class CustomersImportable extends Importable { export class CustomersImportable extends Importable {
// @Inject() constructor(private readonly createCustomerService: CreateCustomer) {
// private createCustomerService: CreateCustomer; super();
}
// /** /**
// * Mapps the imported data to create a new customer service. * Mapps the imported data to create a new customer service.
// * @param {number} tenantId * @param {number} tenantId
// * @param {ICustomerNewDTO} createDTO * @param {ICustomerNewDTO} createDTO
// * @param {Knex.Transaction} trx * @param {Knex.Transaction} trx
// * @returns {Promise<void>} * @returns {Promise<void>}
// */ */
// public async importable( public async importable(
// tenantId: number, createDTO: CreateCustomerDto,
// createDTO: ICustomerNewDTO, trx?: Knex.Transaction<any, any[]>,
// trx?: Knex.Transaction<any, any[]> ): Promise<void> {
// ): Promise<void> { await this.createCustomerService.createCustomer(createDTO, trx);
// await this.createCustomerService.createCustomer(tenantId, createDTO, trx); }
// }
// /** /**
// * Retrieves the sample data of customers used to download sample sheet. * Retrieves the sample data of customers used to download sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return CustomersSampleData; return CustomersSampleData;
// } }
// } }

View File

@@ -0,0 +1,422 @@
export const CustomerMeta = {
importable: true,
exportable: true,
defaultFilterField: 'displayName',
defaultSort: {
sortOrder: 'DESC',
sortField: 'created_at',
},
print: {
pageTitle: 'Customers',
},
fields: {
first_name: {
name: 'vendor.field.first_name',
column: 'first_name',
fieldType: 'text',
},
last_name: {
name: 'vendor.field.last_name',
column: 'last_name',
fieldType: 'text',
},
display_name: {
name: 'vendor.field.display_name',
column: 'display_name',
fieldType: 'text',
},
email: {
name: 'vendor.field.email',
column: 'email',
fieldType: 'text',
},
work_phone: {
name: 'vendor.field.work_phone',
column: 'work_phone',
fieldType: 'text',
},
personal_phone: {
name: 'vendor.field.personal_pone',
column: 'personal_phone',
fieldType: 'text',
},
company_name: {
name: 'vendor.field.company_name',
column: 'company_name',
fieldType: 'text',
},
website: {
name: 'vendor.field.website',
column: 'website',
fieldType: 'text',
},
created_at: {
name: 'vendor.field.created_at',
column: 'created_at',
fieldType: 'date',
},
balance: {
name: 'vendor.field.balance',
column: 'balance',
fieldType: 'number',
},
opening_balance: {
name: 'vendor.field.opening_balance',
column: 'opening_balance',
fieldType: 'number',
},
opening_balance_at: {
name: 'vendor.field.opening_balance_at',
column: 'opening_balance_at',
fieldType: 'date',
},
currency_code: {
name: 'vendor.field.currency',
column: 'currency_code',
fieldType: 'text',
},
status: {
name: 'vendor.field.status',
type: 'enumeration',
options: [
{ key: 'overdue', label: 'vendor.field.status.overdue' },
{ key: 'unpaid', label: 'vendor.field.status.unpaid' },
],
filterCustomQuery: (query, role) => {
switch (role.value) {
case 'overdue':
query.modify('overdue');
break;
case 'unpaid':
query.modify('unpaid');
break;
}
},
},
},
columns: {
customerType: {
name: 'Customer Type',
type: 'text',
accessor: 'formattedCustomerType',
},
firstName: {
name: 'vendor.field.first_name',
type: 'text',
},
lastName: {
name: 'vendor.field.last_name',
type: 'text',
},
displayName: {
name: 'vendor.field.display_name',
type: 'text',
},
email: {
name: 'vendor.field.email',
type: 'text',
},
workPhone: {
name: 'vendor.field.work_phone',
type: 'text',
},
personalPhone: {
name: 'vendor.field.personal_phone',
type: 'text',
},
companyName: {
name: 'vendor.field.company_name',
type: 'text',
},
website: {
name: 'vendor.field.website',
type: 'text',
},
balance: {
name: 'vendor.field.balance',
type: 'number',
accessor: 'formattedBalance',
},
openingBalance: {
name: 'vendor.field.opening_balance',
type: 'number',
printable: false,
},
openingBalanceAt: {
name: 'vendor.field.opening_balance_at',
type: 'date',
printable: false,
accessor: 'formattedOpeningBalanceAt'
},
currencyCode: {
name: 'vendor.field.currency',
type: 'text',
printable: false,
},
status: {
name: 'vendor.field.status',
printable: false,
},
note: {
name: 'vendor.field.note',
printable: false,
},
// Billing Address
billingAddress1: {
name: 'Billing Address 1',
column: 'billing_address1',
type: 'text',
printable: false,
},
billingAddress2: {
name: 'Billing Address 2',
column: 'billing_address2',
type: 'text',
printable: false,
},
billingAddressCity: {
name: 'Billing Address City',
column: 'billing_address_city',
type: 'text',
printable: false,
},
billingAddressCountry: {
name: 'Billing Address Country',
column: 'billing_address_country',
type: 'text',
printable: false,
},
billingAddressPostcode: {
name: 'Billing Address Postcode',
column: 'billing_address_postcode',
type: 'text',
printable: false,
},
billingAddressState: {
name: 'Billing Address State',
column: 'billing_address_state',
type: 'text',
printable: false,
},
billingAddressPhone: {
name: 'Billing Address Phone',
column: 'billing_address_phone',
type: 'text',
printable: false,
},
// Shipping Address
shippingAddress1: {
name: 'Shipping Address 1',
column: 'shipping_address1',
type: 'text',
printable: false,
},
shippingAddress2: {
name: 'Shipping Address 2',
column: 'shipping_address2',
type: 'text',
printable: false,
},
shippingAddressCity: {
name: 'Shipping Address City',
column: 'shipping_address_city',
type: 'text',
printable: false,
},
shippingAddressCountry: {
name: 'Shipping Address Country',
column: 'shipping_address_country',
type: 'text',
printable: false,
},
shippingAddressPostcode: {
name: 'Shipping Address Postcode',
column: 'shipping_address_postcode',
type: 'text',
printable: false,
},
shippingAddressPhone: {
name: 'Shipping Address Phone',
column: 'shipping_address_phone',
type: 'text',
printable: false,
},
shippingAddressState: {
name: 'Shipping Address State',
column: 'shipping_address_state',
type: 'text',
printable: false,
},
createdAt: {
name: 'vendor.field.created_at',
type: 'date',
printable: false,
},
},
fields2: {
customerType: {
name: 'Customer Type',
fieldType: 'enumeration',
options: [
{ key: 'business', label: 'Business' },
{ key: 'individual', label: 'Individual' },
],
required: true,
},
firstName: {
name: 'customer.field.first_name',
column: 'first_name',
fieldType: 'text',
},
lastName: {
name: 'customer.field.last_name',
column: 'last_name',
fieldType: 'text',
},
displayName: {
name: 'customer.field.display_name',
column: 'display_name',
fieldType: 'text',
required: true,
},
email: {
name: 'customer.field.email',
column: 'email',
fieldType: 'text',
},
workPhone: {
name: 'customer.field.work_phone',
column: 'work_phone',
fieldType: 'text',
},
personalPhone: {
name: 'customer.field.personal_phone',
column: 'personal_phone',
fieldType: 'text',
},
companyName: {
name: 'customer.field.company_name',
column: 'company_name',
fieldType: 'text',
},
website: {
name: 'customer.field.website',
column: 'website',
fieldType: 'url',
},
openingBalance: {
name: 'customer.field.opening_balance',
column: 'opening_balance',
fieldType: 'number',
},
openingBalanceAt: {
name: 'customer.field.opening_balance_at',
column: 'opening_balance_at',
filterable: false,
fieldType: 'date',
},
openingBalanceExchangeRate: {
name: 'Opening Balance Ex. Rate',
column: 'opening_balance_exchange_rate',
fieldType: 'number',
},
currencyCode: {
name: 'customer.field.currency',
column: 'currency_code',
fieldType: 'text',
},
note: {
name: 'Note',
column: 'note',
fieldType: 'text',
},
active: {
name: 'Active',
column: 'active',
fieldType: 'boolean',
},
// Billing Address
billingAddress1: {
name: 'Billing Address 1',
column: 'billing_address1',
fieldType: 'text',
},
billingAddress2: {
name: 'Billing Address 2',
column: 'billing_address2',
fieldType: 'text',
},
billingAddressCity: {
name: 'Billing Address City',
column: 'billing_address_city',
fieldType: 'text',
},
billingAddressCountry: {
name: 'Billing Address Country',
column: 'billing_address_country',
fieldType: 'text',
},
billingAddressPostcode: {
name: 'Billing Address Postcode',
column: 'billing_address_postcode',
fieldType: 'text',
},
billingAddressState: {
name: 'Billing Address State',
column: 'billing_address_state',
fieldType: 'text',
},
billingAddressPhone: {
name: 'Billing Address Phone',
column: 'billing_address_phone',
fieldType: 'text',
},
// Shipping Address
shippingAddress1: {
name: 'Shipping Address 1',
column: 'shipping_address1',
fieldType: 'text',
},
shippingAddress2: {
name: 'Shipping Address 2',
column: 'shipping_address2',
fieldType: 'text',
},
shippingAddressCity: {
name: 'Shipping Address City',
column: 'shipping_address_city',
fieldType: 'text',
},
shippingAddressCountry: {
name: 'Shipping Address Country',
column: 'shipping_address_country',
fieldType: 'text',
},
shippingAddressPostcode: {
name: 'Shipping Address Postcode',
column: 'shipping_address_postcode',
fieldType: 'text',
},
shippingAddressPhone: {
name: 'Shipping Address Phone',
column: 'shipping_address_phone',
fieldType: 'text',
},
shippingAddressState: {
name: 'Shipping Address State',
column: 'shipping_address_state',
fieldType: 'text',
},
},
};
function statusFieldFilterQuery(query, role) {
switch (role.value) {
case 'overdue':
query.modify('overdue');
break;
case 'unpaid':
query.modify('unpaid');
break;
}
}

View File

@@ -1,6 +1,9 @@
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { CustomerMeta } from './Customer.meta';
@InjectModelMeta(CustomerMeta)
export class Customer extends TenantBaseModel{ export class Customer extends TenantBaseModel{
contactService: string; contactService: string;
contactType: string; contactType: string;

View File

@@ -17,11 +17,13 @@ import { LedgerModule } from '../Ledger/Ledger.module';
import { BranchesModule } from '../Branches/Branches.module'; import { BranchesModule } from '../Branches/Branches.module';
import { GetExpensesService } from './queries/GetExpenses.service'; import { GetExpensesService } from './queries/GetExpenses.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { ExpensesExportable } from './ExpensesExportable';
import { ExpensesImportable } from './ExpensesImportable';
@Module({ @Module({
imports: [LedgerModule, BranchesModule, DynamicListModule], imports: [LedgerModule, BranchesModule, DynamicListModule],
controllers: [ExpensesController], controllers: [ExpensesController],
exports: [CreateExpense], exports: [CreateExpense, ExpensesExportable, ExpensesImportable],
providers: [ providers: [
CreateExpense, CreateExpense,
ExpenseDTOTransformer, ExpenseDTOTransformer,
@@ -37,6 +39,8 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
ExpenseGLEntriesStorageService, ExpenseGLEntriesStorageService,
ExpenseGLEntriesService, ExpenseGLEntriesService,
GetExpensesService, GetExpensesService,
ExpensesExportable,
ExpensesImportable,
], ],
}) })
export class ExpensesModule {} export class ExpensesModule {}

View File

@@ -1,34 +1,39 @@
// import { Inject, Service } from 'typedi'; import { Exportable } from '../Export/Exportable';
// import { Exportable } from '../Export/Exportable'; import { ExpensesApplication } from './ExpensesApplication.service';
// import { IExpensesFilter } from '@/interfaces'; import { EXPORT_SIZE_LIMIT } from '../Export/constants';
// import { ExpensesApplication } from './ExpensesApplication.service'; import { Injectable } from '@nestjs/common';
// import { EXPORT_SIZE_LIMIT } from '../Export/constants'; import { IExpensesFilter } from './Expenses.types';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { Expense } from './models/Expense.model';
// @Service() @Injectable()
// export class ExpensesExportable extends Exportable { @ExportableService({ name: Expense.name })
// @Inject() export class ExpensesExportable extends Exportable {
// private expensesApplication: ExpensesApplication; constructor(
private readonly expensesApplication: ExpensesApplication,
) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId * @param {IExpensesFilter}
// * @returns */
// */ public exportable(query: IExpensesFilter) {
// public exportable(tenantId: number, query: IExpensesFilter) { const filterQuery = (query) => {
// const filterQuery = (query) => { query.withGraphFetched('branch');
// query.withGraphFetched('branch'); };
// }; const parsedQuery = {
// const parsedQuery = { sortOrder: 'desc',
// sortOrder: 'desc', columnSortBy: 'created_at',
// columnSortBy: 'created_at', ...query,
// ...query, page: 1,
// page: 1, pageSize: EXPORT_SIZE_LIMIT,
// pageSize: EXPORT_SIZE_LIMIT, filterQuery,
// filterQuery, } as IExpensesFilter;
// } as IExpensesFilter;
// return this.expensesApplication return this.expensesApplication
// .getExpenses(tenantId, parsedQuery) .getExpenses(parsedQuery)
// .then((output) => output.expenses); .then((output) => output.expenses);
// } }
// } }

View File

@@ -1,46 +1,41 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { Importable } from '../Import/Importable';
// import { IExpenseCreateDTO } from '@/interfaces'; import { ExpensesSampleData } from './constants';
// import { Importable } from '../Import/Importable'; import { Injectable } from '@nestjs/common';
// import { CreateExpense } from './CRUD/CreateExpense.service'; import { CreateExpense } from './commands/CreateExpense.service';
// import { ExpensesSampleData } from './constants'; import { CreateExpenseDto } from './dtos/Expense.dto';
// @Service() @Injectable()
// export class ExpensesImportable extends Importable { export class ExpensesImportable extends Importable {
// @Inject() constructor(private readonly createExpenseService: CreateExpense) {
// private createExpenseService: CreateExpense; super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO * @param {IAccountCreateDTO} createAccountDTO
// * @returns * @returns
// */ */
// public importable( public importable(
// tenantId: number, createAccountDTO: CreateExpenseDto,
// createAccountDTO: IExpenseCreateDTO, trx?: Knex.Transaction,
// trx?: Knex.Transaction ) {
// ) { return this.createExpenseService.newExpense(createAccountDTO, trx);
// return this.createExpenseService.newExpense( }
// tenantId,
// createAccountDTO,
// {},
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return ExpensesSampleData; return ExpensesSampleData;
// } }
// } }

View File

@@ -3,7 +3,9 @@ import * as moment from 'moment';
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'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
@ExportableModel()
export class Expense extends TenantBaseModel { export class Expense extends TenantBaseModel {
totalAmount!: number; totalAmount!: number;
currencyCode!: string; currencyCode!: string;

View File

@@ -1,20 +1,24 @@
import { Response } from 'express'; import { Response } from 'express';
import { convertAcceptFormatToFormat } from './_utils'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Controller, Headers, Query, Res } from '@nestjs/common'; import { Controller, Get, Headers, Query, Res } from '@nestjs/common';
import { AcceptType } from '@/constants/accept-type';
import { ExportQuery } from './dtos/ExportQuery.dto'; import { ExportQuery } from './dtos/ExportQuery.dto';
import { ExportResourceService } from './ExportService'; import { ExportResourceService } from './ExportService';
import { AcceptType } from '@/constants/accept-type'; import { convertAcceptFormatToFormat } from './Export.utils';
@Controller('/export') @Controller('/export')
@ApiTags('export')
export class ExportController { export class ExportController {
constructor(private readonly exportResourceApp: ExportResourceService) {} constructor(private readonly exportResourceApp: ExportResourceService) {}
@Get()
@ApiOperation({ summary: 'Retrieves exported the given resource.' })
async export( async export(
@Query() query: ExportQuery, @Query() query: ExportQuery,
@Res() res: Response, @Res() res: Response,
@Headers('accept') acceptHeader: string, @Headers('accept') acceptHeader: string,
) { ) {
const applicationFormat = convertAcceptFormatToFormat(acceptType); const applicationFormat = convertAcceptFormatToFormat(acceptHeader);
const data = await this.exportResourceApp.export( const data = await this.exportResourceApp.export(
query.resource, query.resource,

View File

@@ -4,9 +4,32 @@ import { ExportResourceService } from './ExportService';
import { ExportPdf } from './ExportPdf'; import { ExportPdf } from './ExportPdf';
import { ExportAls } from './ExportAls'; import { ExportAls } from './ExportAls';
import { ExportApplication } from './ExportApplication'; import { ExportApplication } from './ExportApplication';
import { ResourceModule } from '../Resource/Resource.module';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { ImportModel } from '../Import/models/Import';
import { ExportableRegistry } from './ExportRegistery';
import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectable.module';
import { ChromiumlyTenancyModule } from '../ChromiumlyTenancy/ChromiumlyTenancy.module';
import { AccountsModule } from '../Accounts/Accounts.module';
const models = [RegisterTenancyModel(ImportModel)];
@Module({ @Module({
providers: [ExportResourceService, ExportPdf, ExportAls, ExportApplication], imports: [
...models,
ResourceModule,
TemplateInjectableModule,
ChromiumlyTenancyModule,
AccountsModule
],
providers: [
ExportResourceService,
ExportPdf,
ExportAls,
ExportApplication,
ExportableRegistry
],
exports: [...models],
controllers: [ExportController], controllers: [ExportController],
}) })
export class ExportModule {} export class ExportModule {}

View File

@@ -0,0 +1,14 @@
import { ACCEPT_TYPE } from '@/common/constants/http.constants';
import { ExportFormat } from './common';
export const convertAcceptFormatToFormat = (accept: string): ExportFormat => {
switch (accept) {
default:
case ACCEPT_TYPE.APPLICATION_CSV:
return ExportFormat.Csv;
case ACCEPT_TYPE.APPLICATION_PDF:
return ExportFormat.Pdf;
case ACCEPT_TYPE.APPLICATION_XLSX:
return ExportFormat.Xlsx;
}
};

View File

@@ -15,7 +15,7 @@ export class ExportAls {
* @returns The result of the callback function. * @returns The result of the callback function.
*/ */
public run<T>(callback: () => T): T { public run<T>(callback: () => T): T {
return this.als.run<T>(new Map(), () => { return this.als.run<T, []>(new Map(), () => {
this.markAsExport(); this.markAsExport();
return callback(); return callback();

View File

@@ -1,75 +0,0 @@
// import Container, { Service } from 'typedi';
// import { AccountsExportable } from '../Accounts/AccountsExportable';
// import { ExportableRegistry } from './ExportRegistery';
// import { ItemsExportable } from '../Items/ItemsExportable';
// import { CustomersExportable } from '../Contacts/Customers/CustomersExportable';
// import { VendorsExportable } from '../Contacts/Vendors/VendorsExportable';
// import { ExpensesExportable } from '../Expenses/ExpensesExportable';
// import { SaleInvoicesExportable } from '../Sales/Invoices/SaleInvoicesExportable';
// import { SaleEstimatesExportable } from '../Sales/Estimates/SaleEstimatesExportable';
// import { SaleReceiptsExportable } from '../Sales/Receipts/SaleReceiptsExportable';
// import { BillsExportable } from '../Purchases/Bills/BillsExportable';
// import { PaymentsReceivedExportable } from '../Sales/PaymentReceived/PaymentsReceivedExportable';
// import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExportable';
// import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExportable';
// import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable';
// import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable';
// import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable';
// import { TaxRatesExportable } from '../TaxRates/TaxRatesExportable';
import { Injectable } from "@nestjs/common";
import { ExportableRegistry } from "./ExportRegistery";
import { AccountsExportable } from "../Accounts/AccountsExportable.service";
@Injectable()
export class ExportableResources {
constructor(
private readonly exportRegistry: ExportableRegistry,
) {
this.boot();
}
/**
* Importable instances.
*/
private importables = [
{ resource: 'Account', exportable: AccountsExportable },
// { resource: 'Item', exportable: ItemsExportable },
// { resource: 'ItemCategory', exportable: ItemCategoriesExportable },
// { resource: 'Customer', exportable: CustomersExportable },
// { resource: 'Vendor', exportable: VendorsExportable },
// { resource: 'Expense', exportable: ExpensesExportable },
// { resource: 'SaleInvoice', exportable: SaleInvoicesExportable },
// { resource: 'SaleEstimate', exportable: SaleEstimatesExportable },
// { resource: 'SaleReceipt', exportable: SaleReceiptsExportable },
// { resource: 'Bill', exportable: BillsExportable },
// { resource: 'PaymentReceive', exportable: PaymentsReceivedExportable },
// { resource: 'BillPayment', exportable: BillPaymentExportable },
// { resource: 'ManualJournal', exportable: ManualJournalsExportable },
// { resource: 'CreditNote', exportable: CreditNotesExportable },
// { resource: 'VendorCredit', exportable: VendorCreditsExportable },
// { resource: 'TaxRate', exportable: TaxRatesExportable },
];
/**
*
*/
public get registry() {
return ExportableResources.registry;
}
/**
* Boots all the registered importables.
*/
public boot() {
if (!ExportableResources.registry) {
const instance = ExportableRegistry.getInstance();
this.importables.forEach((importable) => {
const importableInstance = Container.get(importable.exportable);
instance.registerExportable(importable.resource, importableInstance);
});
ExportableResources.registry = instance;
}
}
}

View File

@@ -1,39 +1,37 @@
// @ts-nocheck import { Injectable } from '@nestjs/common';
import xlsx from 'xlsx'; import * as xlsx from 'xlsx';
import * as R from 'ramda'; import * as R from 'ramda';
import { get } from 'lodash'; import { get } from 'lodash';
import { sanitizeResourceName } from '../Import/_utils'; import { sanitizeResourceName } from '../Import/_utils';
import { ExportableResources } from './ExportResources';
import { ServiceError } from '@/exceptions';
import { Errors, ExportFormat } from './common'; import { Errors, ExportFormat } from './common';
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
import { flatDataCollections, getDataAccessor } from './utils'; import { flatDataCollections, getDataAccessor } from './utils';
import { ExportPdf } from './ExportPdf'; import { ExportPdf } from './ExportPdf';
import { ExportAls } from './ExportAls'; import { ExportAls } from './ExportAls';
import { Injectable } from '@nestjs/common'; import { IModelMeta, IModelMetaColumn } from '@/interfaces/Model';
import { ServiceError } from '../Items/ServiceError';
import { ResourceService } from '../Resource/ResourceService';
import { getExportableService } from './decorators/ExportableModel.decorator';
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
@Injectable() @Injectable()
export class ExportResourceService { export class ExportResourceService {
constructor( constructor(
private readonly exportAls: ExportAls, private readonly exportAls: ExportAls,
private readonly exportPdf: ExportPdf, private readonly exportPdf: ExportPdf,
private readonly exportableResources: ExportableResources,
private readonly resourceService: ResourceService, private readonly resourceService: ResourceService,
private readonly moduleRef: ModuleRef,
) {} ) {}
/** /**
*
* @param {string} resourceName * @param {string} resourceName
* @param {ExportFormat} format * @param {ExportFormat} format
* @returns * @returns
*/ */
public async export( public async export(
resourceName: string, resourceName: string,
format: ExportFormat = ExportFormat.Csv format: ExportFormat = ExportFormat.Csv,
) { ) {
return this.exportAls.run(() => return this.exportAls.run(() => this.exportAlsRun(resourceName, format));
this.exportAlsRun(resourceName, format)
);
} }
/** /**
@@ -43,18 +41,18 @@ export class ExportResourceService {
*/ */
public async exportAlsRun( public async exportAlsRun(
resourceName: string, resourceName: string,
format: ExportFormat = ExportFormat.Csv format: ExportFormat = ExportFormat.Csv,
) { ) {
const resource = sanitizeResourceName(resourceName); const resource = sanitizeResourceName(resourceName);
const resourceMeta = this.getResourceMeta(tenantId, resource); const resourceMeta = this.getResourceMeta(resource);
const resourceColumns = this.resourceService.getResourceColumns(
tenantId, const resourceColumns = this.resourceService.getResourceColumns(resource);
resource
);
this.validateResourceMeta(resourceMeta); this.validateResourceMeta(resourceMeta);
const data = await this.getExportableData(tenantId, resource); const data = await this.getExportableData(resource);
const transformed = this.transformExportedData(tenantId, resource, data); const transformed = this.transformExportedData(resource, data);
console.log(format);
// Returns the csv, xlsx format. // Returns the csv, xlsx format.
if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) { if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) {
@@ -67,10 +65,9 @@ export class ExportResourceService {
const printableColumns = this.getPrintableColumns(resourceMeta); const printableColumns = this.getPrintableColumns(resourceMeta);
return this.exportPdf.pdf( return this.exportPdf.pdf(
tenantId,
printableColumns, printableColumns,
transformed, transformed,
resourceMeta?.print?.pageTitle resourceMeta?.print?.pageTitle,
); );
} }
} }
@@ -106,14 +103,14 @@ export class ExportResourceService {
*/ */
private transformExportedData( private transformExportedData(
resource: string, resource: string,
data: Array<Record<string, any>> data: Array<Record<string, any>>,
): Array<Record<string, any>> { ): Array<Record<string, any>> {
const resourceMeta = this.getResourceMeta(resource); const resourceMeta = this.getResourceMeta(resource);
return R.when<Array<Record<string, any>>, Array<Record<string, any>>>( return R.when<Array<Record<string, any>>, Array<Record<string, any>>>(
R.always(Boolean(resourceMeta.exportFlattenOn)), R.always(Boolean(resourceMeta.exportFlattenOn)),
(data) => flatDataCollections(data, resourceMeta.exportFlattenOn), (data) => flatDataCollections(data, resourceMeta.exportFlattenOn),
data data,
); );
} }
/** /**
@@ -123,10 +120,14 @@ export class ExportResourceService {
* @returns A promise that resolves to the exportable data. * @returns A promise that resolves to the exportable data.
*/ */
private async getExportableData(resource: string) { private async getExportableData(resource: string) {
const exportable = const exportable = getExportableService(resource);
this.exportableResources.registry.getExportable(resource); const contextId = ContextIdFactory.create();
const exportableInstance = await this.moduleRef.resolve(
return exportable.exportable({}); exportable,
contextId,
{ strict: false },
);
return exportableInstance.exportable({});
} }
/** /**
@@ -137,7 +138,7 @@ export class ExportResourceService {
private getExportableColumns(resourceColumns: any) { private getExportableColumns(resourceColumns: any) {
const processColumns = ( const processColumns = (
columns: { [key: string]: IModelMetaColumn }, columns: { [key: string]: IModelMetaColumn },
parent = '' parent = '',
) => { ) => {
return Object.entries(columns) return Object.entries(columns)
.filter(([_, value]) => value.exportable !== false) .filter(([_, value]) => value.exportable !== false)
@@ -163,9 +164,10 @@ export class ExportResourceService {
private getPrintableColumns(resourceMeta: IModelMeta) { private getPrintableColumns(resourceMeta: IModelMeta) {
const processColumns = ( const processColumns = (
columns: { [key: string]: IModelMetaColumn }, columns: { [key: string]: IModelMetaColumn },
parent = '' parent = '',
) => { ) => {
return Object.entries(columns) return Object.entries(columns)
// @ts-expect-error
.filter(([_, value]) => value.printable !== false) .filter(([_, value]) => value.printable !== false)
.flatMap(([key, value]) => { .flatMap(([key, value]) => {
if (value.type === 'collection' && value.collectionOf === 'object') { if (value.type === 'collection' && value.collectionOf === 'object') {
@@ -195,7 +197,7 @@ export class ExportResourceService {
private createWorkbook(data: any[], exportableColumns: any[]) { private createWorkbook(data: any[], exportableColumns: any[]) {
const workbook = xlsx.utils.book_new(); const workbook = xlsx.utils.book_new();
const worksheetData = data.map((item) => const worksheetData = data.map((item) =>
exportableColumns.map((col) => get(item, getDataAccessor(col))) exportableColumns.map((col) => get(item, getDataAccessor(col))),
); );
worksheetData.unshift(exportableColumns.map((col) => col.name)); worksheetData.unshift(exportableColumns.map((col) => col.name));

View File

@@ -6,7 +6,7 @@ export class Exportable {
*/ */
public async exportable( public async exportable(
query: Record<string, any>, query: Record<string, any>,
): Promise<Array<Record<string, any>>> { ): Promise<any> {
return []; return [];
} }

View File

@@ -0,0 +1,37 @@
import { Global } from "@nestjs/common";
const exportableModels = new Map<string, boolean>();
const exportableService = new Map<string, any>()
/**
* Decorator that marks a model as exportable and registers its metadata.
* @param metadata Model metadata configuration for export
*/
export function ExportableModel() {
return function (target: any) {
const modelName = target.name;
exportableModels.set(modelName, true);
};
}
export function ExportableService({ name }: { name: string }) {
return function (target: any) {
exportableService.set(name, target);
// Apply the @Global() decorator to make the service globally available
Global()(target);
};
}
/**
* Gets the registered exportable model metadata
* @param modelName Name of the model class
*/
export function getExportableModelMeta(modelName: string): boolean | undefined {
return exportableModels.get(modelName);
}
export function getExportableService(modelName: string) {
return exportableService.get(modelName);
}

View File

@@ -4,8 +4,4 @@ export class ExportQuery {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
resource: string; resource: string;
@IsString()
@IsNotEmpty()
format: string;
} }

View File

@@ -1,8 +1,40 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ImportAls } from './ImportALS'; import { ImportAls } from './ImportALS';
import { ImportSampleService } from './ImportSample';
import { ImportResourceApplication } from './ImportResourceApplication';
import { ImportDeleteExpiredFiles } from './ImportRemoveExpiredFiles';
import { ImportFileUploadService } from './ImportFileUpload';
import { ImportFileProcessCommit } from './ImportFileProcessCommit';
import { ImportFileProcess } from './ImportFileProcess';
import { ImportFilePreview } from './ImportFilePreview';
import { ImportFileMeta } from './ImportFileMeta';
import { ImportFileMapping } from './ImportFileMapping';
import { ImportFileDataValidator } from './ImportFileDataValidator';
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
import { ImportFileCommon } from './ImportFileCommon';
import { ImportableResources } from './ImportableResources';
import { ResourceModule } from '../Resource/Resource.module';
import { TenancyModule } from '../Tenancy/Tenancy.module';
import { AccountsModule } from '../Accounts/Accounts.module';
@Module({ @Module({
providers: [ImportAls], imports: [ResourceModule, TenancyModule, AccountsModule],
providers: [
ImportAls,
ImportSampleService,
ImportableResources,
ImportResourceApplication,
ImportDeleteExpiredFiles,
ImportFileUploadService,
ImportFileProcessCommit,
ImportFileProcess,
ImportFilePreview,
ImportFileMeta,
ImportFileMapping,
ImportFileDataValidator,
ImportFileDataTransformer,
ImportFileCommon,
],
exports: [ImportAls], exports: [ImportAls],
}) })
export class ImportModule {} export class ImportModule {}

View File

@@ -12,9 +12,9 @@ import {
import { getUniqueImportableValue, trimObject } from './_utils'; import { getUniqueImportableValue, trimObject } from './_utils';
import { ImportableResources } from './ImportableResources'; import { ImportableResources } from './ImportableResources';
import { ResourceService } from '../Resource/ResourceService'; import { ResourceService } from '../Resource/ResourceService';
import { Import } from '@/system/models';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ServiceError } from '../Items/ServiceError'; import { ServiceError } from '../Items/ServiceError';
import { ImportModelShape } from './models/Import';
@Injectable() @Injectable()
export class ImportFileCommon { export class ImportFileCommon {
@@ -32,7 +32,7 @@ export class ImportFileCommon {
* @returns {Promise<[ImportOperSuccess[], ImportOperError[]]>} * @returns {Promise<[ImportOperSuccess[], ImportOperError[]]>}
*/ */
public async import( public async import(
importFile: Import, importFile: ImportModelShape,
parsedData: Record<string, any>[], parsedData: Record<string, any>[],
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<[ImportOperSuccess[], ImportOperError[]]> { ): Promise<[ImportOperSuccess[], ImportOperError[]]> {
@@ -68,7 +68,6 @@ export class ImportFileCommon {
try { try {
// Run the importable function and listen to the errors. // Run the importable function and listen to the errors.
const data = await importable.importable( const data = await importable.importable(
tenantId,
transformedDTO, transformedDTO,
trx, trx,
); );
@@ -135,14 +134,13 @@ export class ImportFileCommon {
* @param {Record<string, any>} params * @param {Record<string, any>} params
*/ */
public async validateParams( public async validateParams(
tenantId: number,
resourceName: string, resourceName: string,
params: Record<string, any>, params: Record<string, any>,
) { ) {
const ImportableRegistry = this.importable.registry; const ImportableRegistry = this.importable.registry;
const importable = ImportableRegistry.getImportable(resourceName); const importable = ImportableRegistry.getImportable(resourceName);
await importable.validateParams(tenantId, params); await importable.validateParams(params);
} }
/** /**

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { fromPairs, isUndefined } from 'lodash'; import { fromPairs, isUndefined } from 'lodash';
import { import {
ImportDateFormats, ImportDateFormats,
@@ -8,15 +8,19 @@ import {
import { ResourceService } from '../Resource/ResourceService'; import { ResourceService } from '../Resource/ResourceService';
import { ServiceError } from '../Items/ServiceError'; import { ServiceError } from '../Items/ServiceError';
import { ERRORS } from './_utils'; import { ERRORS } from './_utils';
import { Import } from './models/Import'; import { ImportModel } from './models/Import';
@Injectable() @Injectable()
export class ImportFileMapping { export class ImportFileMapping {
constructor(private readonly resource: ResourceService) {} constructor(
private readonly resource: ResourceService,
@Inject(ImportModel.name)
private readonly importModel: () => typeof ImportModel,
) {}
/** /**
* Mapping the excel sheet columns with resource columns. * Mapping the excel sheet columns with resource columns.
* @param {number} tenantId
* @param {number} importId * @param {number} importId
* @param {ImportMappingAttr} maps * @param {ImportMappingAttr} maps
*/ */
@@ -24,7 +28,8 @@ export class ImportFileMapping {
importId: string, importId: string,
maps: ImportMappingAttr[], maps: ImportMappingAttr[],
): Promise<ImportFileMapPOJO> { ): Promise<ImportFileMapPOJO> {
const importFile = await Import.query() const importFile = await this.importModel()
.query()
.findOne('filename', importId) .findOne('filename', importId)
.throwIfNotFound(); .throwIfNotFound();
@@ -41,7 +46,7 @@ export class ImportFileMapping {
const mappingStringified = JSON.stringify(maps); const mappingStringified = JSON.stringify(maps);
await Import.query().findById(importFile.id).patch({ await this.importModel().query().findById(importFile.id).patch({
mapping: mappingStringified, mapping: mappingStringified,
}); });
return { return {
@@ -54,7 +59,6 @@ export class ImportFileMapping {
/** /**
* Validate the mapping attributes. * Validate the mapping attributes.
* @param {number} tenantId -
* @param {} importFile - * @param {} importFile -
* @param {ImportMappingAttr[]} maps * @param {ImportMappingAttr[]} maps
* @throws {ServiceError(ERRORS.INVALID_MAP_ATTRS)} * @throws {ServiceError(ERRORS.INVALID_MAP_ATTRS)}
@@ -116,7 +120,6 @@ export class ImportFileMapping {
/** /**
* Validates the date format mapping. * Validates the date format mapping.
* @param {number} tenantId
* @param {string} resource * @param {string} resource
* @param {ImportMappingAttr[]} maps * @param {ImportMappingAttr[]} maps
*/ */

View File

@@ -1,20 +1,29 @@
import { Import } from './models/Import'; import { ImportModel } from './models/Import';
import { ImportFileMetaTransformer } from './ImportFileMetaTransformer'; import { ImportFileMetaTransformer } from './ImportFileMetaTransformer';
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class ImportFileMeta { export class ImportFileMeta {
constructor(private readonly transformer: TransformerInjectable) {} constructor(
private readonly transformer: TransformerInjectable,
private readonly tenancyContext: TenancyContext,
@Inject(ImportModel.name)
private readonly importModel: () => typeof ImportModel,
) {}
/** /**
* Retrieves the import meta of the given import model id. * Retrieves the import meta of the given import model id.
* @param {number} tenantId
* @param {number} importId * @param {number} importId
* @returns {}
*/ */
async getImportMeta(importId: string) { async getImportMeta(importId: string) {
const importFile = await Import.query() const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
const importFile = await this.importModel()
.query()
.where('tenantId', tenantId) .where('tenantId', tenantId)
.findOne('importId', importId); .findOne('importId', importId);

View File

@@ -1,13 +1,18 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ImportFilePreviewPOJO } from './interfaces'; import { ImportFilePreviewPOJO } from './interfaces';
import { ImportFileProcess } from './ImportFileProcess'; import { ImportFileProcess } from './ImportFileProcess';
import { ImportAls } from './ImportALS'; import { ImportAls } from './ImportALS';
import { Injectable } from '@nestjs/common'; import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
@Injectable() @Injectable()
export class ImportFilePreview { export class ImportFilePreview {
constructor( constructor(
private readonly importFile: ImportFileProcess, private readonly importFile: ImportFileProcess,
private readonly importAls: ImportAls, private readonly importAls: ImportAls,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex
) {} ) {}
/** /**
@@ -27,8 +32,7 @@ export class ImportFilePreview {
* @returns {Promise<ImportFilePreviewPOJO>} * @returns {Promise<ImportFilePreviewPOJO>}
*/ */
public async previewAlsRun(importId: string): Promise<ImportFilePreviewPOJO> { public async previewAlsRun(importId: string): Promise<ImportFilePreviewPOJO> {
const knex = this.tenancy.knex(tenantId); const trx = await this.tenantKnex().transaction({ isolationLevel: 'read uncommitted' });
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
const meta = await this.importFile.import(importId, trx); const meta = await this.importFile.import(importId, trx);

View File

@@ -1,14 +1,16 @@
import { chain } from 'lodash'; import { chain } from 'lodash';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS, getUnmappedSheetColumns, readImportFile } from './_utils'; import { ERRORS, getUnmappedSheetColumns, readImportFile } from './_utils';
import { ImportFileCommon } from './ImportFileCommon'; import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataTransformer } from './ImportFileDataTransformer'; import { ImportFileDataTransformer } from './ImportFileDataTransformer';
import { ImportFilePreviewPOJO } from './interfaces'; import { ImportFilePreviewPOJO } from './interfaces';
import { parseSheetData } from './sheet_utils'; import { parseSheetData } from './sheet_utils';
import { Injectable } from '@nestjs/common';
import { ResourceService } from '../Resource/ResourceService'; import { ResourceService } from '../Resource/ResourceService';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service'; import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { ServiceError } from '../Items/ServiceError'; import { ServiceError } from '../Items/ServiceError';
import { ImportModel } from './models/Import';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class ImportFileProcess { export class ImportFileProcess {
@@ -17,6 +19,10 @@ export class ImportFileProcess {
private readonly importCommon: ImportFileCommon, private readonly importCommon: ImportFileCommon,
private readonly importParser: ImportFileDataTransformer, private readonly importParser: ImportFileDataTransformer,
private readonly uow: UnitOfWork, private readonly uow: UnitOfWork,
private readonly tenancyContext: TenancyContext,
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {} ) {}
/** /**
@@ -29,7 +35,11 @@ export class ImportFileProcess {
importId: string, importId: string,
trx?: Knex.Transaction, trx?: Knex.Transaction,
): Promise<ImportFilePreviewPOJO> { ): Promise<ImportFilePreviewPOJO> {
const importFile = await Import.query() const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
const importFile = await this.importModel
.query()
.findOne('importId', importId) .findOne('importId', importId)
.where('tenantId', tenantId) .where('tenantId', tenantId)
.throwIfNotFound(); .throwIfNotFound();
@@ -48,28 +58,21 @@ export class ImportFileProcess {
// Runs the importing operation with ability to return errors that will happen. // Runs the importing operation with ability to return errors that will happen.
const [successedImport, failedImport, allData] = const [successedImport, failedImport, allData] =
await this.uow.withTransaction( await this.uow.withTransaction(async (trx: Knex.Transaction) => {
tenantId, // Prases the sheet json data.
async (trx: Knex.Transaction) => { const parsedData = await this.importParser.parseSheetData(
// Prases the sheet json data. importFile,
const parsedData = await this.importParser.parseSheetData( resourceFields,
tenantId, sheetData,
importFile, trx,
resourceFields, );
sheetData, const [successedImport, failedImport] = await this.importCommon.import(
trx, importFile,
); parsedData,
const [successedImport, failedImport] = trx,
await this.importCommon.import( );
tenantId, return [successedImport, failedImport, parsedData];
importFile, }, trx);
parsedData,
trx,
);
return [successedImport, failedImport, parsedData];
},
trx,
);
const mapping = importFile.mappingParsed; const mapping = importFile.mappingParsed;
const errors = chain(failedImport) const errors = chain(failedImport)
.map((oper) => oper.error) .map((oper) => oper.error)

View File

@@ -1,9 +1,11 @@
import { ImportFilePreviewPOJO } from './interfaces'; import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { IImportFileCommitedEventPayload, ImportFilePreviewPOJO } from './interfaces';
import { ImportFileProcess } from './ImportFileProcess'; import { ImportFileProcess } from './ImportFileProcess';
import { ImportAls } from './ImportALS'; import { ImportAls } from './ImportALS';
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events'; import { events } from '@/common/events/events';
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
@Injectable() @Injectable()
export class ImportFileProcessCommit { export class ImportFileProcessCommit {
@@ -11,6 +13,9 @@ export class ImportFileProcessCommit {
private readonly importFile: ImportFileProcess, private readonly importFile: ImportFileProcess,
private readonly importAls: ImportAls, private readonly importAls: ImportAls,
private readonly eventEmitter: EventEmitter2, private readonly eventEmitter: EventEmitter2,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {} ) {}
/** /**
@@ -30,8 +35,9 @@ export class ImportFileProcessCommit {
* @returns {Promise<ImportFilePreviewPOJO>} * @returns {Promise<ImportFilePreviewPOJO>}
*/ */
public async commitAlsRun(importId: string): Promise<ImportFilePreviewPOJO> { public async commitAlsRun(importId: string): Promise<ImportFilePreviewPOJO> {
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' }); const trx = await this.tenantKnex().transaction({
isolationLevel: 'read uncommitted',
});
const meta = await this.importFile.import(importId, trx); const meta = await this.importFile.import(importId, trx);
// Commit the successed transaction. // Commit the successed transaction.

View File

@@ -9,9 +9,10 @@ import { ResourceService } from '../Resource/ResourceService';
import { ImportFileCommon } from './ImportFileCommon'; import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataValidator } from './ImportFileDataValidator'; import { ImportFileDataValidator } from './ImportFileDataValidator';
import { ImportFileUploadPOJO } from './interfaces'; import { ImportFileUploadPOJO } from './interfaces';
import { Import } from '@/system/models';
import { parseSheetData } from './sheet_utils'; import { parseSheetData } from './sheet_utils';
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { ImportModel } from './models/Import';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable() @Injectable()
export class ImportFileUploadService { export class ImportFileUploadService {
@@ -19,6 +20,10 @@ export class ImportFileUploadService {
private resourceService: ResourceService, private resourceService: ResourceService,
private importFileCommon: ImportFileCommon, private importFileCommon: ImportFileCommon,
private importValidator: ImportFileDataValidator, private importValidator: ImportFileDataValidator,
private tenancyContext: TenancyContext,
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {} ) {}
/** /**
@@ -36,12 +41,7 @@ export class ImportFileUploadService {
params: Record<string, number | string>, params: Record<string, number | string>,
): Promise<ImportFileUploadPOJO> { ): Promise<ImportFileUploadPOJO> {
try { try {
return await this.importUnhandled( return await this.importUnhandled(resourceName, filename, params);
tenantId,
resourceName,
filename,
params,
);
} catch (err) { } catch (err) {
deleteImportFile(filename); deleteImportFile(filename);
throw err; throw err;
@@ -81,15 +81,18 @@ export class ImportFileUploadService {
await this.importFileCommon.validateParamsSchema(resource, params); await this.importFileCommon.validateParamsSchema(resource, params);
// Validates importable params asyncly. // Validates importable params asyncly.
await this.importFileCommon.validateParams(tenantId, resource, params); await this.importFileCommon.validateParams(resource, params);
} catch (error) { } catch (error) {
throw error; throw error;
} }
const _params = this.importFileCommon.transformParams(resource, params); const _params = this.importFileCommon.transformParams(resource, params);
const paramsStringified = JSON.stringify(_params); const paramsStringified = JSON.stringify(_params);
const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
// Store the import model with related metadata. // Store the import model with related metadata.
const importFile = await Import.query().insert({ const importFile = await this.importModel.query().insert({
filename, filename,
resource, resource,
tenantId, tenantId,
@@ -97,10 +100,8 @@ export class ImportFileUploadService {
columns: coumnsStringified, columns: coumnsStringified,
params: paramsStringified, params: paramsStringified,
}); });
const resourceColumnsMap = this.resourceService.getResourceFields2( const resourceColumnsMap =
tenantId, this.resourceService.getResourceFields2(resource);
resource,
);
const resourceColumns = getResourceColumns(resourceColumnsMap); const resourceColumns = getResourceColumns(resourceColumnsMap);
return { return {

View File

@@ -1,34 +1,36 @@
import * as moment from 'moment'; import * as moment from 'moment';
import bluebird from 'bluebird'; import bluebird from 'bluebird';
import { deleteImportFile } from './_utils'; import { deleteImportFile } from './_utils';
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Import } from './models/Import'; import { ImportModel } from './models/Import';
@Injectable() @Injectable()
export class ImportDeleteExpiredFiles { export class ImportDeleteExpiredFiles {
constructor(
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {}
/** /**
* Delete expired files. * Delete expired files.
*/ */
async deleteExpiredFiles() { async deleteExpiredFiles() {
const yesterday = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm'); const yesterday = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm');
const expiredImports = await Import.query().where( const expiredImports = await this.importModel
'createdAt', .query()
'<', .where('createdAt', '<', yesterday);
yesterday
);
await bluebird.map( await bluebird.map(
expiredImports, expiredImports,
async (expiredImport) => { async (expiredImport) => {
await deleteImportFile(expiredImport.filename); await deleteImportFile(expiredImport.filename);
}, },
{ concurrency: 10 } { concurrency: 10 },
); );
const expiredImportsIds = expiredImports.map( const expiredImportsIds = expiredImports.map(
(expiredImport) => expiredImport.id (expiredImport) => expiredImport.id,
); );
if (expiredImportsIds.length > 0) { if (expiredImportsIds.length > 0) {
await Import.query().whereIn('id', expiredImportsIds).delete(); await this.importModel.query().whereIn('id', expiredImportsIds).delete();
} }
} }
} }

View File

@@ -35,7 +35,7 @@ export class ImportableResources {
// resource: 'UncategorizedCashflowTransaction', // resource: 'UncategorizedCashflowTransaction',
// importable: UncategorizedTransactionsImportable, // importable: UncategorizedTransactionsImportable,
// }, // },
// { resource: 'Customer', importable: CustomersImportable }, // { resource: 'Customer', importable: CustomersImportable },
// { resource: 'Vendor', importable: VendorsImportable }, // { resource: 'Vendor', importable: VendorsImportable },
// { resource: 'Item', importable: ItemsImportable }, // { resource: 'Item', importable: ItemsImportable },
// { resource: 'ItemCategory', importable: ItemCategoriesImportable }, // { resource: 'ItemCategory', importable: ItemCategoriesImportable },

View File

@@ -2,8 +2,8 @@ import * as Yup from 'yup';
import * as moment from 'moment'; import * as moment from 'moment';
import * as R from 'ramda'; import * as R from 'ramda';
import { Knex } from 'knex'; import { Knex } from 'knex';
import fs from 'fs/promises'; import * as fs from 'fs/promises';
import path from 'path'; import * as path from 'path';
import { import {
defaultTo, defaultTo,
upperFirst, upperFirst,
@@ -18,11 +18,11 @@ import {
split, split,
last, last,
} from 'lodash'; } from 'lodash';
import pluralize from 'pluralize'; import * as pluralize from 'pluralize';
import { ResourceMetaFieldsMap } from './interfaces'; import { ResourceMetaFieldsMap } from './interfaces';
import { IModelMetaField, IModelMetaField2 } from '@/interfaces';
import { ServiceError } from '@/exceptions';
import { multiNumberParse } from '@/utils/multi-number-parse'; import { multiNumberParse } from '@/utils/multi-number-parse';
import { ServiceError } from '../Items/ServiceError';
import { IModelMetaField, IModelMetaField2 } from '@/interfaces/Model';
export const ERRORS = { export const ERRORS = {
RESOURCE_NOT_IMPORTABLE: 'RESOURCE_NOT_IMPORTABLE', RESOURCE_NOT_IMPORTABLE: 'RESOURCE_NOT_IMPORTABLE',
@@ -70,13 +70,13 @@ export const convertFieldsToYupValidation = (fields: ResourceMetaFieldsMap) => {
if (!isUndefined(field.minLength)) { if (!isUndefined(field.minLength)) {
fieldSchema = fieldSchema.min( fieldSchema = fieldSchema.min(
field.minLength, field.minLength,
`Minimum length is ${field.minLength} characters` `Minimum length is ${field.minLength} characters`,
); );
} }
if (!isUndefined(field.maxLength)) { if (!isUndefined(field.maxLength)) {
fieldSchema = fieldSchema.max( fieldSchema = fieldSchema.max(
field.maxLength, field.maxLength,
`Maximum length is ${field.maxLength} characters` `Maximum length is ${field.maxLength} characters`,
); );
} }
} else if (field.fieldType === 'number') { } else if (field.fieldType === 'number') {
@@ -106,11 +106,12 @@ export const convertFieldsToYupValidation = (fields: ResourceMetaFieldsMap) => {
return true; return true;
} }
return moment(val, 'YYYY-MM-DD', true).isValid(); return moment(val, 'YYYY-MM-DD', true).isValid();
} },
); );
} else if (field.fieldType === 'url') { } else if (field.fieldType === 'url') {
fieldSchema = fieldSchema.url(); fieldSchema = fieldSchema.url();
} else if (field.fieldType === 'collection') { } else if (field.fieldType === 'collection') {
// @ts-expect-error
const nestedFieldShema = convertFieldsToYupValidation(field.fields); const nestedFieldShema = convertFieldsToYupValidation(field.fields);
fieldSchema = Yup.array().label(field.name); fieldSchema = Yup.array().label(field.name);
@@ -149,12 +150,12 @@ const parseFieldName = (fieldName: string, field: IModelMetaField) => {
*/ */
export const getUnmappedSheetColumns = (columns, mapping) => { export const getUnmappedSheetColumns = (columns, mapping) => {
return columns.filter( return columns.filter(
(column) => !mapping.some((map) => map.from === column) (column) => !mapping.some((map) => map.from === column),
); );
}; };
export const sanitizeResourceName = (resourceName: string) => { export const sanitizeResourceName = (resourceName: string) => {
return upperFirst(camelCase(pluralize.singular(resourceName))); return upperFirst(camelCase(pluralize(resourceName, 1)));
}; };
export const getSheetColumns = (sheetData: unknown[]) => { export const getSheetColumns = (sheetData: unknown[]) => {
@@ -170,11 +171,11 @@ export const getSheetColumns = (sheetData: unknown[]) => {
*/ */
export const getUniqueImportableValue = ( export const getUniqueImportableValue = (
importableFields: { [key: string]: IModelMetaField2 }, importableFields: { [key: string]: IModelMetaField2 },
objectDTO: Record<string, any> objectDTO: Record<string, any>,
) => { ) => {
const uniqueImportableValue = pickBy( const uniqueImportableValue = pickBy(
importableFields, importableFields,
(field) => field.unique (field) => field.unique,
); );
const uniqueImportableKeys = Object.keys(uniqueImportableValue); const uniqueImportableKeys = Object.keys(uniqueImportableValue);
const uniqueImportableKey = first(uniqueImportableKeys); const uniqueImportableKey = first(uniqueImportableKeys);
@@ -254,7 +255,7 @@ export const getResourceColumns = (resourceColumns: {
(group: string) => (group: string) =>
([fieldKey, { name, importHint, required, order, ...field }]: [ ([fieldKey, { name, importHint, required, order, ...field }]: [
string, string,
IModelMetaField2 IModelMetaField2,
]) => { ]) => {
const extra: Record<string, any> = {}; const extra: Record<string, any> = {};
const key = fieldKey; const key = fieldKey;
@@ -299,7 +300,7 @@ export const valueParser =
// Parses the enumeration value. // Parses the enumeration value.
} else if (field.fieldType === 'enumeration') { } else if (field.fieldType === 'enumeration') {
const option = get(field, 'options', []).find( const option = get(field, 'options', []).find(
(option) => option.label?.toLowerCase() === value?.toLowerCase() (option) => option.label?.toLowerCase() === value?.toLowerCase(),
); );
_value = get(option, 'key'); _value = get(option, 'key');
// Parses the numeric value. // Parses the numeric value.
@@ -336,7 +337,7 @@ export const valueParser =
* @param {string} key - Mapped key path. formats: `group.key` or `key`. * @param {string} key - Mapped key path. formats: `group.key` or `key`.
* @returns {string} * @returns {string}
*/ */
export const parseKey: R.Curry<string> = R.curry( export const parseKey = R.curry(
(fields: { [key: string]: IModelMetaField2 }, key: string) => { (fields: { [key: string]: IModelMetaField2 }, key: string) => {
const fieldKey = getFieldKey(key); const fieldKey = getFieldKey(key);
const field = fields[fieldKey]; const field = fields[fieldKey];
@@ -355,7 +356,7 @@ export const parseKey: R.Curry<string> = R.curry(
} }
} }
return _key; return _key;
} },
); );
/** /**
@@ -398,11 +399,11 @@ export const getFieldKey = (input: string) => {
export function aggregate( export function aggregate(
input: Array<any>, input: Array<any>,
comparatorAttr: string, comparatorAttr: string,
groupOn: string groupOn: string,
): Array<Record<string, any>> { ): Array<Record<string, any>> {
return input.reduce((acc, curr) => { return input.reduce((acc, curr) => {
const existingEntry = acc.find( const existingEntry = acc.find(
(entry) => entry[comparatorAttr] === curr[comparatorAttr] (entry) => entry[comparatorAttr] === curr[comparatorAttr],
); );
if (existingEntry) { if (existingEntry) {

View File

@@ -1,5 +1,5 @@
import { IModelMetaField2 } from "@/interfaces/Model"; import { IModelMetaField2 } from "@/interfaces/Model";
import { Import } from "./models/Import"; import { ImportModelShape } from "./models/Import";
export interface ImportMappingAttr { export interface ImportMappingAttr {
from: string; from: string;
@@ -65,7 +65,7 @@ export interface ImportOperError {
} }
export interface ImportableContext { export interface ImportableContext {
import: Import; import: ImportModelShape;
rowIndex: number; rowIndex: number;
} }
@@ -75,3 +75,9 @@ export const ImportDateFormats = [
'MM/dd/yy', 'MM/dd/yy',
'dd/MMM/yyyy', 'dd/MMM/yyyy',
]; ];
export interface IImportFileCommitedEventPayload {
importId: string;
meta: ImportFilePreviewPOJO;
}

View File

@@ -1,13 +1,14 @@
import { Model, ModelObject } from 'objection'; import { Model, ModelObject } from 'objection';
// import SystemModel from './SystemModel';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
export class Import extends BaseModel { export class ImportModel extends BaseModel {
resource: string; resource!: string;
tenantId: number; tenantId!: number;
filename!: string;
mapping!: string; mapping!: string;
columns!: string; columns!: string;
params!: string; params!: string;
importId!: string;
/** /**
* Table name. * Table name.
@@ -84,4 +85,4 @@ export class Import extends BaseModel {
} }
} }
export type ImportShape = ModelObject<Import>; export type ImportModelShape = ModelObject<ImportModel>;

View File

@@ -1,29 +1,29 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import { Exportable } from '../Export/Exportable'; import { Exportable } from '../Export/Exportable';
// import { IAccountsFilter, IAccountsStructureType } from '@/interfaces'; import { ItemCategoryApplication } from './ItemCategory.application';
// import ItemCategoriesService from './ItemCategoriesService'; import { IItemCategoriesFilter } from './ItemCategory.interfaces';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { ItemCategory } from './models/ItemCategory.model';
// @Service() @Injectable()
// export class ItemCategoriesExportable extends Exportable { @ExportableService({ name: ItemCategory.name })
// @Inject() export class ItemCategoriesExportable extends Exportable {
// private itemCategoriesApplication: ItemCategoriesService; constructor(private readonly itemCategoryApp: ItemCategoryApplication) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId * @param {number} tenantId
// * @returns * @returns
// */ */
// public exportable(tenantId: number, query: IAccountsFilter) { public exportable(query: Partial<IItemCategoriesFilter>) {
// const parsedQuery = { const parsedQuery = {
// sortOrder: 'desc', ...query
// columnSortBy: 'created_at', } as IItemCategoriesFilter;
// inactiveMode: false,
// ...query,
// structure: IAccountsStructureType.Flat,
// } as IAccountsFilter;
// return this.itemCategoriesApplication return this.itemCategoryApp
// .getItemCategoriesList(tenantId, parsedQuery, {}) .getItemCategories(parsedQuery)
// .then((output) => output.itemCategories); .then((output) => output.itemCategories);
// } }
// } }

View File

@@ -1,38 +1,32 @@
// import { Inject, Service } from 'typedi'; import { Importable } from '../Import/Importable';
// import ItemCategoriesService from './ItemCategoriesService'; import { Knex } from 'knex';
// import { Importable } from '../Import/Importable'; import { ItemCategoriesSampleData } from './constants';
// import { Knex } from 'knex'; import { Injectable } from '@nestjs/common';
// import { IItemCategoryOTD } from '@/interfaces'; import { CreateItemCategoryDto } from './dtos/ItemCategory.dto';
// import { ItemCategoriesSampleData } from './constants'; import { ItemCategoryApplication } from './ItemCategory.application';
// @Service() @Injectable()
// export class ItemCategoriesImportable extends Importable { export class ItemCategoriesImportable extends Importable {
// @Inject() constructor(private readonly itemCategoriesApp: ItemCategoryApplication) {
// private itemCategoriesService: ItemCategoriesService; super();
}
// /** /**
// * Importing to create new item category service. * Importing to create new item category service.
// * @param {number} tenantId * @param {CreateItemCategoryDto} createDTO
// * @param {any} createDTO * @param {Knex.Transaction} trx
// * @param {Knex.Transaction} trx */
// */ public async importable(
// public async importable( createDTO: CreateItemCategoryDto,
// tenantId: number, trx?: Knex.Transaction,
// createDTO: IItemCategoryOTD, ) {
// trx?: Knex.Transaction await this.itemCategoriesApp.createItemCategory(createDTO, trx);
// ) { }
// await this.itemCategoriesService.newItemCategory(
// tenantId,
// createDTO,
// {},
// trx
// );
// }
// /** /**
// * Item categories sample data used to download sample sheet file. * Item categories sample data used to download sample sheet file.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return ItemCategoriesSampleData; return ItemCategoriesSampleData;
// } }
// } }

View File

@@ -9,6 +9,7 @@ import { EditItemCategoryService } from './commands/EditItemCategory.service';
import { GetItemCategoryService } from './queries/GetItemCategory.service'; import { GetItemCategoryService } from './queries/GetItemCategory.service';
import { GetItemCategoriesService } from './queries/GetItemCategories.service'; import { GetItemCategoriesService } from './queries/GetItemCategories.service';
import { CreateItemCategoryDto, EditItemCategoryDto } from './dtos/ItemCategory.dto'; import { CreateItemCategoryDto, EditItemCategoryDto } from './dtos/ItemCategory.dto';
import { Knex } from 'knex';
@Injectable() @Injectable()
export class ItemCategoryApplication { export class ItemCategoryApplication {
@@ -33,8 +34,9 @@ export class ItemCategoryApplication {
*/ */
public createItemCategory( public createItemCategory(
itemCategoryDTO: CreateItemCategoryDto, itemCategoryDTO: CreateItemCategoryDto,
trx?: Knex.Transaction,
) { ) {
return this.createItemCategoryService.newItemCategory(itemCategoryDTO); return this.createItemCategoryService.newItemCategory(itemCategoryDTO, trx);
} }
/** /**

View File

@@ -11,6 +11,8 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv
import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { GetItemCategoriesService } from './queries/GetItemCategories.service'; import { GetItemCategoriesService } from './queries/GetItemCategories.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { ItemCategoriesExportable } from './ItemCategoriesExportable';
import { ItemCategoriesImportable } from './ItemCategoriesImportable';
@Module({ @Module({
imports: [TenancyDatabaseModule, DynamicListModule], imports: [TenancyDatabaseModule, DynamicListModule],
@@ -25,6 +27,12 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
CommandItemCategoryValidatorService, CommandItemCategoryValidatorService,
TransformerInjectable, TransformerInjectable,
TenancyContext, TenancyContext,
ItemCategoriesExportable,
ItemCategoriesImportable,
],
exports: [
ItemCategoriesExportable,
ItemCategoriesImportable,
], ],
}) })
export class ItemCategoryModule {} export class ItemCategoryModule {}

View File

@@ -0,0 +1,64 @@
export const ItemCategoryMeta = {
defaultFilterField: 'name',
defaultSort: {
sortField: 'name',
sortOrder: 'DESC',
},
importable: true,
exportable: true,
fields: {
name: {
name: 'item_category.field.name',
column: 'name',
fieldType: 'text',
},
description: {
name: 'item_category.field.description',
column: 'description',
fieldType: 'text',
},
count: {
name: 'item_category.field.count',
column: 'count',
fieldType: 'number',
virtualColumn: true,
},
created_at: {
name: 'item_category.field.created_at',
column: 'created_at',
columnType: 'date',
},
},
columns: {
name: {
name: 'item_category.field.name',
type: 'text',
},
description: {
name: 'item_category.field.description',
type: 'text',
},
count: {
name: 'item_category.field.count',
type: 'text',
},
createdAt: {
name: 'item_category.field.created_at',
type: 'text',
},
},
fields2: {
name: {
name: 'item_category.field.name',
column: 'name',
fieldType: 'text',
},
description: {
name: 'item_category.field.description',
column: 'description',
fieldType: 'text',
},
},
};

View File

@@ -1,6 +1,11 @@
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { Model } from 'objection'; import { Model } from 'objection';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { ItemCategoryMeta } from './ItemCategory.meta';
@ExportableModel()
@InjectModelMeta(ItemCategoryMeta)
export class ItemCategory extends TenantBaseModel { export class ItemCategory extends TenantBaseModel {
name!: string; name!: string;
description!: string; description!: string;

View File

@@ -16,6 +16,7 @@ import { ItemsEntriesService } from './ItemsEntries.service';
import { GetItemsService } from './GetItems.service'; import { GetItemsService } from './GetItems.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module'; import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module'; import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module';
import { ItemsExportable } from './ItemsExportable.service';
@Module({ @Module({
imports: [ imports: [
@@ -38,7 +39,8 @@ import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdju
TenancyContext, TenancyContext,
TransformerInjectable, TransformerInjectable,
ItemsEntriesService, ItemsEntriesService,
ItemsExportable,
], ],
exports: [ItemsEntriesService], exports: [ItemsEntriesService, ItemsExportable],
}) })
export class ItemsModule {} export class ItemsModule {}

View File

@@ -0,0 +1,36 @@
import { Global, Injectable } from '@nestjs/common';
import { Exportable } from '../Export/Exportable';
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
import { ItemsApplicationService } from './ItemsApplication.service';
import { IItemsFilter } from './types/Items.types';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { Item } from './models/Item';
@Injectable()
@ExportableService({ name: Item.name })
@Global()
export class ItemsExportable extends Exportable {
constructor(
private readonly itemsApplication: ItemsApplicationService,
) {
super();
}
/**
* Retrieves the accounts data to exportable sheet.
* @param {IItemsFilter} query - Items export query.
*/
public exportable(query: IItemsFilter) {
const parsedQuery = {
sortOrder: 'DESC',
columnSortBy: 'created_at',
page: 1,
...query,
pageSize: EXPORT_SIZE_LIMIT,
} as IItemsFilter;
return this.itemsApplication
.getItems(parsedQuery)
.then((output) => output.items);
}
}

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { Importable } from '../Import/Importable';
import { CreateItemService } from './CreateItem.service';
import { CreateItemDto } from './dtos/Item.dto';
import { ItemsSampleData } from './Items.constants';
@Injectable()
export class ItemsImportable extends Importable {
constructor(
private readonly createItemService: CreateItemService,
) {
super();
}
/**
* Mapps the imported data to create a new item service.
* @param {number} tenantId
* @param {ICustomerNewDTO} createDTO
* @param {Knex.Transaction} trx
* @returns {Promise<void>}
*/
public async importable(
createDTO: CreateItemDto,
trx?: Knex.Transaction<any, any[]>
): Promise<void> {
await this.createItemService.createItem(createDTO, trx);
}
/**
* Retrieves the sample data of customers used to download sample sheet.
*/
public sampleData(): any[] {
return ItemsSampleData;
}
}

View File

@@ -0,0 +1,309 @@
export const ItemMeta = {
importable: true,
exportable: true,
defaultFilterField: 'name',
defaultSort: {
sortField: 'name',
sortOrder: 'DESC',
},
print: {
pageTitle: 'Items',
},
fields: {
type: {
name: 'item.field.type',
column: 'type',
fieldType: 'enumeration',
options: [
{ key: 'inventory', label: 'item.field.type.inventory' },
{ key: 'service', label: 'item.field.type.service' },
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
],
},
name: {
name: 'item.field.name',
column: 'name',
fieldType: 'text',
},
code: {
name: 'item.field.code',
column: 'code',
fieldType: 'text',
},
sellable: {
name: 'item.field.sellable',
column: 'sellable',
fieldType: 'boolean',
},
purchasable: {
name: 'item.field.purchasable',
column: 'purchasable',
fieldType: 'boolean',
},
sell_price: {
name: 'item.field.sell_price',
column: 'sell_price',
fieldType: 'number',
},
cost_price: {
name: 'item.field.cost_price',
column: 'cost_price',
fieldType: 'number',
},
cost_account: {
name: 'item.field.cost_account',
column: 'cost_account_id',
fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'costAccount',
relationEntityLabel: 'name',
relationEntityKey: 'slug',
},
sell_account: {
name: 'item.field.sell_account',
column: 'sell_account_id',
fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'sellAccount',
relationEntityLabel: 'name',
relationEntityKey: 'slug',
},
inventory_account: {
name: 'item.field.inventory_account',
column: 'inventory_account_id',
relationType: 'enumeration',
relationKey: 'inventoryAccount',
relationEntityLabel: 'name',
relationEntityKey: 'slug',
},
sell_description: {
name: 'Sell description',
column: 'sell_description',
fieldType: 'text',
},
purchase_description: {
name: 'Purchase description',
column: 'purchase_description',
fieldType: 'text',
},
quantity_on_hand: {
name: 'item.field.quantity_on_hand',
column: 'quantity_on_hand',
fieldType: 'number',
},
note: {
name: 'item.field.note',
column: 'note',
fieldType: 'text',
},
category: {
name: 'item.field.category',
column: 'category_id',
relationType: 'enumeration',
relationKey: 'category',
relationEntityLabel: 'name',
relationEntityKey: 'id',
},
active: {
name: 'item.field.active',
column: 'active',
fieldType: 'boolean',
filterable: false,
},
created_at: {
name: 'item.field.created_at',
column: 'created_at',
columnType: 'date',
fieldType: 'date',
},
},
columns: {
type: {
name: 'item.field.type',
type: 'text',
exportable: true,
accessor: 'typeFormatted',
},
name: {
name: 'item.field.name',
type: 'text',
exportable: true,
},
code: {
name: 'item.field.code',
type: 'text',
exportable: true,
},
sellable: {
name: 'item.field.sellable',
type: 'boolean',
exportable: true,
printable: false,
},
purchasable: {
name: 'item.field.purchasable',
type: 'boolean',
exportable: true,
printable: false,
},
sellPrice: {
name: 'item.field.sell_price',
type: 'number',
exportable: true,
},
costPrice: {
name: 'item.field.cost_price',
type: 'number',
exportable: true,
},
costAccount: {
name: 'item.field.cost_account',
type: 'text',
accessor: 'costAccount.name',
exportable: true,
printable: false,
},
sellAccount: {
name: 'item.field.sell_account',
type: 'text',
accessor: 'sellAccount.name',
exportable: true,
printable: false,
},
inventoryAccount: {
name: 'item.field.inventory_account',
type: 'text',
accessor: 'inventoryAccount.name',
exportable: true,
},
sellDescription: {
name: 'Sell description',
type: 'text',
exportable: true,
printable: false,
},
purchaseDescription: {
name: 'Purchase description',
type: 'text',
exportable: true,
printable: false,
},
quantityOnHand: {
name: 'item.field.quantity_on_hand',
type: 'number',
exportable: true,
},
note: {
name: 'item.field.note',
type: 'text',
exportable: true,
},
category: {
name: 'item.field.category',
type: 'text',
accessor: 'category.name',
exportable: true,
},
active: {
name: 'item.field.active',
fieldType: 'boolean',
exportable: true,
printable: false,
},
createdAt: {
name: 'item.field.created_at',
type: 'date',
exportable: true,
printable: false,
},
},
fields2: {
type: {
name: 'item.field.type',
fieldType: 'enumeration',
options: [
{ key: 'inventory', label: 'item.field.type.inventory' },
{ key: 'service', label: 'item.field.type.service' },
{ key: 'non-inventory', label: 'item.field.type.non-inventory' },
],
required: true,
},
name: {
name: 'item.field.name',
fieldType: 'text',
required: true,
},
code: {
name: 'item.field.code',
fieldType: 'text',
},
sellable: {
name: 'item.field.sellable',
fieldType: 'boolean',
},
purchasable: {
name: 'item.field.purchasable',
fieldType: 'boolean',
},
sellPrice: {
name: 'item.field.sell_price',
fieldType: 'number',
},
costPrice: {
name: 'item.field.cost_price',
fieldType: 'number',
},
costAccountId: {
name: 'item.field.cost_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
importHint: 'Matches the account name or code.',
},
sellAccountId: {
name: 'item.field.sell_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
importHint: 'Matches the account name or code.',
},
inventoryAccountId: {
name: 'item.field.inventory_account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
importHint: 'Matches the account name or code.',
},
sellDescription: {
name: 'Sell Description',
fieldType: 'text',
},
purchaseDescription: {
name: 'Purchase Description',
fieldType: 'text',
},
note: {
name: 'item.field.note',
fieldType: 'text',
},
categoryId: {
name: 'item.field.category',
fieldType: 'relation',
relationModel: 'ItemCategory',
relationImportMatch: ['name'],
importHint: 'Matches the category name.',
},
active: {
name: 'item.field.active',
fieldType: 'boolean',
},
},
};

View File

@@ -1,7 +1,12 @@
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model'; import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { Model } from 'objection'; import { Model } from 'objection';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { ItemMeta } from './Item.meta';
@ExportableModel()
@InjectModelMeta(ItemMeta)
export class Item extends TenantBaseModel { export class Item extends TenantBaseModel {
public readonly quantityOnHand: number; public readonly quantityOnHand: number;
public readonly name: string; public readonly name: string;
@@ -48,6 +53,13 @@ export class Item extends TenantBaseModel {
} }
return q; return q;
}, },
/**
* Inactive/Active mode.
*/
inactiveMode(query, active = false) {
query.where('items.active', !active);
},
}; };
} }

View File

@@ -15,9 +15,13 @@ import { GetManualJournal } from './queries/GetManualJournal.service';
import { ManualJournalWriteGLSubscriber } from './commands/ManualJournalGLEntriesSubscriber'; import { ManualJournalWriteGLSubscriber } from './commands/ManualJournalGLEntriesSubscriber';
import { ManualJournalGLEntries } from './commands/ManualJournalGLEntries'; import { ManualJournalGLEntries } from './commands/ManualJournalGLEntries';
import { LedgerModule } from '../Ledger/Ledger.module'; import { LedgerModule } from '../Ledger/Ledger.module';
import { ManualJournalsExportable } from './commands/ManualJournalExportable';
import { ManualJournalImportable } from './commands/ManualJournalsImport';
import { GetManualJournals } from './queries/GetManualJournals.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
@Module({ @Module({
imports: [BranchesModule, LedgerModule], imports: [BranchesModule, LedgerModule, DynamicListModule],
controllers: [ManualJournalsController], controllers: [ManualJournalsController],
providers: [ providers: [
TenancyContext, TenancyContext,
@@ -32,8 +36,12 @@ import { LedgerModule } from '../Ledger/Ledger.module';
AutoIncrementOrdersService, AutoIncrementOrdersService,
ManualJournalsApplication, ManualJournalsApplication,
GetManualJournal, GetManualJournal,
GetManualJournals,
ManualJournalGLEntries, ManualJournalGLEntries,
ManualJournalWriteGLSubscriber ManualJournalWriteGLSubscriber,
ManualJournalsExportable,
ManualJournalImportable,
], ],
exports: [ManualJournalsExportable, ManualJournalImportable],
}) })
export class ManualJournalsModule {} export class ManualJournalsModule {}

View File

@@ -4,8 +4,12 @@ import { EditManualJournal } from './commands/EditManualJournal.service';
import { PublishManualJournal } from './commands/PublishManualJournal.service'; import { PublishManualJournal } from './commands/PublishManualJournal.service';
import { GetManualJournal } from './queries/GetManualJournal.service'; import { GetManualJournal } from './queries/GetManualJournal.service';
import { DeleteManualJournalService } from './commands/DeleteManualJournal.service'; import { DeleteManualJournalService } from './commands/DeleteManualJournal.service';
import { IManualJournalDTO, } from './types/ManualJournals.types'; import { IManualJournalsFilter } from './types/ManualJournals.types';
import { CreateManualJournalDto, EditManualJournalDto } from './dtos/ManualJournal.dto'; import {
CreateManualJournalDto,
EditManualJournalDto,
} from './dtos/ManualJournal.dto';
import { GetManualJournals } from './queries/GetManualJournals.service';
// import { GetManualJournals } from './queries/GetManualJournals'; // import { GetManualJournals } from './queries/GetManualJournals';
@Injectable() @Injectable()
@@ -16,7 +20,7 @@ export class ManualJournalsApplication {
private deleteManualJournalService: DeleteManualJournalService, private deleteManualJournalService: DeleteManualJournalService,
private publishManualJournalService: PublishManualJournal, private publishManualJournalService: PublishManualJournal,
private getManualJournalService: GetManualJournal, private getManualJournalService: GetManualJournal,
// private getManualJournalsService: GetManualJournals, private getManualJournalsService: GetManualJournals,
) {} ) {}
/** /**
@@ -50,9 +54,7 @@ export class ManualJournalsApplication {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
public deleteManualJournal = (manualJournalId: number) => { public deleteManualJournal = (manualJournalId: number) => {
return this.deleteManualJournalService.deleteManualJournal( return this.deleteManualJournalService.deleteManualJournal(manualJournalId);
manualJournalId,
);
}; };
/** /**
@@ -68,23 +70,16 @@ export class ManualJournalsApplication {
/** /**
* Retrieves the specific manual journal. * Retrieves the specific manual journal.
* @param {number} manualJournalId * @param {number} manualJournalId
* @returns
*/ */
public getManualJournal = (manualJournalId: number) => { public getManualJournal = (manualJournalId: number) => {
return this.getManualJournalService.getManualJournal( return this.getManualJournalService.getManualJournal(manualJournalId);
manualJournalId,
);
}; };
/** /**
* Retrieves the paginated manual journals. * Retrieves the paginated manual journals.
* @param {number} tenantId
* @param {IManualJournalsFilter} filterDTO * @param {IManualJournalsFilter} filterDTO
* @returns
*/ */
// public getManualJournals = ( public getManualJournals = (filterDTO: IManualJournalsFilter) => {
// filterDTO: IManualJournalsFilter, return this.getManualJournalsService.getManualJournals(filterDTO);
// ) => { };
// // return this.getManualJournalsService.getManualJournals(filterDTO);
// };
} }

View File

@@ -1,30 +1,35 @@
// import { Inject, Service } from 'typedi'; import { Exportable } from '../../Export/Exportable';
// import { IManualJournalsFilter } from '@/interfaces'; import { EXPORT_SIZE_LIMIT } from '../../Export/constants';
// import { Exportable } from '../../Export/Exportable'; import { Injectable } from '@nestjs/common';
// import { ManualJournalsApplication } from '../ManualJournalsApplication'; import { IManualJournalsFilter } from '../types/ManualJournals.types';
// import { EXPORT_SIZE_LIMIT } from '../../Export/constants'; import { ManualJournalsApplication } from '../ManualJournalsApplication.service';
import { ExportableService } from '@/modules/Export/decorators/ExportableModel.decorator';
import { ManualJournal } from '../models/ManualJournal';
// @Service() @Injectable()
// export class ManualJournalsExportable extends Exportable { @ExportableService({ name: ManualJournal.name })
// @Inject() export class ManualJournalsExportable extends Exportable {
// private manualJournalsApplication: ManualJournalsApplication; constructor(
private readonly manualJournalsApplication: ManualJournalsApplication,
) {
super();
}
// /** /**
// * Retrieves the manual journals data to exportable sheet. * Retrieves the manual journals data to exportable sheet.
// * @param {number} tenantId * @param {IManualJournalsFilter} query -
// * @returns */
// */ public exportable(query: IManualJournalsFilter) {
// public exportable(tenantId: number, query: IManualJournalsFilter) { const parsedQuery = {
// const parsedQuery = { sortOrder: 'desc',
// sortOrder: 'desc', columnSortBy: 'created_at',
// columnSortBy: 'created_at', ...query,
// ...query, page: 1,
// page: 1, pageSize: EXPORT_SIZE_LIMIT,
// pageSize: EXPORT_SIZE_LIMIT, } as IManualJournalsFilter;
// } as IManualJournalsFilter;
// return this.manualJournalsApplication return this.manualJournalsApplication
// .getManualJournals(tenantId, parsedQuery) .getManualJournals(parsedQuery)
// .then((output) => output.manualJournals); .then((output) => output.manualJournals);
// } }
// } }

View File

@@ -1,60 +1,57 @@
// import { Inject } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import * as Yup from 'yup';
// import * as Yup from 'yup'; import { Importable } from '../../Import/Importable';
// import { Importable } from '../../Import/Importable'; import { CreateManualJournalService } from './CreateManualJournal.service';
// import { CreateManualJournalService } from './CreateManualJournal.service'; import { ImportableContext } from '../../Import/interfaces';
// import { IManualJournalDTO } from '@/interfaces'; import { ManualJournalsSampleData } from '../constants';
// import { ImportableContext } from '../../Import/interfaces'; import { CreateManualJournalDto } from '../dtos/ManualJournal.dto';
// import { ManualJournalsSampleData } from '../constants';
// export class ManualJournalImportable extends Importable { export class ManualJournalImportable extends Importable {
// @Inject() constructor(
// private createManualJournalService: CreateManualJournalService; private readonly createManualJournalService: CreateManualJournalService,
) {
super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {CreateManualJournalDto} createAccountDTO
// * @param {IAccountCreateDTO} createAccountDTO */
// * @returns public importable(
// */ createJournalDTO: CreateManualJournalDto,
// public importable( trx?: Knex.Transaction,
// tenantId: number, ) {
// createJournalDTO: IManualJournalDTO, return this.createManualJournalService.makeJournalEntries(
// trx?: Knex.Transaction createJournalDTO,
// ) { trx,
// return this.createManualJournalService.makeJournalEntries( );
// tenantId, }
// createJournalDTO,
// {},
// trx
// );
// }
// /** /**
// * Transformes the DTO before passing it to importable and validation. * Transformes the DTO before passing it to importable and validation.
// * @param {Record<string, any>} createDTO * @param {Record<string, any>} createDTO
// * @param {ImportableContext} context * @param {ImportableContext} context
// * @returns {Record<string, any>} * @returns {Record<string, any>}
// */ */
// public transform(createDTO: Record<string, any>, context: ImportableContext) { public transform(createDTO: Record<string, any>, context: ImportableContext) {
// return createDTO; return createDTO;
// } }
// /** /**
// * Params validation schema. * Params validation schema.
// * @returns {ValidationSchema[]} * @returns {ValidationSchema[]}
// */ */
// public paramsValidationSchema() { public paramsValidationSchema() {
// return Yup.object().shape({ return Yup.object().shape({
// autoIncrement: Yup.boolean(), autoIncrement: Yup.boolean(),
// }); });
// } }
// /** /**
// * Retrieves the sample data of manual journals that used to download sample sheet. * Retrieves the sample data of manual journals that used to download sample sheet.
// * @returns {Record<string, any>} * @returns {Record<string, any>}
// */ */
// public sampleData(): Record<string, any>[] { public sampleData(): Record<string, any>[] {
// return ManualJournalsSampleData; return ManualJournalsSampleData;
// } }
// } }

View File

@@ -0,0 +1,230 @@
export const ManualJournalMeta = {
defaultFilterField: 'date',
defaultSort: {
sortOrder: 'DESC',
sortField: 'name',
},
importable: true,
exportFlattenOn: 'entries',
exportable: true,
importAggregator: 'group',
importAggregateOn: 'entries',
importAggregateBy: 'journalNumber',
print: {
pageTitle: 'Manual Journals',
},
fields: {
date: {
name: 'manual_journal.field.date',
column: 'date',
fieldType: 'date',
},
journal_number: {
name: 'manual_journal.field.journal_number',
column: 'journal_number',
fieldType: 'text',
},
reference: {
name: 'manual_journal.field.reference',
column: 'reference',
fieldType: 'text',
},
journal_type: {
name: 'manual_journal.field.journal_type',
column: 'journal_type',
fieldType: 'text',
},
amount: {
name: 'manual_journal.field.amount',
column: 'amount',
fieldType: 'number',
},
description: {
name: 'manual_journal.field.description',
column: 'description',
fieldType: 'text',
},
status: {
name: 'manual_journal.field.status',
column: 'status',
fieldType: 'enumeration',
options: [
{ key: 'draft', label: 'Draft' },
{ key: 'published', label: 'published' },
],
filterCustomQuery: StatusFieldFilterQuery,
sortCustomQuery: StatusFieldSortQuery,
},
created_at: {
name: 'manual_journal.field.created_at',
column: 'created_at',
fieldType: 'date',
},
},
columns: {
date: {
name: 'manual_journal.field.date',
type: 'date',
accessor: 'formattedDate',
},
journalNumber: {
name: 'manual_journal.field.journal_number',
type: 'text',
},
reference: {
name: 'manual_journal.field.reference',
type: 'text',
},
journalType: {
name: 'manual_journal.field.journal_type',
type: 'text',
},
amount: {
name: 'Amount',
accessor: 'formattedAmount',
},
currencyCode: {
name: 'manual_journal.field.currency',
type: 'text',
printable: false,
},
exchangeRate: {
name: 'manual_journal.field.exchange_rate',
type: 'number',
printable: false,
},
description: {
name: 'manual_journal.field.description',
type: 'text',
},
entries: {
name: 'Entries',
type: 'collection',
collectionOf: 'object',
columns: {
credit: {
name: 'Credit',
type: 'text',
},
debit: {
name: 'Debit',
type: 'text',
},
account: {
name: 'Account',
accessor: 'account.name',
},
contact: {
name: 'Contact',
accessor: 'contact.displayName',
},
note: {
name: 'Note',
},
},
publish: {
name: 'Publish',
type: 'boolean',
printable: false,
},
publishedAt: {
name: 'Published At',
printable: false,
},
},
createdAt: {
name: 'Created At',
accessor: 'formattedCreatedAt',
printable: false,
},
},
fields2: {
date: {
name: 'manual_journal.field.date',
fieldType: 'date',
required: true,
},
journalNumber: {
name: 'manual_journal.field.journal_number',
fieldType: 'text',
required: true,
},
reference: {
name: 'manual_journal.field.reference',
fieldType: 'text',
},
journalType: {
name: 'manual_journal.field.journal_type',
fieldType: 'text',
},
currencyCode: {
name: 'manual_journal.field.currency',
fieldType: 'text',
},
exchange_rate: {
name: 'manual_journal.field.exchange_rate',
fieldType: 'number',
},
description: {
name: 'manual_journal.field.description',
fieldType: 'text',
},
entries: {
name: 'Entries',
fieldType: 'collection',
collectionOf: 'object',
collectionMinLength: 2,
required: true,
fields: {
credit: {
name: 'Credit',
fieldType: 'number',
required: true,
},
debit: {
name: 'Debit',
fieldType: 'number',
required: true,
},
accountId: {
name: 'Account',
fieldType: 'relation',
relationModel: 'Account',
relationImportMatch: ['name', 'code'],
required: true,
},
contact: {
name: 'Contact',
fieldType: 'relation',
relationModel: 'Contact',
relationImportMatch: 'displayName',
},
note: {
name: 'Note',
fieldType: 'text',
},
},
},
publish: {
name: 'Publish',
fieldType: 'boolean',
},
},
};
/**
* Status field sorting custom query.
*/
function StatusFieldSortQuery(query, role) {
return query.modify('sortByStatus', role.order);
}
/**
* Status field filter custom query.
*/
function StatusFieldFilterQuery(query, role) {
query.modify('filterByStatus', role.value);
}

View File

@@ -9,7 +9,12 @@ import { Model, mixin } from 'objection';
import { ManualJournalEntry } from './ManualJournalEntry'; import { ManualJournalEntry } from './ManualJournalEntry';
import { Document } from '@/modules/ChromiumlyTenancy/models/Document'; import { Document } from '@/modules/ChromiumlyTenancy/models/Document';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { ManualJournalMeta } from './ManualJournal.meta';
@ExportableModel()
@InjectModelMeta(ManualJournalMeta)
export class ManualJournal extends TenantBaseModel { export class ManualJournal extends TenantBaseModel {
date: Date; date: Date;
journalNumber: string; journalNumber: string;

View File

@@ -34,6 +34,8 @@ import { MailModule } from '../Mail/Mail.module';
import { SendPaymentReceivedMailProcessor } from './processors/PaymentReceivedMailNotification.processor'; import { SendPaymentReceivedMailProcessor } from './processors/PaymentReceivedMailNotification.processor';
import { BullModule } from '@nestjs/bull'; import { BullModule } from '@nestjs/bull';
import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants'; import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants';
import { PaymentsReceivedExportable } from './commands/PaymentsReceivedExportable';
import { PaymentsReceivedImportable } from './commands/PaymentsReceivedImportable';
@Module({ @Module({
controllers: [PaymentReceivesController], controllers: [PaymentReceivesController],
@@ -59,6 +61,8 @@ import { SEND_PAYMENT_RECEIVED_MAIL_QUEUE } from './constants';
GetPaymentsReceivedService, GetPaymentsReceivedService,
SendPaymentReceiveMailNotification, SendPaymentReceiveMailNotification,
SendPaymentReceivedMailProcessor, SendPaymentReceivedMailProcessor,
PaymentsReceivedExportable,
PaymentsReceivedImportable
], ],
exports: [ exports: [
PaymentReceivesApplication, PaymentReceivesApplication,

View File

@@ -1,39 +1,34 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import { IAccountsStructureType, IPaymentsReceivedFilter } from '@/interfaces'; import { PaymentReceivesApplication } from '../PaymentReceived.application';
// import { Exportable } from '@/services/Export/Exportable'; import { IPaymentsReceivedFilter } from '../types/PaymentReceived.types';
// import { PaymentReceivesApplication } from './PaymentReceived.application'; import { EXPORT_SIZE_LIMIT } from '@/modules/Export/constants';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { Exportable } from '@/modules/Export/Exportable';
// @Service() @Injectable()
// export class PaymentsReceivedExportable extends Exportable { export class PaymentsReceivedExportable extends Exportable {
// @Inject() constructor(private readonly paymentReceivedApp: PaymentReceivesApplication) {
// private paymentReceivedApp: PaymentReceivesApplication; super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId * @param {number} tenantId
// * @param {IPaymentsReceivedFilter} query - * @param {IPaymentsReceivedFilter} query -
// * @returns * @returns
// */ */
// public exportable(tenantId: number, query: IPaymentsReceivedFilter) { public exportable(query: IPaymentsReceivedFilter) {
// const filterQuery = (builder) => { const filterQuery = (builder) => {
// builder.withGraphFetched('entries.invoice'); builder.withGraphFetched('entries.invoice');
// builder.withGraphFetched('branch'); builder.withGraphFetched('branch');
// }; };
const parsedQuery = {
// const parsedQuery = { page: 1,
// sortOrder: 'desc', pageSize: EXPORT_SIZE_LIMIT,
// columnSortBy: 'created_at', filterQuery,
// inactiveMode: false, ...query
// ...query, };
// structure: IAccountsStructureType.Flat, return this.paymentReceivedApp
// page: 1, .getPaymentsReceived(parsedQuery)
// pageSize: EXPORT_SIZE_LIMIT, .then((output) => output.paymentReceives);
// filterQuery, }
// } as IPaymentsReceivedFilter; }
// return this.paymentReceivedApp
// .getPaymentReceives(tenantId, parsedQuery)
// .then((output) => output.paymentReceives);
// }
// }

View File

@@ -1,46 +1,45 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { CreatePaymentReceivedDto } from '../dtos/PaymentReceived.dto';
// import { IPaymentReceivedCreateDTO } from '@/interfaces'; import { Injectable } from '@nestjs/common';
// import { Importable } from '@/services/Import/Importable'; import { PaymentsReceiveSampleData } from '../constants';
// import { CreatePaymentReceived } from './commands/CreatePaymentReceived.serivce'; import { CreatePaymentReceivedService } from './CreatePaymentReceived.serivce';
// import { PaymentsReceiveSampleData } from './constants'; import { Importable } from '@/modules/Import/Importable';
// @Service() @Injectable()
// export class PaymentsReceivedImportable extends Importable { export class PaymentsReceivedImportable extends Importable {
// @Inject() constructor(
// private createPaymentReceiveService: CreatePaymentReceived; private readonly createPaymentReceiveService: CreatePaymentReceivedService,
) {
super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {CreatePaymentReceivedDto} createAccountDTO
// * @param {IAccountCreateDTO} createAccountDTO * @returns
// * @returns */
// */ public importable(
// public importable( createPaymentDTO: CreatePaymentReceivedDto,
// tenantId: number, trx?: Knex.Transaction,
// createPaymentDTO: IPaymentReceivedCreateDTO, ) {
// trx?: Knex.Transaction return this.createPaymentReceiveService.createPaymentReceived(
// ) { createPaymentDTO,
// return this.createPaymentReceiveService.createPaymentReceived( trx,
// tenantId, );
// createPaymentDTO, }
// {},
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return PaymentsReceiveSampleData; return PaymentsReceiveSampleData;
// } }
// } }

View File

@@ -1,7 +1,9 @@
import { Model } from 'objection'; import { Model } from 'objection';
import { PaymentReceivedEntry } from './PaymentReceivedEntry'; import { PaymentReceivedEntry } from './PaymentReceivedEntry';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
@ExportableModel()
export class PaymentReceived extends TenantBaseModel { export class PaymentReceived extends TenantBaseModel {
customerId: number; customerId: number;
paymentDate: string; paymentDate: string;

View File

@@ -1,7 +1,12 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ResourceService } from './ResourceService'; import { ResourceService } from './ResourceService';
import { BranchesModule } from '../Branches/Branches.module';
import { WarehousesModule } from '../Warehouses/Warehouses.module';
import { AccountsExportable } from '../Accounts/AccountsExportable.service';
import { AccountsModule } from '../Accounts/Accounts.module';
@Module({ @Module({
imports: [BranchesModule, WarehousesModule, AccountsModule],
providers: [ResourceService], providers: [ResourceService],
exports: [ResourceService], exports: [ResourceService],
}) })

View File

@@ -28,7 +28,7 @@ export class ResourceService {
*/ */
public getResourceModel(inputModelName: string) { public getResourceModel(inputModelName: string) {
const modelName = resourceToModelName(inputModelName); const modelName = resourceToModelName(inputModelName);
const resourceModel = this.moduleRef.get(modelName); const resourceModel = this.moduleRef.get(modelName, { strict: false });
if (!resourceModel) { if (!resourceModel) {
throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND); throw new ServiceError(ERRORS.RESOURCE_MODEL_NOT_FOUND);
@@ -46,7 +46,7 @@ export class ResourceService {
const resourceModel = this.getResourceModel(modelName); const resourceModel = this.getResourceModel(modelName);
// Retrieve the resource meta. // Retrieve the resource meta.
const resourceMeta = resourceModel.getMeta(metakey); const resourceMeta = resourceModel().getMeta(metakey);
// Localization the fields names. // Localization the fields names.
return resourceMeta; return resourceMeta;

View File

@@ -1,5 +1,5 @@
import { camelCase, upperFirst } from 'lodash'; import { camelCase, upperFirst } from 'lodash';
import pluralize from 'pluralize'; import * as pluralize from 'pluralize';
export const resourceToModelName = (resourceName: string): string => { export const resourceToModelName = (resourceName: string): string => {
return upperFirst(camelCase(pluralize.singular(resourceName))); return upperFirst(camelCase(pluralize.singular(resourceName)));

View File

@@ -35,6 +35,8 @@ import { TemplateInjectableModule } from '../TemplateInjectable/TemplateInjectab
import { SaleEstimatePdfTemplate } from '../SaleInvoices/queries/SaleEstimatePdfTemplate.service'; import { SaleEstimatePdfTemplate } from '../SaleInvoices/queries/SaleEstimatePdfTemplate.service';
import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module'; import { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { SendSaleEstimateMailQueue } from './types/SaleEstimates.types'; import { SendSaleEstimateMailQueue } from './types/SaleEstimates.types';
import { SaleEstimatesExportable } from './SaleEstimatesExportable';
import { SaleEstimatesImportable } from './SaleEstimatesImportable';
@Module({ @Module({
imports: [ imports: [
@@ -74,7 +76,13 @@ import { SendSaleEstimateMailQueue } from './types/SaleEstimates.types';
SaleEstimatesApplication, SaleEstimatesApplication,
SendSaleEstimateMail, SendSaleEstimateMail,
GetSaleEstimatePdf, GetSaleEstimatePdf,
SaleEstimatePdfTemplate SaleEstimatePdfTemplate,
SaleEstimatesExportable,
SaleEstimatesImportable
], ],
exports: [
SaleEstimatesExportable,
SaleEstimatesImportable
]
}) })
export class SaleEstimatesModule {} export class SaleEstimatesModule {}

View File

@@ -1,35 +1,41 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import { ISalesInvoicesFilter } from '@/interfaces'; import { EXPORT_SIZE_LIMIT } from '../Export/constants';
// import { Exportable } from '@/services/Export/Exportable'; import { Exportable } from '../Export/Exportable';
// import { SaleEstimatesApplication } from './SaleEstimates.application'; import { ISalesInvoicesFilter } from '../SaleInvoices/SaleInvoice.types';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { SaleEstimatesApplication } from './SaleEstimates.application';
import { ISalesEstimatesFilter } from './types/SaleEstimates.types';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { SaleEstimate } from './models/SaleEstimate';
// @Service() @Injectable()
// export class SaleEstimatesExportable extends Exportable { @ExportableService({ name: SaleEstimate.name })
// @Inject() export class SaleEstimatesExportable extends Exportable {
// private saleEstimatesApplication: SaleEstimatesApplication; constructor(
private readonly saleEstimatesApplication: SaleEstimatesApplication,
) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId * @param {ISalesEstimatesFilter} query -
// * @returns */
// */ public exportable(query: ISalesEstimatesFilter) {
// public exportable(tenantId: number, query: ISalesInvoicesFilter) { const filterQuery = (query) => {
// const filterQuery = (query) => { query.withGraphFetched('branch');
// query.withGraphFetched('branch'); query.withGraphFetched('warehouse');
// query.withGraphFetched('warehouse'); };
// }; const parsedQuery = {
// const parsedQuery = { sortOrder: 'desc',
// sortOrder: 'desc', columnSortBy: 'created_at',
// columnSortBy: 'created_at', ...query,
// ...query, page: 1,
// page: 1, pageSize: EXPORT_SIZE_LIMIT,
// pageSize: EXPORT_SIZE_LIMIT, filterQuery,
// filterQuery, } as ISalesInvoicesFilter;
// } as ISalesInvoicesFilter;
// return this.saleEstimatesApplication return this.saleEstimatesApplication
// .getSaleEstimates(tenantId, parsedQuery) .getSaleEstimates(parsedQuery)
// .then((output) => output.salesEstimates); .then((output) => output.salesEstimates);
// } }
// } }

View File

@@ -1,45 +1,45 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { CreateSaleEstimate } from './commands/CreateSaleEstimate.service';
// import { ISaleEstimateDTO } from '@/interfaces'; import { SaleEstimatesSampleData } from './constants';
// import { CreateSaleEstimate } from './commands/CreateSaleEstimate.service'; import { Injectable } from '@nestjs/common';
// import { Importable } from '@/services/Import/Importable'; import { CreateSaleEstimateDto } from './dtos/SaleEstimate.dto';
// import { SaleEstimatesSampleData } from './constants'; import { Importable } from '../Import/Importable';
// @Service() @Injectable()
// export class SaleEstimatesImportable extends Importable { export class SaleEstimatesImportable extends Importable{
// @Inject() constructor(
// private createEstimateService: CreateSaleEstimate; private readonly createEstimateService: CreateSaleEstimate
) {
super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {CreateSaleEstimateDto} createAccountDTO
// * @param {IAccountCreateDTO} createAccountDTO * @returns
// * @returns */
// */ public importable(
// public importable( createEstimateDTO: CreateSaleEstimateDto,
// tenantId: number, trx?: Knex.Transaction
// createEstimateDTO: ISaleEstimateDTO, ) {
// trx?: Knex.Transaction return this.createEstimateService.createEstimate(
// ) { createEstimateDTO,
// return this.createEstimateService.createEstimate( trx
// tenantId, );
// createEstimateDTO, }
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return SaleEstimatesSampleData; return SaleEstimatesSampleData;
// } }
// } }

View File

@@ -2,8 +2,9 @@ import * as moment from 'moment';
import { Model } from 'objection'; import { Model } from 'objection';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
@Injectable() @ExportableModel()
export class SaleEstimate extends TenantBaseModel { export class SaleEstimate extends TenantBaseModel {
exchangeRate!: number; exchangeRate!: number;
amount!: number; amount!: number;

View File

@@ -56,6 +56,8 @@ import { SaleInvoiceCostGLEntries } from './SaleInvoiceCostGLEntries';
import { InvoicePaymentsGLEntriesRewrite } from './InvoicePaymentsGLRewrite'; import { InvoicePaymentsGLEntriesRewrite } from './InvoicePaymentsGLRewrite';
import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module'; import { PaymentsReceivedModule } from '../PaymentReceived/PaymentsReceived.module';
import { SaleInvoicesCost } from './SalesInvoicesCost'; import { SaleInvoicesCost } from './SalesInvoicesCost';
import { SaleInvoicesExportable } from './commands/SaleInvoicesExportable';
import { SaleInvoicesImportable } from './commands/SaleInvoicesImportable';
@Module({ @Module({
imports: [ imports: [
@@ -118,7 +120,15 @@ import { SaleInvoicesCost } from './SalesInvoicesCost';
SaleInvoiceWriteInventoryTransactionsSubscriber, SaleInvoiceWriteInventoryTransactionsSubscriber,
InvoicePaymentsGLEntriesRewrite, InvoicePaymentsGLEntriesRewrite,
SaleInvoicesCost, SaleInvoicesCost,
SaleInvoicesExportable,
SaleInvoicesImportable,
],
exports: [
GetSaleInvoice,
SaleInvoicesCost,
SaleInvoicePdf,
SaleInvoicesExportable,
SaleInvoicesImportable,
], ],
exports: [GetSaleInvoice, SaleInvoicesCost, SaleInvoicePdf],
}) })
export class SaleInvoicesModule {} export class SaleInvoicesModule {}

View File

@@ -1,35 +1,39 @@
// import { Inject, Service } from 'typedi'; import { Exportable } from '@/modules/Export/Exportable';
// import { ISalesInvoicesFilter } from '@/interfaces'; import { Injectable } from '@nestjs/common';
// import { SaleInvoiceApplication } from './SaleInvoices.application'; import { SaleInvoiceApplication } from '../SaleInvoices.application';
// import { Exportable } from '@/services/Export/Exportable'; import { ISalesInvoicesFilter } from '../SaleInvoice.types';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { EXPORT_SIZE_LIMIT } from '@/modules/Export/constants';
import { ExportableService } from '@/modules/Export/decorators/ExportableModel.decorator';
import { SaleInvoice } from '../models/SaleInvoice';
// @Service() @Injectable()
// export class SaleInvoicesExportable extends Exportable { @ExportableService({ name: SaleInvoice.name })
// @Inject() export class SaleInvoicesExportable extends Exportable{
// private saleInvoicesApplication: SaleInvoiceApplication; constructor(
private readonly saleInvoicesApplication: SaleInvoiceApplication,
) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId */
// * @returns public exportable(query: ISalesInvoicesFilter) {
// */ const filterQuery = (query) => {
// public exportable(tenantId: number, query: ISalesInvoicesFilter) { query.withGraphFetched('branch');
// const filterQuery = (query) => { query.withGraphFetched('warehouse');
// query.withGraphFetched('branch'); };
// query.withGraphFetched('warehouse'); const parsedQuery = {
// }; sortOrder: 'desc',
// const parsedQuery = { columnSortBy: 'created_at',
// sortOrder: 'desc', ...query,
// columnSortBy: 'created_at', page: 1,
// ...query, pageSize: EXPORT_SIZE_LIMIT,
// page: 1, filterQuery,
// pageSize: EXPORT_SIZE_LIMIT, } as ISalesInvoicesFilter;
// filterQuery,
// } as ISalesInvoicesFilter;
// return this.saleInvoicesApplication return this.saleInvoicesApplication
// .getSaleInvoices(tenantId, parsedQuery) .getSaleInvoices(parsedQuery)
// .then((output) => output.salesInvoices); .then((output) => output.salesInvoices);
// } }
// } }

View File

@@ -1,46 +1,39 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import { Knex } from 'knex'; import { Knex } from 'knex';
// import { ISaleInvoiceCreateDTO } from '@/interfaces'; import { CreateSaleInvoice } from './CreateSaleInvoice.service';
// import { CreateSaleInvoice } from './commands/CreateSaleInvoice.service'; import { Importable } from '@/modules/Import/Importable';
// import { Importable } from '@/services/Import/Importable'; import { CreateSaleInvoiceDto } from '../dtos/SaleInvoice.dto';
// import { SaleInvoicesSampleData } from './constants'; import { SaleInvoicesSampleData } from '../constants';
// @Service() @Injectable()
// export class SaleInvoicesImportable extends Importable { export class SaleInvoicesImportable extends Importable {
// @Inject() constructor(private readonly createInvoiceService: CreateSaleInvoice) {
// private createInvoiceService: CreateSaleInvoice; super();
}
// /** /**
// * Importing to account service. * Importing to account service.
// * @param {number} tenantId * @param {CreateSaleInvoiceDto} createAccountDTO
// * @param {IAccountCreateDTO} createAccountDTO */
// * @returns public importable(
// */ createAccountDTO: CreateSaleInvoiceDto,
// public importable( trx?: Knex.Transaction,
// tenantId: number, ) {
// createAccountDTO: ISaleInvoiceCreateDTO, return this.createInvoiceService.createSaleInvoice(createAccountDTO, trx);
// trx?: Knex.Transaction }
// ) {
// return this.createInvoiceService.createSaleInvoice(
// tenantId,
// createAccountDTO,
// {},
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return SaleInvoicesSampleData; return SaleInvoicesSampleData;
// } }
// } }

View File

@@ -0,0 +1,316 @@
import { Features } from "@/common/types/Features";
export const SaleInvoiceMeta = {
defaultFilterField: 'customer',
defaultSort: {
sortOrder: 'DESC',
sortField: 'created_at',
},
exportable: true,
exportFlattenOn: 'entries',
importable: true,
importAggregator: 'group',
importAggregateOn: 'entries',
importAggregateBy: 'invoiceNo',
print: {
pageTitle: 'Sale invoices',
},
fields: {
customer: {
name: 'invoice.field.customer',
column: 'customer_id',
fieldType: 'relation',
relationType: 'enumeration',
relationKey: 'customer',
relationEntityLabel: 'display_name',
relationEntityKey: 'id',
},
invoice_date: {
name: 'invoice.field.invoice_date',
column: 'invoice_date',
fieldType: 'date',
},
due_date: {
name: 'invoice.field.due_date',
column: 'due_date',
fieldType: 'date',
},
invoice_no: {
name: 'invoice.field.invoice_no',
column: 'invoice_no',
fieldType: 'text',
},
reference_no: {
name: 'invoice.field.reference_no',
column: 'reference_no',
fieldType: 'text',
},
invoice_message: {
name: 'invoice.field.invoice_message',
column: 'invoice_message',
fieldType: 'text',
},
terms_conditions: {
name: 'invoice.field.terms_conditions',
column: 'terms_conditions',
fieldType: 'text',
},
amount: {
name: 'invoice.field.amount',
column: 'balance',
fieldType: 'number',
},
payment_amount: {
name: 'invoice.field.payment_amount',
column: 'payment_amount',
fieldType: 'number',
},
due_amount: {
// calculated.
name: 'invoice.field.due_amount',
column: 'due_amount',
fieldType: 'number',
virtualColumn: true,
},
status: {
name: 'invoice.field.status',
fieldType: 'enumeration',
options: [
{ key: 'draft', label: 'invoice.field.status.draft' },
{ key: 'delivered', label: 'invoice.field.status.delivered' },
{ key: 'unpaid', label: 'invoice.field.status.unpaid' },
{ key: 'overdue', label: 'invoice.field.status.overdue' },
{ key: 'partially-paid', label: 'invoice.field.status.partially-paid' },
{ key: 'paid', label: 'invoice.field.status.paid' },
],
filterCustomQuery: StatusFieldFilterQuery,
sortCustomQuery: StatusFieldSortQuery,
},
created_at: {
name: 'invoice.field.created_at',
column: 'created_at',
fieldType: 'date',
},
},
columns: {
invoiceDate: {
name: 'invoice.field.invoice_date',
type: 'date',
accessor: 'invoiceDateFormatted',
},
dueDate: {
name: 'invoice.field.due_date',
type: 'date',
accessor: 'dueDateFormatted',
},
referenceNo: {
name: 'invoice.field.reference_no',
type: 'text',
},
invoiceNo: {
name: 'invoice.field.invoice_no',
type: 'text',
},
customer: {
name: 'invoice.field.customer',
type: 'text',
accessor: 'customer.displayName',
},
amount: {
name: 'invoice.field.amount',
type: 'text',
accessor: 'balanceAmountFormatted',
},
exchangeRate: {
name: 'invoice.field.exchange_rate',
type: 'number',
printable: false,
},
currencyCode: {
name: 'invoice.field.currency',
type: 'text',
printable: false,
},
paidAmount: {
name: 'Paid Amount',
accessor: 'paymentAmountFormatted',
},
dueAmount: {
name: 'Due Amount',
accessor: 'dueAmountFormatted',
},
invoiceMessage: {
name: 'invoice.field.invoice_message',
type: 'text',
printable: false,
},
termsConditions: {
name: 'invoice.field.terms_conditions',
type: 'text',
printable: false,
},
delivered: {
name: 'invoice.field.delivered',
type: 'boolean',
printable: false,
accessor: 'isDelivered',
},
entries: {
name: 'Entries',
accessor: 'entries',
type: 'collection',
collectionOf: 'object',
columns: {
itemName: {
name: 'Item Name',
accessor: 'item.name',
},
rate: {
name: 'Item Rate',
accessor: 'rateFormatted',
},
quantity: {
name: 'Item Quantity',
accessor: 'quantityFormatted',
},
description: {
name: 'Item Description',
printable: false,
},
amount: {
name: 'Item Amount',
accessor: 'totalFormatted',
},
},
},
branch: {
name: 'Branch',
type: 'text',
accessor: 'branch.name',
features: [Features.BRANCHES],
},
warehouse: {
name: 'Warehouse',
type: 'text',
accessor: 'warehouse.name',
features: [Features.BRANCHES],
},
},
fields2: {
invoiceDate: {
name: 'invoice.field.invoice_date',
fieldType: 'date',
required: true,
},
dueDate: {
name: 'invoice.field.due_date',
fieldType: 'date',
required: true,
},
referenceNo: {
name: 'invoice.field.reference_no',
fieldType: 'text',
},
invoiceNo: {
name: 'invoice.field.invoice_no',
fieldType: 'text',
},
customerId: {
name: 'invoice.field.customer',
fieldType: 'relation',
relationModel: 'Contact',
relationImportMatch: 'displayName',
required: true,
},
exchangeRate: {
name: 'invoice.field.exchange_rate',
fieldType: 'number',
printable: false,
},
currencyCode: {
name: 'invoice.field.currency',
fieldType: 'text',
printable: false,
},
invoiceMessage: {
name: 'invoice.field.invoice_message',
fieldType: 'text',
printable: false,
},
termsConditions: {
name: 'invoice.field.terms_conditions',
fieldType: 'text',
printable: false,
},
entries: {
name: 'invoice.field.entries',
fieldType: 'collection',
collectionOf: 'object',
collectionMinLength: 1,
required: true,
fields: {
itemId: {
name: 'invoice.field.item_name',
fieldType: 'relation',
relationModel: 'Item',
relationImportMatch: ['name', 'code'],
required: true,
importHint: 'Matches the item name or code.',
},
rate: {
name: 'invoice.field.rate',
fieldType: 'number',
required: true,
},
quantity: {
name: 'invoice.field.quantity',
fieldType: 'number',
required: true,
},
description: {
name: 'invoice.field.description',
fieldType: 'text',
},
},
},
delivered: {
name: 'invoice.field.delivered',
fieldType: 'boolean',
printable: false,
},
branchId: {
name: 'Branch',
fieldType: 'relation',
relationModel: 'Branch',
relationImportMatch: ['name', 'code'],
features: [Features.BRANCHES],
required: true,
},
warehouseId: {
name: 'Warehouse',
fieldType: 'relation',
relationModel: 'Warehouse',
relationImportMatch: ['name', 'code'],
features: [Features.WAREHOUSES],
required: true,
},
},
};
/**
* Status field filter custom query.
*/
function StatusFieldFilterQuery(query, role) {
query.modify('statusFilter', role.value);
}
/**
* Status field sort custom query.
*/
function StatusFieldSortQuery(query, role) {
query.modify('sortByStatus', role.order);
}

View File

@@ -11,11 +11,15 @@ 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 { ISearchRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel'; import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { PaymentIntegrationTransactionLink } from '../SaleInvoice.types';
import { TransactionPaymentServiceEntry } from '@/modules/PaymentServices/models/TransactionPaymentServiceEntry.model'; import { TransactionPaymentServiceEntry } from '@/modules/PaymentServices/models/TransactionPaymentServiceEntry.model';
import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator'; import { InjectAttachable } from '@/modules/Attachments/decorators/InjectAttachable.decorator';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
import { SaleInvoiceMeta } from './SaleInvoice.meta';
@InjectAttachable() @InjectAttachable()
@ExportableModel()
@InjectModelMeta(SaleInvoiceMeta)
export class SaleInvoice extends TenantBaseModel{ export class SaleInvoice extends TenantBaseModel{
public taxAmountWithheld: number; public taxAmountWithheld: number;
public balance: number; public balance: number;

View File

@@ -1,18 +1,22 @@
import * as R from 'ramda'; import * as R from 'ramda';
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { SaleInvoiceTransformer } from './SaleInvoice.transformer'; import { SaleInvoiceTransformer } from './SaleInvoice.transformer';
import { Injectable } from '@nestjs/common';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service'; import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { DynamicListService } from '@/modules/DynamicListing/DynamicList.service'; 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'; import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
@Injectable() @Injectable()
export class GetSaleInvoicesService { export class GetSaleInvoicesService {
constructor( constructor(
private readonly dynamicListService: DynamicListService, private readonly dynamicListService: DynamicListService,
private readonly transformer: TransformerInjectable, private readonly transformer: TransformerInjectable,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
) {} ) {}
/** /**
@@ -33,7 +37,8 @@ export class GetSaleInvoicesService {
SaleInvoice, SaleInvoice,
filter, filter,
); );
const { results, pagination } = await SaleInvoice.query() const { results, pagination } = await this.saleInvoiceModel()
.query()
.onBuild((builder) => { .onBuild((builder) => {
builder.withGraphFetched('entries.item'); builder.withGraphFetched('entries.item');
builder.withGraphFetched('customer'); builder.withGraphFetched('customer');

View File

@@ -35,6 +35,8 @@ import { MailNotificationModule } from '../MailNotification/MailNotification.mod
import { SendSaleReceiptMailProcess } from './processes/SendSaleReceiptMail.process'; import { SendSaleReceiptMailProcess } from './processes/SendSaleReceiptMail.process';
import { MailModule } from '../Mail/Mail.module'; import { MailModule } from '../Mail/Mail.module';
import { SendSaleReceiptMailQueue } from './constants'; import { SendSaleReceiptMailQueue } from './constants';
import { SaleReceiptsExportable } from './commands/SaleReceiptsExportable';
import { SaleReceiptsImportable } from './commands/SaleReceiptsImportable';
@Module({ @Module({
controllers: [SaleReceiptsController], controllers: [SaleReceiptsController],
@@ -75,6 +77,8 @@ import { SendSaleReceiptMailQueue } from './constants';
SaleReceiptInventoryTransactions, SaleReceiptInventoryTransactions,
SaleReceiptInventoryTransactionsSubscriber, SaleReceiptInventoryTransactionsSubscriber,
SendSaleReceiptMailProcess, SendSaleReceiptMailProcess,
SaleReceiptsExportable,
SaleReceiptsImportable,
], ],
}) })
export class SaleReceiptsModule {} export class SaleReceiptsModule {}

View File

@@ -1,35 +1,35 @@
// import { Inject, Service } from 'typedi'; import { Exportable } from '@/modules/Export/Exportable';
// import { ISalesReceiptsFilter } from '@/interfaces'; import { Injectable } from '@nestjs/common';
// import { Exportable } from '@/services/Export/Exportable'; import { SaleReceiptApplication } from '../SaleReceiptApplication.service';
// import { SaleReceiptApplication } from './SaleReceiptApplication'; import { ISalesReceiptsFilter } from '../types/SaleReceipts.types';
// import { EXPORT_SIZE_LIMIT } from '@/services/Export/constants'; import { EXPORT_SIZE_LIMIT } from '@/modules/Export/constants';
// @Service() @Injectable()
// export class SaleReceiptsExportable extends Exportable { export class SaleReceiptsExportable extends Exportable {
// @Inject() constructor(private readonly saleReceiptsApp: SaleReceiptApplication) {
// private saleReceiptsApp: SaleReceiptApplication; super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId * @param {ISalesReceiptsFilter} query -
// * @returns */
// */ public exportable(query: ISalesReceiptsFilter) {
// public exportable(tenantId: number, query: ISalesReceiptsFilter) { const filterQuery = (query) => {
// const filterQuery = (query) => { query.withGraphFetched('branch');
// query.withGraphFetched('branch'); query.withGraphFetched('warehouse');
// query.withGraphFetched('warehouse'); };
// }; const parsedQuery = {
// const parsedQuery = { sortOrder: 'desc',
// sortOrder: 'desc', columnSortBy: 'created_at',
// columnSortBy: 'created_at', ...query,
// ...query, page: 1,
// page: 1, pageSize: EXPORT_SIZE_LIMIT,
// pageSize: EXPORT_SIZE_LIMIT, filterQuery,
// filterQuery, } as ISalesReceiptsFilter;
// } as ISalesReceiptsFilter;
// return this.saleReceiptsApp return this.saleReceiptsApp
// .getSaleReceipts(tenantId, parsedQuery) .getSaleReceipts(parsedQuery)
// .then((output) => output.data); .then((output) => output.data);
// } }
// } }

View File

@@ -1,45 +1,44 @@
// import { Inject, Service } from 'typedi'; import { Injectable } from '@nestjs/common';
// import { Knex } from 'knex'; import { Knex } from 'knex';
// import { IAccountCreateDTO, ISaleReceiptDTO } from '@/interfaces'; import { CreateSaleReceipt } from './CreateSaleReceipt.service';
// import { CreateSaleReceipt } from './commands/CreateSaleReceipt.service'; import { Importable } from '@/modules/Import/Importable';
// import { Importable } from '@/services/Import/Importable'; import { CreateSaleReceiptDto } from '../dtos/SaleReceipt.dto';
// import { SaleReceiptsSampleData } from './constants'; import { SaleReceiptsSampleData } from '../constants';
// @Service() @Injectable()
// export class SaleReceiptsImportable extends Importable { export class SaleReceiptsImportable extends Importable {
// @Inject() constructor(private readonly createReceiptService: CreateSaleReceipt) {
// private createReceiptService: CreateSaleReceipt; super();
}
// /** /**
// * Importing to sale receipts service. * Importing to sale receipts service.
// * @param {number} tenantId * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO * @param {IAccountCreateDTO} createAccountDTO
// * @returns * @returns
// */ */
// public importable( public importable(
// tenantId: number, createAccountDTO: CreateSaleReceiptDto,
// createAccountDTO: ISaleReceiptDTO, trx?: Knex.Transaction,
// trx?: Knex.Transaction ) {
// ) { return this.createReceiptService.createSaleReceipt(
// return this.createReceiptService.createSaleReceipt( createAccountDTO,
// tenantId, trx,
// createAccountDTO, );
// trx }
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return SaleReceiptsSampleData; return SaleReceiptsSampleData;
// } }
// } }

View File

@@ -12,6 +12,7 @@ import { MetadataModelMixin } from '@/modules/DynamicListing/models/MetadataMode
import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableModel'; import { ResourceableModelMixin } from '@/modules/Resource/models/ResourcableModel';
import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel'; import { CustomViewBaseModelMixin } from '@/modules/CustomViews/CustomViewBaseModel';
import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel'; import { SearchableBaseModelMixin } from '@/modules/DynamicListing/models/SearchableBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
const ExtendedModel = R.pipe( const ExtendedModel = R.pipe(
CustomViewBaseModelMixin, CustomViewBaseModelMixin,
@@ -20,6 +21,7 @@ const ExtendedModel = R.pipe(
MetadataModelMixin, MetadataModelMixin,
)(BaseModel); )(BaseModel);
@ExportableModel()
export class SaleReceipt extends ExtendedModel { export class SaleReceipt extends ExtendedModel {
public amount!: number; public amount!: number;
public exchangeRate!: number; public exchangeRate!: number;

View File

@@ -21,6 +21,8 @@ import { WriteTaxTransactionsItemEntries } from './WriteTaxTransactionsItemEntri
import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate'; import { SyncItemTaxRateOnEditTaxRate } from './SyncItemTaxRateOnEditTaxRate';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module'; import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { TaxRateTransaction } from './models/TaxRateTransaction.model'; import { TaxRateTransaction } from './models/TaxRateTransaction.model';
import { TaxRatesExportable } from './TaxRatesExportable';
import { TaxRatesImportable } from './TaxRatesImportable';
const models = [RegisterTenancyModel(TaxRateTransaction)]; const models = [RegisterTenancyModel(TaxRateTransaction)];
@@ -47,6 +49,8 @@ const models = [RegisterTenancyModel(TaxRateTransaction)];
SyncItemTaxRateOnEditTaxSubscriber, SyncItemTaxRateOnEditTaxSubscriber,
WriteTaxTransactionsItemEntries, WriteTaxTransactionsItemEntries,
SyncItemTaxRateOnEditTaxRate, SyncItemTaxRateOnEditTaxRate,
TaxRatesExportable,
TaxRatesImportable
], ],
exports: [ItemEntriesTaxTransactions, ...models], exports: [ItemEntriesTaxTransactions, ...models],
}) })

View File

@@ -1,18 +1,20 @@
// import { Inject, Service } from 'typedi'; import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
// import { Exportable } from '../Export/Exportable'; import { Exportable } from '../Export/Exportable';
// import { TaxRatesApplication } from './TaxRate.application'; import { TaxRateModel } from './models/TaxRate.model';
import { TaxRatesApplication } from './TaxRate.application';
import { Injectable } from '@nestjs/common';
// @Service() @Injectable()
// export class TaxRatesExportable extends Exportable { @ExportableService({ name: TaxRateModel.name })
// @Inject() export class TaxRatesExportable extends Exportable {
// private taxRatesApplication: TaxRatesApplication; constructor(private readonly taxRatesApplication: TaxRatesApplication) {
super();
}
// /** /**
// * Retrieves the accounts data to exportable sheet. * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId */
// * @returns public exportable() {
// */ return this.taxRatesApplication.getTaxRates();
// public exportable(tenantId: number) { }
// return this.taxRatesApplication.getTaxRates(tenantId); }
// }
// }

View File

@@ -1,46 +1,40 @@
// import { Inject, Service } from 'typedi'; import { Knex } from 'knex';
// import { Knex } from 'knex'; import { CreateTaxRate } from './commands/CreateTaxRate.service';
// import { ICreateTaxRateDTO } from '@/interfaces'; import { Importable } from '../Import/Importable';
// import { CreateTaxRate } from './commands/CreateTaxRate.service'; import { TaxRatesSampleData } from './TaxRatesImportable.SampleData';
// import { Importable } from '../Import/Importable'; import { CreateTaxRateDto } from './dtos/TaxRate.dto';
// import { TaxRatesSampleData } from './TaxRatesImportable.SampleData'; import { Injectable } from '@nestjs/common';
// @Service() @Injectable()
// export class TaxRatesImportable extends Importable { export class TaxRatesImportable extends Importable {
// @Inject() constructor(private readonly createTaxRateService: CreateTaxRate) {
// private createTaxRateService: CreateTaxRate; super();
}
// /** /**
// * Importing to tax rate creating service. * Importing to tax rate creating service.
// * @param {number} tenantId - * @param {CreateTaxRateDto} ICreateTaxRateDTO -
// * @param {ICreateTaxRateDTO} ICreateTaxRateDTO - * @param {Knex.Transaction} trx -
// * @param {Knex.Transaction} trx - */
// * @returns public importable(
// */ createAccountDTO: CreateTaxRateDto,
// public importable( trx?: Knex.Transaction,
// tenantId: number, ) {
// createAccountDTO: ICreateTaxRateDTO, return this.createTaxRateService.createTaxRate(createAccountDTO, trx);
// trx?: Knex.Transaction }
// ) {
// return this.createTaxRateService.createTaxRate(
// tenantId,
// createAccountDTO,
// trx
// );
// }
// /** /**
// * Concurrrency controlling of the importing process. * Concurrrency controlling of the importing process.
// * @returns {number} * @returns {number}
// */ */
// public get concurrency() { public get concurrency() {
// return 1; return 1;
// } }
// /** /**
// * Retrieves the sample data that used to download accounts sample sheet. * Retrieves the sample data that used to download accounts sample sheet.
// */ */
// public sampleData(): any[] { public sampleData(): any[] {
// return TaxRatesSampleData; return TaxRatesSampleData;
// } }
// } }

View File

@@ -5,7 +5,9 @@ import { mixin, Model, raw } from 'objection';
// import TaxRateMeta from './TaxRate.settings'; // import TaxRateMeta from './TaxRate.settings';
// import ModelSetting from './ModelSetting'; // import ModelSetting from './ModelSetting';
import { BaseModel } from '@/models/Model'; import { BaseModel } from '@/models/Model';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
@ExportableModel()
export class TaxRateModel extends BaseModel { export class TaxRateModel extends BaseModel {
active!: boolean; active!: boolean;
code!: string; code!: string;

View File

@@ -82,6 +82,7 @@ const models = [
TenantUser, TenantUser,
]; ];
/** /**
* Decorator factory that registers a model with the tenancy system. * Decorator factory that registers a model with the tenancy system.
* @param model The model class to register * @param model The model class to register

Some files were not shown because too many files have changed in this diff Show More