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 &
IModelMetaFieldWithFields &
(
| IModelMetaFieldText
| IModelMetaFieldNumber

View File

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

View File

@@ -2,8 +2,17 @@ import { AccountsApplication } from './AccountsApplication.service';
import { Exportable } from '../Export/Exportable';
import { EXPORT_SIZE_LIMIT } from '../Export/constants';
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 {
/**
* @param {AccountsApplication} accountsApplication
*/
constructor(private readonly accountsApplication: AccountsApplication) {
super();
}

View File

@@ -1,45 +1,43 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { IAccountCreateDTO } from '@/interfaces';
// import { CreateAccount } from './CreateAccount.service';
// import { Importable } from '../Import/Importable';
// import { AccountsSampleData } from './AccountsImportable.SampleData';
import { Knex } from 'knex';
import { Injectable } from '@nestjs/common';
import { Importable } from '../Import/Importable';
import { AccountsSampleData } from './AccountsImportable.SampleData';
import { CreateAccountDTO } from './CreateAccount.dto';
import { CreateAccountService } from './CreateAccount.service';
// @Service()
// export class AccountsImportable extends Importable {
// @Inject()
// private createAccountService: CreateAccount;
@Injectable()
export class AccountsImportable extends Importable {
constructor(private readonly createAccountService: CreateAccountService) {
super();
}
// /**
// * Importing to account service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: IAccountCreateDTO,
// trx?: Knex.Transaction
// ) {
// return this.createAccountService.createAccount(
// tenantId,
// createAccountDTO,
// trx
// );
// }
/**
* Importing to account service.
* @param {CreateAccountDTO} createAccountDTO - Create account dto.
* @returns
*/
public importable(
createAccountDTO: CreateAccountDTO,
trx?: Knex.Transaction,
) {
return this.createAccountService.createAccount(
createAccountDTO,
trx,
);
}
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return AccountsSampleData;
// }
// }
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
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 */
// import { mixin, Model } from 'objection';
import { castArray } from 'lodash';
import { Model } from 'objection';
import DependencyGraph from '@/libs/dependency-graph';
import {
ACCOUNT_TYPES,
getAccountsSupportsMultiCurrency,
} 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 { Model } from 'objection';
import { PlaidItem } from '@/modules/BankingPlaid/models/PlaidItem';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { flatToNestedArray } from '@/utils/flat-to-nested-array';
// import AccountSettings from './Account.Settings';
// import { DEFAULT_VIEWS } from '@/modules/Accounts/constants';
// import { buildFilterQuery, buildSortColumnQuery } from '@/lib/ViewRolesBuilder';
// import { flatToNestedArray } from 'utils';
import { ExportableModel } from '../../Export/decorators/ExportableModel.decorator';
import { AccountMeta } from './Account.meta';
import { InjectModelMeta } from '@/modules/Tenancy/TenancyModels/decorators/InjectModelMeta.decorator';
@ExportableModel()
@InjectModelMeta(AccountMeta)
export class Account extends TenantBaseModel {
public name!: string;
public slug!: string;

View File

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

View File

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

View File

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

View File

@@ -20,12 +20,12 @@ export class BillPaymentsApplication {
private deleteBillPaymentService: DeleteBillPayment,
private getBillPaymentService: GetBillPayment,
private getPaymentBillsService: GetPaymentBills,
// private getBillPaymentsService: GetBillPayments,
private getBillPaymentsService: GetBillPayments,
) {}
/**
* Creates a bill payment with associated GL entries.
* @param {IBillPaymentDTO} billPaymentDTO
* @param {IBillPaymentDTO} billPaymentDTO - Create bill payment dto.
* @returns {Promise<IBillPayment>}
*/
public createBillPayment(billPaymentDTO: CreateBillPaymentDto) {
@@ -34,7 +34,7 @@ export class BillPaymentsApplication {
/**
* Delets the given bill payment with associated GL entries.
* @param {number} billPaymentId
* @param {number} billPaymentId - Bill payment id.
*/
public deleteBillPayment(billPaymentId: number) {
return this.deleteBillPaymentService.deleteBillPayment(billPaymentId);
@@ -58,13 +58,10 @@ export class BillPaymentsApplication {
/**
* Retrieves bill payments list.
* @param {number} tenantId
* @param filterDTO
* @returns
*/
// public getBillPayments(filterDTO: IBillPaymentsFilter) {
// return this.getBillPaymentsService.getBillPayments(filterDTO);
// }
public getBillPayments() {
// return this.getBillPaymentsService.getBillPayments(filterDTO);
}
/**
* 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 { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
import { BillsExportable } from './commands/BillsExportable';
import { BillsImportable } from './commands/BillsImportable';
@Module({
imports: [
@@ -57,7 +59,10 @@ import { InventoryCostModule } from '../InventoryCost/InventoryCost.module';
BillGLEntriesSubscriber,
BillInventoryTransactions,
BillWriteInventoryTransactionsSubscriber,
BillsExportable,
BillsImportable
],
controllers: [BillsController],
exports: [BillsExportable, BillsImportable],
})
export class BillsModule {}

View File

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

View File

@@ -1,46 +1,46 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { Importable } from '@/services/Import/Importable';
// import { CreateBill } from './CreateBill.service';
// import { IBillDTO } from '@/interfaces';
// import { BillsSampleData } from '../Bills.constants';
import { Knex } from 'knex';
import { CreateBill } from './CreateBill.service';
import { BillsSampleData } from '../Bills.constants';
import { Injectable } from '@nestjs/common';
import { Importable } from '@/modules/Import/Importable';
import { CreateBillDto } from '../dtos/Bill.dto';
// @Service()
// export class BillsImportable extends Importable {
// @Inject()
// private createBillService: CreateBill;
@Injectable()
export class BillsImportable extends Importable {
constructor(
private readonly createBillService: CreateBill,
) {
super();
}
// /**
// * Importing to account service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: IBillDTO,
// trx?: Knex.Transaction
// ) {
// return this.createBillService.createBill(
// tenantId,
// createAccountDTO,
// {},
// trx
// );
// }
/**
* Importing to account service.
* @param {number} tenantId
* @param {IAccountCreateDTO} createAccountDTO
* @returns
*/
public importable(
createBillDto: CreateBillDto,
trx?: Knex.Transaction
) {
return this.createBillService.createBill(
createBillDto,
trx
);
}
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return BillsSampleData;
// }
// }
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
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 * as moment from 'moment';
import * as R from 'ramda';
@@ -12,9 +13,13 @@ import { BaseModel, PaginationQueryBuilderType } from '@/models/Model';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';
import { DiscountType } from '@/common/types/Discount';
import type { Knex, QueryBuilder } from 'knex';
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 {
public amount: number;
public paymentAmount: number;

View File

@@ -13,8 +13,7 @@ export class GetBillPayments {
/**
* Retrieve the specific bill associated payment transactions.
* @param {number} billId
* @returns {}
* @param {number} billId - Bill id.
*/
public getBillPayments = async (billId: number) => {
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 ModelSetting from './ModelSetting';
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{
name!: string;
code!: string;

View File

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

View File

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

View File

@@ -2,11 +2,13 @@ import { DiscountType } from '@/common/types/Discount';
import { BaseModel } from '@/models/Model';
import { Branch } from '@/modules/Branches/models/Branch.model';
import { Customer } from '@/modules/Customers/models/Customer';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
import { Warehouse } from '@/modules/Warehouses/models/Warehouse.model';
import { mixin, Model, raw } from 'objection';
@ExportableModel()
export class CreditNote extends TenantBaseModel {
public amount: number;
public exchangeRate: number;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,9 @@ import * as moment from 'moment';
import { ExpenseCategory } from './ExpenseCategory.model';
import { Account } from '@/modules/Accounts/models/Account.model';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { ExportableModel } from '@/modules/Export/decorators/ExportableModel.decorator';
@ExportableModel()
export class Expense extends TenantBaseModel {
totalAmount!: number;
currencyCode!: string;

View File

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

View File

@@ -4,9 +4,32 @@ import { ExportResourceService } from './ExportService';
import { ExportPdf } from './ExportPdf';
import { ExportAls } from './ExportAls';
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({
providers: [ExportResourceService, ExportPdf, ExportAls, ExportApplication],
imports: [
...models,
ResourceModule,
TemplateInjectableModule,
ChromiumlyTenancyModule,
AccountsModule
],
providers: [
ExportResourceService,
ExportPdf,
ExportAls,
ExportApplication,
ExportableRegistry
],
exports: [...models],
controllers: [ExportController],
})
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.
*/
public run<T>(callback: () => T): T {
return this.als.run<T>(new Map(), () => {
return this.als.run<T, []>(new Map(), () => {
this.markAsExport();
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 xlsx from 'xlsx';
import { Injectable } from '@nestjs/common';
import * as xlsx from 'xlsx';
import * as R from 'ramda';
import { get } from 'lodash';
import { sanitizeResourceName } from '../Import/_utils';
import { ExportableResources } from './ExportResources';
import { ServiceError } from '@/exceptions';
import { Errors, ExportFormat } from './common';
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
import { flatDataCollections, getDataAccessor } from './utils';
import { ExportPdf } from './ExportPdf';
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()
export class ExportResourceService {
constructor(
private readonly exportAls: ExportAls,
private readonly exportPdf: ExportPdf,
private readonly exportableResources: ExportableResources,
private readonly resourceService: ResourceService,
private readonly moduleRef: ModuleRef,
) {}
/**
*
* @param {string} resourceName
* @param {ExportFormat} format
* @returns
*/
public async export(
resourceName: string,
format: ExportFormat = ExportFormat.Csv
format: ExportFormat = ExportFormat.Csv,
) {
return this.exportAls.run(() =>
this.exportAlsRun(resourceName, format)
);
return this.exportAls.run(() => this.exportAlsRun(resourceName, format));
}
/**
@@ -43,18 +41,18 @@ export class ExportResourceService {
*/
public async exportAlsRun(
resourceName: string,
format: ExportFormat = ExportFormat.Csv
format: ExportFormat = ExportFormat.Csv,
) {
const resource = sanitizeResourceName(resourceName);
const resourceMeta = this.getResourceMeta(tenantId, resource);
const resourceColumns = this.resourceService.getResourceColumns(
tenantId,
resource
);
const resourceMeta = this.getResourceMeta(resource);
const resourceColumns = this.resourceService.getResourceColumns(resource);
this.validateResourceMeta(resourceMeta);
const data = await this.getExportableData(tenantId, resource);
const transformed = this.transformExportedData(tenantId, resource, data);
const data = await this.getExportableData(resource);
const transformed = this.transformExportedData(resource, data);
console.log(format);
// Returns the csv, xlsx format.
if (format === ExportFormat.Csv || format === ExportFormat.Xlsx) {
@@ -67,10 +65,9 @@ export class ExportResourceService {
const printableColumns = this.getPrintableColumns(resourceMeta);
return this.exportPdf.pdf(
tenantId,
printableColumns,
transformed,
resourceMeta?.print?.pageTitle
resourceMeta?.print?.pageTitle,
);
}
}
@@ -106,14 +103,14 @@ export class ExportResourceService {
*/
private transformExportedData(
resource: string,
data: Array<Record<string, any>>
data: Array<Record<string, any>>,
): Array<Record<string, any>> {
const resourceMeta = this.getResourceMeta(resource);
return R.when<Array<Record<string, any>>, Array<Record<string, any>>>(
R.always(Boolean(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.
*/
private async getExportableData(resource: string) {
const exportable =
this.exportableResources.registry.getExportable(resource);
return exportable.exportable({});
const exportable = getExportableService(resource);
const contextId = ContextIdFactory.create();
const exportableInstance = await this.moduleRef.resolve(
exportable,
contextId,
{ strict: false },
);
return exportableInstance.exportable({});
}
/**
@@ -137,7 +138,7 @@ export class ExportResourceService {
private getExportableColumns(resourceColumns: any) {
const processColumns = (
columns: { [key: string]: IModelMetaColumn },
parent = ''
parent = '',
) => {
return Object.entries(columns)
.filter(([_, value]) => value.exportable !== false)
@@ -163,9 +164,10 @@ export class ExportResourceService {
private getPrintableColumns(resourceMeta: IModelMeta) {
const processColumns = (
columns: { [key: string]: IModelMetaColumn },
parent = ''
parent = '',
) => {
return Object.entries(columns)
// @ts-expect-error
.filter(([_, value]) => value.printable !== false)
.flatMap(([key, value]) => {
if (value.type === 'collection' && value.collectionOf === 'object') {
@@ -195,7 +197,7 @@ export class ExportResourceService {
private createWorkbook(data: any[], exportableColumns: any[]) {
const workbook = xlsx.utils.book_new();
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));

View File

@@ -6,7 +6,7 @@ export class Exportable {
*/
public async exportable(
query: Record<string, any>,
): Promise<Array<Record<string, any>>> {
): Promise<any> {
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()
@IsNotEmpty()
resource: string;
@IsString()
@IsNotEmpty()
format: string;
}

View File

@@ -1,8 +1,40 @@
import { Module } from '@nestjs/common';
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({
providers: [ImportAls],
imports: [ResourceModule, TenancyModule, AccountsModule],
providers: [
ImportAls,
ImportSampleService,
ImportableResources,
ImportResourceApplication,
ImportDeleteExpiredFiles,
ImportFileUploadService,
ImportFileProcessCommit,
ImportFileProcess,
ImportFilePreview,
ImportFileMeta,
ImportFileMapping,
ImportFileDataValidator,
ImportFileDataTransformer,
ImportFileCommon,
],
exports: [ImportAls],
})
export class ImportModule {}

View File

@@ -12,9 +12,9 @@ import {
import { getUniqueImportableValue, trimObject } from './_utils';
import { ImportableResources } from './ImportableResources';
import { ResourceService } from '../Resource/ResourceService';
import { Import } from '@/system/models';
import { Injectable } from '@nestjs/common';
import { ServiceError } from '../Items/ServiceError';
import { ImportModelShape } from './models/Import';
@Injectable()
export class ImportFileCommon {
@@ -32,7 +32,7 @@ export class ImportFileCommon {
* @returns {Promise<[ImportOperSuccess[], ImportOperError[]]>}
*/
public async import(
importFile: Import,
importFile: ImportModelShape,
parsedData: Record<string, any>[],
trx?: Knex.Transaction,
): Promise<[ImportOperSuccess[], ImportOperError[]]> {
@@ -68,7 +68,6 @@ export class ImportFileCommon {
try {
// Run the importable function and listen to the errors.
const data = await importable.importable(
tenantId,
transformedDTO,
trx,
);
@@ -135,14 +134,13 @@ export class ImportFileCommon {
* @param {Record<string, any>} params
*/
public async validateParams(
tenantId: number,
resourceName: string,
params: Record<string, any>,
) {
const ImportableRegistry = this.importable.registry;
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 {
ImportDateFormats,
@@ -8,15 +8,19 @@ import {
import { ResourceService } from '../Resource/ResourceService';
import { ServiceError } from '../Items/ServiceError';
import { ERRORS } from './_utils';
import { Import } from './models/Import';
import { ImportModel } from './models/Import';
@Injectable()
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.
* @param {number} tenantId
* @param {number} importId
* @param {ImportMappingAttr} maps
*/
@@ -24,7 +28,8 @@ export class ImportFileMapping {
importId: string,
maps: ImportMappingAttr[],
): Promise<ImportFileMapPOJO> {
const importFile = await Import.query()
const importFile = await this.importModel()
.query()
.findOne('filename', importId)
.throwIfNotFound();
@@ -41,7 +46,7 @@ export class ImportFileMapping {
const mappingStringified = JSON.stringify(maps);
await Import.query().findById(importFile.id).patch({
await this.importModel().query().findById(importFile.id).patch({
mapping: mappingStringified,
});
return {
@@ -54,7 +59,6 @@ export class ImportFileMapping {
/**
* Validate the mapping attributes.
* @param {number} tenantId -
* @param {} importFile -
* @param {ImportMappingAttr[]} maps
* @throws {ServiceError(ERRORS.INVALID_MAP_ATTRS)}
@@ -116,7 +120,6 @@ export class ImportFileMapping {
/**
* Validates the date format mapping.
* @param {number} tenantId
* @param {string} resource
* @param {ImportMappingAttr[]} maps
*/

View File

@@ -1,20 +1,29 @@
import { Import } from './models/Import';
import { ImportModel } from './models/Import';
import { ImportFileMetaTransformer } from './ImportFileMetaTransformer';
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable()
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.
* @param {number} tenantId
* @param {number} importId
* @returns {}
*/
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)
.findOne('importId', importId);

View File

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

View File

@@ -1,14 +1,16 @@
import { chain } from 'lodash';
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS, getUnmappedSheetColumns, readImportFile } from './_utils';
import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
import { ImportFilePreviewPOJO } from './interfaces';
import { parseSheetData } from './sheet_utils';
import { Injectable } from '@nestjs/common';
import { ResourceService } from '../Resource/ResourceService';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { ServiceError } from '../Items/ServiceError';
import { ImportModel } from './models/Import';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable()
export class ImportFileProcess {
@@ -17,6 +19,10 @@ export class ImportFileProcess {
private readonly importCommon: ImportFileCommon,
private readonly importParser: ImportFileDataTransformer,
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,
trx?: Knex.Transaction,
): 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)
.where('tenantId', tenantId)
.throwIfNotFound();
@@ -48,28 +58,21 @@ export class ImportFileProcess {
// Runs the importing operation with ability to return errors that will happen.
const [successedImport, failedImport, allData] =
await this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Prases the sheet json data.
const parsedData = await this.importParser.parseSheetData(
tenantId,
importFile,
resourceFields,
sheetData,
trx,
);
const [successedImport, failedImport] =
await this.importCommon.import(
tenantId,
importFile,
parsedData,
trx,
);
return [successedImport, failedImport, parsedData];
},
trx,
);
await this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Prases the sheet json data.
const parsedData = await this.importParser.parseSheetData(
importFile,
resourceFields,
sheetData,
trx,
);
const [successedImport, failedImport] = await this.importCommon.import(
importFile,
parsedData,
trx,
);
return [successedImport, failedImport, parsedData];
}, trx);
const mapping = importFile.mappingParsed;
const errors = chain(failedImport)
.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 { ImportAls } from './ImportALS';
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Inject, Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
@Injectable()
export class ImportFileProcessCommit {
@@ -11,6 +13,9 @@ export class ImportFileProcessCommit {
private readonly importFile: ImportFileProcess,
private readonly importAls: ImportAls,
private readonly eventEmitter: EventEmitter2,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {}
/**
@@ -30,8 +35,9 @@ export class ImportFileProcessCommit {
* @returns {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);
// Commit the successed transaction.

View File

@@ -9,9 +9,10 @@ import { ResourceService } from '../Resource/ResourceService';
import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataValidator } from './ImportFileDataValidator';
import { ImportFileUploadPOJO } from './interfaces';
import { Import } from '@/system/models';
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()
export class ImportFileUploadService {
@@ -19,6 +20,10 @@ export class ImportFileUploadService {
private resourceService: ResourceService,
private importFileCommon: ImportFileCommon,
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>,
): Promise<ImportFileUploadPOJO> {
try {
return await this.importUnhandled(
tenantId,
resourceName,
filename,
params,
);
return await this.importUnhandled(resourceName, filename, params);
} catch (err) {
deleteImportFile(filename);
throw err;
@@ -81,15 +81,18 @@ export class ImportFileUploadService {
await this.importFileCommon.validateParamsSchema(resource, params);
// Validates importable params asyncly.
await this.importFileCommon.validateParams(tenantId, resource, params);
await this.importFileCommon.validateParams(resource, params);
} catch (error) {
throw error;
}
const _params = this.importFileCommon.transformParams(resource, params);
const paramsStringified = JSON.stringify(_params);
const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
// Store the import model with related metadata.
const importFile = await Import.query().insert({
const importFile = await this.importModel.query().insert({
filename,
resource,
tenantId,
@@ -97,10 +100,8 @@ export class ImportFileUploadService {
columns: coumnsStringified,
params: paramsStringified,
});
const resourceColumnsMap = this.resourceService.getResourceFields2(
tenantId,
resource,
);
const resourceColumnsMap =
this.resourceService.getResourceFields2(resource);
const resourceColumns = getResourceColumns(resourceColumnsMap);
return {

View File

@@ -1,34 +1,36 @@
import * as moment from 'moment';
import bluebird from 'bluebird';
import { deleteImportFile } from './_utils';
import { Injectable } from '@nestjs/common';
import { Import } from './models/Import';
import { Inject, Injectable } from '@nestjs/common';
import { ImportModel } from './models/Import';
@Injectable()
export class ImportDeleteExpiredFiles {
constructor(
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {}
/**
* Delete expired files.
*/
async deleteExpiredFiles() {
const yesterday = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm');
const expiredImports = await Import.query().where(
'createdAt',
'<',
yesterday
);
const expiredImports = await this.importModel
.query()
.where('createdAt', '<', yesterday);
await bluebird.map(
expiredImports,
async (expiredImport) => {
await deleteImportFile(expiredImport.filename);
},
{ concurrency: 10 }
{ concurrency: 10 },
);
const expiredImportsIds = expiredImports.map(
(expiredImport) => expiredImport.id
(expiredImport) => expiredImport.id,
);
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',
// importable: UncategorizedTransactionsImportable,
// },
// { resource: 'Customer', importable: CustomersImportable },
// { resource: 'Customer', importable: CustomersImportable },
// { resource: 'Vendor', importable: VendorsImportable },
// { resource: 'Item', importable: ItemsImportable },
// { resource: 'ItemCategory', importable: ItemCategoriesImportable },

View File

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

View File

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

View File

@@ -1,13 +1,14 @@
import { Model, ModelObject } from 'objection';
// import SystemModel from './SystemModel';
import { BaseModel } from '@/models/Model';
export class Import extends BaseModel {
resource: string;
tenantId: number;
export class ImportModel extends BaseModel {
resource!: string;
tenantId!: number;
filename!: string;
mapping!: string;
columns!: string;
params!: string;
importId!: string;
/**
* 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 { Exportable } from '../Export/Exportable';
// import { IAccountsFilter, IAccountsStructureType } from '@/interfaces';
// import ItemCategoriesService from './ItemCategoriesService';
import { Injectable } from '@nestjs/common';
import { Exportable } from '../Export/Exportable';
import { ItemCategoryApplication } from './ItemCategory.application';
import { IItemCategoriesFilter } from './ItemCategory.interfaces';
import { ExportableService } from '../Export/decorators/ExportableModel.decorator';
import { ItemCategory } from './models/ItemCategory.model';
// @Service()
// export class ItemCategoriesExportable extends Exportable {
// @Inject()
// private itemCategoriesApplication: ItemCategoriesService;
@Injectable()
@ExportableService({ name: ItemCategory.name })
export class ItemCategoriesExportable extends Exportable {
constructor(private readonly itemCategoryApp: ItemCategoryApplication) {
super();
}
// /**
// * Retrieves the accounts data to exportable sheet.
// * @param {number} tenantId
// * @returns
// */
// public exportable(tenantId: number, query: IAccountsFilter) {
// const parsedQuery = {
// sortOrder: 'desc',
// columnSortBy: 'created_at',
// inactiveMode: false,
// ...query,
// structure: IAccountsStructureType.Flat,
// } as IAccountsFilter;
/**
* Retrieves the accounts data to exportable sheet.
* @param {number} tenantId
* @returns
*/
public exportable(query: Partial<IItemCategoriesFilter>) {
const parsedQuery = {
...query
} as IItemCategoriesFilter;
// return this.itemCategoriesApplication
// .getItemCategoriesList(tenantId, parsedQuery, {})
// .then((output) => output.itemCategories);
// }
// }
return this.itemCategoryApp
.getItemCategories(parsedQuery)
.then((output) => output.itemCategories);
}
}

View File

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

View File

@@ -9,6 +9,7 @@ import { EditItemCategoryService } from './commands/EditItemCategory.service';
import { GetItemCategoryService } from './queries/GetItemCategory.service';
import { GetItemCategoriesService } from './queries/GetItemCategories.service';
import { CreateItemCategoryDto, EditItemCategoryDto } from './dtos/ItemCategory.dto';
import { Knex } from 'knex';
@Injectable()
export class ItemCategoryApplication {
@@ -33,8 +34,9 @@ export class ItemCategoryApplication {
*/
public createItemCategory(
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 { GetItemCategoriesService } from './queries/GetItemCategories.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { ItemCategoriesExportable } from './ItemCategoriesExportable';
import { ItemCategoriesImportable } from './ItemCategoriesImportable';
@Module({
imports: [TenancyDatabaseModule, DynamicListModule],
@@ -25,6 +27,12 @@ import { DynamicListModule } from '../DynamicListing/DynamicList.module';
CommandItemCategoryValidatorService,
TransformerInjectable,
TenancyContext,
ItemCategoriesExportable,
ItemCategoriesImportable,
],
exports: [
ItemCategoriesExportable,
ItemCategoriesImportable,
],
})
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 { 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 {
name!: string;
description!: string;

View File

@@ -16,6 +16,7 @@ import { ItemsEntriesService } from './ItemsEntries.service';
import { GetItemsService } from './GetItems.service';
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdjustments.module';
import { ItemsExportable } from './ItemsExportable.service';
@Module({
imports: [
@@ -38,7 +39,8 @@ import { InventoryAdjustmentsModule } from '../InventoryAdjutments/InventoryAdju
TenancyContext,
TransformerInjectable,
ItemsEntriesService,
ItemsExportable,
],
exports: [ItemsEntriesService],
exports: [ItemsEntriesService, ItemsExportable],
})
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 { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
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 {
public readonly quantityOnHand: number;
public readonly name: string;
@@ -48,6 +53,13 @@ export class Item extends TenantBaseModel {
}
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 { ManualJournalGLEntries } from './commands/ManualJournalGLEntries';
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({
imports: [BranchesModule, LedgerModule],
imports: [BranchesModule, LedgerModule, DynamicListModule],
controllers: [ManualJournalsController],
providers: [
TenancyContext,
@@ -32,8 +36,12 @@ import { LedgerModule } from '../Ledger/Ledger.module';
AutoIncrementOrdersService,
ManualJournalsApplication,
GetManualJournal,
GetManualJournals,
ManualJournalGLEntries,
ManualJournalWriteGLSubscriber
ManualJournalWriteGLSubscriber,
ManualJournalsExportable,
ManualJournalImportable,
],
exports: [ManualJournalsExportable, ManualJournalImportable],
})
export class ManualJournalsModule {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,12 @@
import { Module } from '@nestjs/common';
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({
imports: [BranchesModule, WarehousesModule, AccountsModule],
providers: [ResourceService],
exports: [ResourceService],
})

View File

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

View File

@@ -1,5 +1,5 @@
import { camelCase, upperFirst } from 'lodash';
import pluralize from 'pluralize';
import * as pluralize from 'pluralize';
export const resourceToModelName = (resourceName: string): string => {
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 { PdfTemplatesModule } from '../PdfTemplate/PdfTemplates.module';
import { SendSaleEstimateMailQueue } from './types/SaleEstimates.types';
import { SaleEstimatesExportable } from './SaleEstimatesExportable';
import { SaleEstimatesImportable } from './SaleEstimatesImportable';
@Module({
imports: [
@@ -74,7 +76,13 @@ import { SendSaleEstimateMailQueue } from './types/SaleEstimates.types';
SaleEstimatesApplication,
SendSaleEstimateMail,
GetSaleEstimatePdf,
SaleEstimatePdfTemplate
SaleEstimatePdfTemplate,
SaleEstimatesExportable,
SaleEstimatesImportable
],
exports: [
SaleEstimatesExportable,
SaleEstimatesImportable
]
})
export class SaleEstimatesModule {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,46 +1,39 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { ISaleInvoiceCreateDTO } from '@/interfaces';
// import { CreateSaleInvoice } from './commands/CreateSaleInvoice.service';
// import { Importable } from '@/services/Import/Importable';
// import { SaleInvoicesSampleData } from './constants';
import { Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { CreateSaleInvoice } from './CreateSaleInvoice.service';
import { Importable } from '@/modules/Import/Importable';
import { CreateSaleInvoiceDto } from '../dtos/SaleInvoice.dto';
import { SaleInvoicesSampleData } from '../constants';
// @Service()
// export class SaleInvoicesImportable extends Importable {
// @Inject()
// private createInvoiceService: CreateSaleInvoice;
@Injectable()
export class SaleInvoicesImportable extends Importable {
constructor(private readonly createInvoiceService: CreateSaleInvoice) {
super();
}
// /**
// * Importing to account service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: ISaleInvoiceCreateDTO,
// trx?: Knex.Transaction
// ) {
// return this.createInvoiceService.createSaleInvoice(
// tenantId,
// createAccountDTO,
// {},
// trx
// );
// }
/**
* Importing to account service.
* @param {CreateSaleInvoiceDto} createAccountDTO
*/
public importable(
createAccountDTO: CreateSaleInvoiceDto,
trx?: Knex.Transaction,
) {
return this.createInvoiceService.createSaleInvoice(createAccountDTO, trx);
}
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return SaleInvoicesSampleData;
// }
// }
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
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 { ISearchRole } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { PaymentIntegrationTransactionLink } from '../SaleInvoice.types';
import { TransactionPaymentServiceEntry } from '@/modules/PaymentServices/models/TransactionPaymentServiceEntry.model';
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()
@ExportableModel()
@InjectModelMeta(SaleInvoiceMeta)
export class SaleInvoice extends TenantBaseModel{
public taxAmountWithheld: number;
public balance: number;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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