feat(server): implement tracking tags for GL entries and reporting
- Add tracking tags infrastructure with 5 database tables - Create TrackingTags module with CRUD API endpoints - Associate tags with invoice/bill line items and manual journal entries - Propagate tags to GL entries (accounts_transactions) via ledger storage - Add tracking tag filtering to all 12 GL-dependent financial reports: - General Ledger, Balance Sheet, Profit & Loss - Trial Balance, Journal Sheet, Cash Flow Statement - Customer/Vendor Balance Summary - Transactions by Customer/Vendor/Reference - Sales Tax Liability Summary - Add filterByTrackingTags query modifier to AccountTransaction model - Add sdk-ts types and fetch functions for tracking tags - Add React hooks (useTrackingTags, useCreateTrackingTag, etc.) - TypeScript typecheck passes across all packages
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
exports.up = function(knex) {
|
||||
return knex.schema
|
||||
.createTable('tracking_tags', table => {
|
||||
table.increments();
|
||||
table.string('name').notNullable();
|
||||
table.string('description').nullable();
|
||||
table.boolean('active').defaultTo(true);
|
||||
table.timestamps();
|
||||
table.index(['active']);
|
||||
table.unique(['name']);
|
||||
})
|
||||
.createTable('tracking_tag_options', table => {
|
||||
table.increments();
|
||||
table.integer('tag_id').unsigned().notNullable();
|
||||
table.string('name').notNullable();
|
||||
table.boolean('active').defaultTo(true);
|
||||
table.timestamps();
|
||||
table.foreign('tag_id').references('tracking_tags.id').onDelete('CASCADE');
|
||||
table.unique(['tag_id', 'name']);
|
||||
})
|
||||
.createTable('item_entry_tracking_tags', table => {
|
||||
table.integer('item_entry_id').unsigned().notNullable();
|
||||
table.integer('tag_id').unsigned().notNullable();
|
||||
table.integer('option_id').unsigned().notNullable();
|
||||
table.timestamps();
|
||||
table.primary(['item_entry_id', 'tag_id']);
|
||||
table.foreign('item_entry_id').references('items_entries.id').onDelete('CASCADE');
|
||||
table.foreign('tag_id').references('tracking_tags.id').onDelete('CASCADE');
|
||||
table.foreign('option_id').references('tracking_tag_options.id').onDelete('CASCADE');
|
||||
table.index(['tag_id']);
|
||||
table.index(['option_id']);
|
||||
})
|
||||
.createTable('manual_journal_entry_tracking_tags', table => {
|
||||
table.integer('manual_journal_entry_id').unsigned().notNullable();
|
||||
table.integer('tag_id').unsigned().notNullable();
|
||||
table.integer('option_id').unsigned().notNullable();
|
||||
table.timestamps();
|
||||
table.primary(['manual_journal_entry_id', 'tag_id']);
|
||||
table.foreign('manual_journal_entry_id').references('manual_journals_entries.id').onDelete('CASCADE');
|
||||
table.foreign('tag_id').references('tracking_tags.id').onDelete('CASCADE');
|
||||
table.foreign('option_id').references('tracking_tag_options.id').onDelete('CASCADE');
|
||||
table.index(['tag_id']);
|
||||
table.index(['option_id']);
|
||||
})
|
||||
.createTable('account_transaction_tracking_tags', table => {
|
||||
table.integer('account_transaction_id').unsigned().notNullable();
|
||||
table.integer('tag_id').unsigned().notNullable();
|
||||
table.integer('option_id').unsigned().notNullable();
|
||||
table.timestamps();
|
||||
table.primary(['account_transaction_id', 'tag_id']);
|
||||
table.foreign('account_transaction_id').references('accounts_transactions.id').onDelete('CASCADE');
|
||||
table.foreign('tag_id').references('tracking_tags.id').onDelete('CASCADE');
|
||||
table.foreign('option_id').references('tracking_tag_options.id').onDelete('CASCADE');
|
||||
table.index(['tag_id']);
|
||||
table.index(['option_id']);
|
||||
table.index(['account_transaction_id']);
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema
|
||||
.dropTableIfExists('account_transaction_tracking_tags')
|
||||
.dropTableIfExists('manual_journal_entry_tracking_tags')
|
||||
.dropTableIfExists('item_entry_tracking_tags')
|
||||
.dropTableIfExists('tracking_tag_options')
|
||||
.dropTableIfExists('tracking_tags');
|
||||
};
|
||||
@@ -230,6 +230,23 @@ export class AccountTransaction extends BaseModel {
|
||||
query.where('reference_id', referenceId);
|
||||
query.where('reference_type', referenceType);
|
||||
},
|
||||
|
||||
filterByTrackingTags(query, trackingTags: Array<{ tagId: number; optionId?: number }>) {
|
||||
if (isEmpty(trackingTags)) {
|
||||
return;
|
||||
}
|
||||
const tagIds = trackingTags.map((t) => t.tagId);
|
||||
query.whereExists(
|
||||
query
|
||||
.knex()
|
||||
.select(1)
|
||||
.from('account_transaction_tracking_tags')
|
||||
.whereRaw(
|
||||
'account_transaction_tracking_tags.account_transaction_id = accounts_transactions.id',
|
||||
)
|
||||
.whereIn('account_transaction_tracking_tags.tag_id', tagIds),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ import { UsersModule } from '../UsersModule/Users.module';
|
||||
import { ContactsModule } from '../Contacts/Contacts.module';
|
||||
import { BankingPlaidModule } from '../BankingPlaid/BankingPlaid.module';
|
||||
import { CustomFieldsModule } from '../CustomFields/CustomFields.module';
|
||||
import { TrackingTagsModule } from '../TrackingTags/TrackingTags.module';
|
||||
import { BankingCategorizeModule } from '../BankingCategorize/BankingCategorize.module';
|
||||
import { ExchangeRatesModule } from '../ExchangeRates/ExchangeRates.module';
|
||||
import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module';
|
||||
@@ -258,6 +259,7 @@ import { AppThrottleModule } from './AppThrottle.module';
|
||||
UsersModule,
|
||||
ContactsModule,
|
||||
CustomFieldsModule,
|
||||
TrackingTagsModule,
|
||||
SocketModule,
|
||||
ExchangeRatesModule,
|
||||
],
|
||||
|
||||
+9
@@ -11,6 +11,7 @@ import {
|
||||
} from 'class-validator';
|
||||
import { FinancialSheetBranchesQueryDto } from '../../dtos/FinancialSheetBranchesQuery.dto';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class BalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@ApiProperty({
|
||||
@@ -173,4 +174,12 @@ export class BalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@Transform(({ value }) => parseBoolean(value, false))
|
||||
@IsOptional()
|
||||
previousYearPercentageChange: boolean;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import {
|
||||
INumberFormatQuery,
|
||||
} from '../../types/Report.types';
|
||||
import { IFinancialTable } from '../../types/Table.types';
|
||||
import { ITrackingTagFilter } from '../../types/TrackingTagFilter.types';
|
||||
|
||||
// Balance sheet schema nodes types.
|
||||
export enum BALANCE_SHEET_SCHEMA_NODE_TYPE {
|
||||
@@ -63,6 +64,8 @@ export interface IBalanceSheetQuery extends IFinancialSheetBranchesQuery {
|
||||
previousYear: boolean;
|
||||
previousYearAmountChange: boolean;
|
||||
previousYearPercentageChange: boolean;
|
||||
|
||||
trackingTags?: ITrackingTagFilter[];
|
||||
}
|
||||
|
||||
// Balance sheet meta.
|
||||
|
||||
+3
@@ -401,5 +401,8 @@ export class BalanceSheetRepository extends R.compose(
|
||||
if (!isEmpty(this.query.branchesIds)) {
|
||||
query.modify('filterByBranches', this.query.branchesIds);
|
||||
}
|
||||
if (!isEmpty(this.query.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.query.trackingTags);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+3
@@ -173,5 +173,8 @@ export class CashFlowRepository {
|
||||
if (!isEmpty(query.branchesIds)) {
|
||||
knexQuery.modify('filterByBranches', query.branchesIds);
|
||||
}
|
||||
if (!isEmpty(query.trackingTags)) {
|
||||
knexQuery.modify('filterByTrackingTags', query.trackingTags);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+9
@@ -11,6 +11,7 @@ import { NumberFormatQueryDto } from '@/modules/BankingTransactions/dtos/NumberF
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { parseBoolean } from '@/utils/parse-boolean';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@ApiProperty({
|
||||
@@ -92,4 +93,12 @@ export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
basis: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+1
@@ -16,6 +16,7 @@ export interface ICashFlowStatementQuery {
|
||||
basis: string;
|
||||
|
||||
branchesIds?: number[];
|
||||
trackingTags?: Array<{ tagId: number; optionId?: number }>;
|
||||
}
|
||||
|
||||
export interface ICashFlowStatementTotal {
|
||||
|
||||
+8
@@ -1,6 +1,7 @@
|
||||
import { IsArray, IsOptional } from 'class-validator';
|
||||
import { ContactBalanceSummaryQueryDto } from '../ContactBalanceSummary/ContactBalanceSummaryQuery.dto';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class CustomerBalanceSummaryQueryDto extends ContactBalanceSummaryQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
@@ -11,4 +12,11 @@ export class CustomerBalanceSummaryQueryDto extends ContactBalanceSummaryQueryDt
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
customersIds: number[];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+4
@@ -56,6 +56,7 @@ export class CustomerBalanceSummaryRepository {
|
||||
*/
|
||||
public async getCustomersTransactions(
|
||||
asDate: any,
|
||||
trackingTags?: Array<{ tagId: number; optionId?: number }>,
|
||||
): Promise<ModelObject<AccountTransaction>[]> {
|
||||
// Retrieve the receivable accounts A/R.
|
||||
const receivableAccounts = await this.getReceivableAccounts();
|
||||
@@ -67,6 +68,9 @@ export class CustomerBalanceSummaryRepository {
|
||||
.onBuild((query) => {
|
||||
query.whereIn('accountId', receivableAccountsIds);
|
||||
query.modify('filterDateRange', null, asDate);
|
||||
if (!isEmpty(trackingTags)) {
|
||||
query.modify('filterByTrackingTags', trackingTags);
|
||||
}
|
||||
query.groupBy('contactId');
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
|
||||
+14
@@ -1,5 +1,6 @@
|
||||
import { IFinancialSheetCommonMeta, INumberFormatQuery } from "../../types/Report.types";
|
||||
import { IFinancialTable } from "../../types/Table.types";
|
||||
import { ITrackingTagFilter } from "../../types/TrackingTagFilter.types";
|
||||
|
||||
export interface IGeneralLedgerSheetQuery {
|
||||
fromDate: Date | string;
|
||||
@@ -10,6 +11,19 @@ export interface IGeneralLedgerSheetQuery {
|
||||
noneTransactions: boolean;
|
||||
accountsIds: number[];
|
||||
branchesIds?: number[];
|
||||
trackingTags?: ITrackingTagFilter[];
|
||||
}
|
||||
|
||||
export interface IGeneralLedgerSheetQuery {
|
||||
fromDate: Date | string;
|
||||
toDate: Date | string;
|
||||
basis: string;
|
||||
numberFormat: IGeneralLedgerNumberFormat;
|
||||
dateFormat?: string;
|
||||
noneTransactions: boolean;
|
||||
accountsIds: number[];
|
||||
branchesIds?: number[];
|
||||
trackingTags?: ITrackingTagFilter[];
|
||||
}
|
||||
|
||||
export interface IGeneralLedgerNumberFormat extends INumberFormatQuery{
|
||||
|
||||
+6
@@ -119,6 +119,9 @@ export class GeneralLedgerRepository {
|
||||
if (!isEmpty(this.filter.branchesIds)) {
|
||||
query.modify('filterByBranches', this.filter.branchesIds);
|
||||
}
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
query.orderBy('date', 'ASC');
|
||||
|
||||
if (this.filter.accountsIds?.length > 0) {
|
||||
@@ -146,6 +149,9 @@ export class GeneralLedgerRepository {
|
||||
if (!isEmpty(this.filter.branchesIds)) {
|
||||
query.modify('filterByBranches', this.filter.branchesIds);
|
||||
}
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
query.withGraphFetched('account');
|
||||
});
|
||||
// Accounts opening transactions.
|
||||
|
||||
+8
@@ -10,6 +10,7 @@ import {
|
||||
import { Type } from 'class-transformer';
|
||||
import { FinancialSheetBranchesQueryDto } from '../../dtos/FinancialSheetBranchesQuery.dto';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
class JournalSheetNumberFormatQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
@@ -93,4 +94,11 @@ export class JournalSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
toRange: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+4
@@ -6,6 +6,7 @@ import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { transformToMap } from '@/utils/transform-to-key';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { ModelObject } from 'objection';
|
||||
|
||||
export class JournalSheetRepository {
|
||||
@@ -112,6 +113,9 @@ export class JournalSheetRepository {
|
||||
if (this.filter.transactionType && this.filter.transactionId) {
|
||||
query.where('reference_id', this.filter.transactionId);
|
||||
}
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
query.withGraphFetched('account');
|
||||
});
|
||||
this.accountTransactions = transactions;
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import {
|
||||
INumberFormatQuery,
|
||||
} from '../../types/Report.types';
|
||||
import { IFinancialTable } from '../../types/Table.types';
|
||||
import { ITrackingTagFilter } from '../../types/TrackingTagFilter.types';
|
||||
|
||||
export enum ProfitLossAggregateNodeId {
|
||||
INCOME = 'INCOME',
|
||||
@@ -86,6 +87,8 @@ export interface IProfitLossSheetQuery extends IFinancialSheetBranchesQuery {
|
||||
previousYear: boolean;
|
||||
previousYearAmountChange: boolean;
|
||||
previousYearPercentageChange: boolean;
|
||||
|
||||
trackingTags?: ITrackingTagFilter[];
|
||||
}
|
||||
|
||||
export interface IProfitLossSheetTotal {
|
||||
|
||||
+8
@@ -14,6 +14,7 @@ import { Transform, Type } from 'class-transformer';
|
||||
import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { parseBoolean } from '@/utils/parse-boolean';
|
||||
import { NumberFormatQueryDto } from '@/modules/BankingTransactions/dtos/NumberFormatQuery.dto';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class ProfitLossSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@IsString()
|
||||
@@ -136,4 +137,11 @@ export class ProfitLossSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
description: 'Whether to show previous year percentage change',
|
||||
})
|
||||
previousYearPercentageChange: boolean;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+3
@@ -363,6 +363,9 @@ export class ProfitLossSheetRepository extends R.compose(FinancialDatePeriods)(
|
||||
if (!isEmpty(this.query.query.branchesIds)) {
|
||||
query.modify('filterByBranches', this.query.query.branchesIds);
|
||||
}
|
||||
if (!isEmpty(this.query.query.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.query.query.trackingTags);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+1
@@ -31,4 +31,5 @@ export interface ITransactionsByContactsFilter {
|
||||
numberFormat: INumberFormatQuery;
|
||||
noneTransactions: boolean;
|
||||
noneZero: boolean;
|
||||
trackingTags?: Array<{ tagId: number; optionId?: number }>;
|
||||
}
|
||||
|
||||
+9
@@ -1,8 +1,17 @@
|
||||
import { IsArray, IsOptional } from 'class-validator';
|
||||
import { TransactionsByContactQueryDto } from '../TransactionsByContact/TransactionsByContactQuery.dto';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class TransactionsByCustomerQueryDto extends TransactionsByContactQueryDto {
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
customersIds: number[];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+10
-1
@@ -236,7 +236,12 @@ export class TransactionsByCustomersRepository extends TransactionsByContactRepo
|
||||
openingDate,
|
||||
receivableAccountsIds,
|
||||
customersIds,
|
||||
);
|
||||
)
|
||||
.onBuild((query) => {
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
});
|
||||
return openingTransactions;
|
||||
}
|
||||
|
||||
@@ -264,6 +269,10 @@ export class TransactionsByCustomersRepository extends TransactionsByContactRepo
|
||||
|
||||
// Filter by accounts.
|
||||
query.whereIn('accountId', receivableAccountsIds);
|
||||
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
});
|
||||
return transactions;
|
||||
}
|
||||
|
||||
+10
-1
@@ -1,5 +1,6 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class TransactionsByReferenceQueryDto {
|
||||
@IsString()
|
||||
@@ -19,4 +20,12 @@ export class TransactionsByReferenceQueryDto {
|
||||
required: true,
|
||||
})
|
||||
referenceId: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+7
@@ -2,6 +2,7 @@ import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ModelObject } from 'objection';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
@Injectable()
|
||||
export class TransactionsByReferenceRepository {
|
||||
@@ -21,12 +22,18 @@ export class TransactionsByReferenceRepository {
|
||||
public async getTransactions(
|
||||
referenceId: number,
|
||||
referenceType: string,
|
||||
trackingTags?: Array<{ tagId: number; optionId?: number }>,
|
||||
): Promise<Array<ModelObject<AccountTransaction>>> {
|
||||
return this.accountTransactionModel()
|
||||
.query()
|
||||
.skipUndefined()
|
||||
.where('reference_id', referenceId)
|
||||
.where('reference_type', referenceType)
|
||||
.onBuild((query) => {
|
||||
if (!isEmpty(trackingTags)) {
|
||||
query.modify('filterByTrackingTags', trackingTags);
|
||||
}
|
||||
})
|
||||
.withGraphFetched('account');
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -1,6 +1,7 @@
|
||||
import { IsArray, IsOptional } from 'class-validator';
|
||||
import { TransactionsByContactQueryDto } from '../TransactionsByContact/TransactionsByContactQuery.dto';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class TransactionsByVendorQueryDto extends TransactionsByContactQueryDto {
|
||||
@IsArray()
|
||||
@@ -10,4 +11,11 @@ export class TransactionsByVendorQueryDto extends TransactionsByContactQueryDto
|
||||
example: [1, 2, 3],
|
||||
})
|
||||
vendorsIds: number[];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+10
-1
@@ -242,7 +242,12 @@ export class TransactionsByVendorRepository extends TransactionsByContactReposit
|
||||
openingDate,
|
||||
payableAccountsIds,
|
||||
customersIds,
|
||||
);
|
||||
)
|
||||
.onBuild((query) => {
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
});
|
||||
return openingTransactions;
|
||||
}
|
||||
|
||||
@@ -270,6 +275,10 @@ export class TransactionsByVendorRepository extends TransactionsByContactReposit
|
||||
|
||||
// Filter by accounts.
|
||||
query.whereIn('accountId', receivableAccountsIds);
|
||||
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
});
|
||||
return transactions;
|
||||
}
|
||||
|
||||
+2
@@ -1,5 +1,6 @@
|
||||
import { IFinancialSheetCommonMeta, INumberFormatQuery } from "../../types/Report.types";
|
||||
import { IFinancialTable } from "../../types/Table.types";
|
||||
import { ITrackingTagFilter } from "../../types/TrackingTagFilter.types";
|
||||
|
||||
export interface ITrialBalanceSheetQuery {
|
||||
fromDate: Date | string;
|
||||
@@ -11,6 +12,7 @@ export interface ITrialBalanceSheetQuery {
|
||||
onlyActive: boolean;
|
||||
accountIds: number[];
|
||||
branchesIds?: number[];
|
||||
trackingTags?: ITrackingTagFilter[];
|
||||
}
|
||||
|
||||
export interface ITrialBalanceTotal {
|
||||
|
||||
+9
@@ -12,6 +12,7 @@ import {
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { parseBoolean } from '@/utils/parse-boolean';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class TrialBalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@ApiProperty({
|
||||
@@ -92,4 +93,12 @@ export class TrialBalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@IsArray()
|
||||
@IsOptional()
|
||||
accountIds: number[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
required: false,
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+4
@@ -111,5 +111,9 @@ export class TrialBalanceSheetRepository {
|
||||
// @ts-ignore
|
||||
query.modify('filterByBranches', this.query.branchesIds);
|
||||
}
|
||||
if (!isEmpty(this.query.trackingTags)) {
|
||||
// @ts-ignore
|
||||
query.modify('filterByTrackingTags', this.query.trackingTags);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ export interface IVendorBalanceSummaryQuery {
|
||||
percentageColumn: boolean;
|
||||
noneTransactions: boolean;
|
||||
noneZero: boolean;
|
||||
trackingTags?: Array<{ tagId: number; optionId?: number }>;
|
||||
}
|
||||
|
||||
export interface IVendorBalanceSummaryAmount {
|
||||
|
||||
+8
@@ -1,6 +1,7 @@
|
||||
import { IsArray, IsOptional } from 'class-validator';
|
||||
import { ContactBalanceSummaryQueryDto } from '../ContactBalanceSummary/ContactBalanceSummaryQuery.dto';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class VendorBalanceSummaryQueryDto extends ContactBalanceSummaryQueryDto {
|
||||
@IsArray()
|
||||
@@ -11,4 +12,11 @@ export class VendorBalanceSummaryQueryDto extends ContactBalanceSummaryQueryDto
|
||||
example: [1, 2, 3],
|
||||
})
|
||||
vendorsIds: number[];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Tracking tags to filter the report',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
})
|
||||
@IsOptional()
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
+3
@@ -138,6 +138,9 @@ export class VendorBalanceSummaryRepository {
|
||||
.onBuild((query) => {
|
||||
query.whereIn('accountId', payableAccountsIds);
|
||||
query.modify('filterDateRange', null, asDate);
|
||||
if (!isEmpty(this.filter.trackingTags)) {
|
||||
query.modify('filterByTrackingTags', this.filter.trackingTags);
|
||||
}
|
||||
query.groupBy('contactId');
|
||||
query.sum('credit as credit');
|
||||
query.sum('debit as debit');
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ITrackingTagFilter {
|
||||
tagId: number;
|
||||
optionId?: number;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from './types/Ledger.types';
|
||||
import { ILedger } from './types/Ledger.types';
|
||||
import { AccountTransaction } from '../Accounts/models/AccountTransaction.model';
|
||||
import { AccountTransactionTrackingTag } from '../TrackingTags/models/AccountTransactionTrackingTag';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
|
||||
// Filter the blank entries.
|
||||
@@ -24,6 +25,11 @@ export class LedgerEntriesStorageService {
|
||||
private readonly accountTransactionModel: TenantModelProxy<
|
||||
typeof AccountTransaction
|
||||
>,
|
||||
|
||||
@Inject(AccountTransactionTrackingTag.name)
|
||||
private readonly accountTransactionTrackingTagModel: TenantModelProxy<
|
||||
typeof AccountTransactionTrackingTag
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -71,7 +77,22 @@ export class LedgerEntriesStorageService {
|
||||
): Promise<void> => {
|
||||
const transaction = transformLedgerEntryToTransaction(entry);
|
||||
|
||||
await this.accountTransactionModel().query(trx).insert(transaction);
|
||||
const insertedTransaction = await this.accountTransactionModel()
|
||||
.query(trx)
|
||||
.insert(transaction);
|
||||
|
||||
// Save tracking tag associations if present.
|
||||
if (entry.trackingTags?.length > 0) {
|
||||
const tagAssociations = entry.trackingTags.map((tag) => ({
|
||||
accountTransactionId: insertedTransaction.id,
|
||||
tagId: tag.tagId,
|
||||
optionId: tag.optionId,
|
||||
}));
|
||||
|
||||
await this.accountTransactionTrackingTagModel()
|
||||
.query(trx)
|
||||
.insert(tagAssociations);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,8 @@ export interface ILedgerEntry {
|
||||
createdAt?: Date | string;
|
||||
|
||||
costable?: boolean;
|
||||
|
||||
trackingTags?: Array<{ tagId: number; optionId: number }>;
|
||||
}
|
||||
|
||||
export interface ISaveLedgerEntryQueuePayload {
|
||||
|
||||
@@ -56,6 +56,18 @@ export class CreateManualJournalService {
|
||||
const authorizedUser = await this.tenancyContext.getSystemUser();
|
||||
|
||||
const entries = R.compose(
|
||||
// Map trackingTags to trackingTagAssociations for upsertGraph.
|
||||
R.map((entry: any) => ({
|
||||
...entry,
|
||||
...(entry.trackingTags
|
||||
? {
|
||||
trackingTagAssociations: entry.trackingTags.map((tag) => ({
|
||||
tagId: tag.tagId,
|
||||
optionId: tag.optionId,
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
})),
|
||||
// Associate the default index to each item entry.
|
||||
assocItemEntriesDefaultIndex,
|
||||
)(manualJournalDTO.entries);
|
||||
|
||||
@@ -68,6 +68,18 @@ export class EditManualJournal {
|
||||
const amount = sumBy(manualJournalDTO.entries, 'credit') || 0;
|
||||
const date = moment(manualJournalDTO.date).format('YYYY-MM-DD');
|
||||
|
||||
const entries = manualJournalDTO.entries.map((entry: any) => ({
|
||||
...entry,
|
||||
...(entry.trackingTags
|
||||
? {
|
||||
trackingTagAssociations: entry.trackingTags.map((tag) => ({
|
||||
tagId: tag.tagId,
|
||||
optionId: tag.optionId,
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
|
||||
return {
|
||||
id: oldManualJournal.id,
|
||||
...omit(manualJournalDTO, ['publish', 'attachments']),
|
||||
@@ -76,6 +88,7 @@ export class EditManualJournal {
|
||||
: {}),
|
||||
amount,
|
||||
date,
|
||||
entries,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -52,6 +52,11 @@ export class ManualJournalGL {
|
||||
public getManualJournalEntry(entry: ManualJournalEntry): ILedgerEntry {
|
||||
const commonEntry = this.manualJournalCommonEntry;
|
||||
|
||||
const trackingTags = entry.trackingTagAssociations?.map((assoc) => ({
|
||||
tagId: assoc.tagId,
|
||||
optionId: assoc.optionId,
|
||||
}));
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: entry.debit,
|
||||
@@ -66,6 +71,7 @@ export class ManualJournalGL {
|
||||
|
||||
branchId: entry.branchId,
|
||||
projectId: entry.projectId,
|
||||
trackingTags,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Min,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class ManualJournalEntryDto {
|
||||
@ApiProperty({ description: 'Entry index' })
|
||||
@@ -63,6 +64,17 @@ export class ManualJournalEntryDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
projectId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TrackingTagAssignmentDto)
|
||||
@ApiProperty({
|
||||
description: 'The tracking tags of the manual journal entry',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
required: false,
|
||||
})
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
class AttachmentDto {
|
||||
|
||||
@@ -18,6 +18,7 @@ export class ManualJournalEntry extends BaseModel {
|
||||
contact?: Contact;
|
||||
account?: Account;
|
||||
branch?: Branch;
|
||||
trackingTagAssociations?: any[];
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
@@ -40,6 +41,7 @@ export class ManualJournalEntry extends BaseModel {
|
||||
const { Account } = require('../../Accounts/models/Account.model');
|
||||
const { Contact } = require('../../Contacts/models/Contact');
|
||||
const { Branch } = require('../../Branches/models/Branch.model');
|
||||
const { ManualJournalEntryTrackingTag } = require('../../TrackingTags/models/ManualJournalEntryTrackingTag');
|
||||
|
||||
return {
|
||||
account: {
|
||||
@@ -66,6 +68,15 @@ export class ManualJournalEntry extends BaseModel {
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
trackingTagAssociations: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ManualJournalEntryTrackingTag,
|
||||
join: {
|
||||
from: 'manual_journals_entries.id',
|
||||
to: 'manual_journal_entry_tracking_tags.manualJournalEntryId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ export enum AbilitySubject {
|
||||
Project = 'Project',
|
||||
TaxRate = 'TaxRate',
|
||||
CustomField = 'CustomField',
|
||||
TrackingTag = 'TrackingTag',
|
||||
}
|
||||
|
||||
export interface IRoleCreatedPayload {
|
||||
|
||||
+13
@@ -91,6 +91,19 @@ export class CommandSaleInvoiceDTOTransformer {
|
||||
// Remove tax code from entries.
|
||||
R.map(R.omit(['taxCode'])),
|
||||
|
||||
// Map trackingTags to trackingTagAssociations for upsertGraph.
|
||||
R.map((entry: any) => ({
|
||||
...entry,
|
||||
...(entry.trackingTags
|
||||
? {
|
||||
trackingTagAssociations: entry.trackingTags.map((tag) => ({
|
||||
tagId: tag.tagId,
|
||||
optionId: tag.optionId,
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
})),
|
||||
|
||||
// Associate the default index for each item entry lin.
|
||||
assocItemEntriesDefaultIndex,
|
||||
)(asyncEntries);
|
||||
|
||||
@@ -109,6 +109,11 @@ export class InvoiceGL {
|
||||
const localAmount =
|
||||
entry.totalExcludingTax * this.saleInvoice.exchangeRate;
|
||||
|
||||
const trackingTags = entry.trackingTagAssociations?.map((assoc) => ({
|
||||
tagId: assoc.tagId,
|
||||
optionId: assoc.optionId,
|
||||
}));
|
||||
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: localAmount,
|
||||
@@ -119,6 +124,7 @@ export class InvoiceGL {
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
trackingTags,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -40,6 +40,11 @@ import { PaymentReceived } from '@/modules/PaymentReceived/models/PaymentReceive
|
||||
import { Model } from 'objection';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
import { TenantUser } from './models/TenantUser.model';
|
||||
import { TrackingTag } from '@/modules/TrackingTags/models/TrackingTag';
|
||||
import { TrackingTagOption } from '@/modules/TrackingTags/models/TrackingTagOption';
|
||||
import { ItemEntryTrackingTag } from '@/modules/TrackingTags/models/ItemEntryTrackingTag';
|
||||
import { ManualJournalEntryTrackingTag } from '@/modules/TrackingTags/models/ManualJournalEntryTrackingTag';
|
||||
import { AccountTransactionTrackingTag } from '@/modules/TrackingTags/models/AccountTransactionTrackingTag';
|
||||
|
||||
const models = [
|
||||
Item,
|
||||
@@ -80,6 +85,11 @@ const models = [
|
||||
PaymentReceived,
|
||||
PaymentReceivedEntry,
|
||||
TenantUser,
|
||||
TrackingTag,
|
||||
TrackingTagOption,
|
||||
ItemEntryTrackingTag,
|
||||
ManualJournalEntryTrackingTag,
|
||||
AccountTransactionTrackingTag,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CreateTrackingTagService } from './commands/CreateTrackingTag.service';
|
||||
import { EditTrackingTagService } from './commands/EditTrackingTag.service';
|
||||
import { DeleteTrackingTagService } from './commands/DeleteTrackingTag.service';
|
||||
import { GetTrackingTagsService } from './queries/GetTrackingTags.service';
|
||||
import { GetTrackingTagService } from './queries/GetTrackingTag.service';
|
||||
import { CreateTrackingTagDto, EditTrackingTagDto } from './dtos/TrackingTag.dto';
|
||||
import { TrackingTag } from './models/TrackingTag';
|
||||
|
||||
@Injectable()
|
||||
export class TrackingTagsApplication {
|
||||
constructor(
|
||||
private createService: CreateTrackingTagService,
|
||||
private editService: EditTrackingTagService,
|
||||
private deleteService: DeleteTrackingTagService,
|
||||
private getTagsService: GetTrackingTagsService,
|
||||
private getTagService: GetTrackingTagService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new tracking tag.
|
||||
* @param {CreateTrackingTagDto} dto
|
||||
* @returns {Promise<TrackingTag>}
|
||||
*/
|
||||
public createTrackingTag = (dto: CreateTrackingTagDto): Promise<TrackingTag> => {
|
||||
return this.createService.createTrackingTag(dto);
|
||||
};
|
||||
|
||||
/**
|
||||
* Edits a tracking tag.
|
||||
* @param {number} tagId
|
||||
* @param {EditTrackingTagDto} dto
|
||||
* @returns {Promise<TrackingTag>}
|
||||
*/
|
||||
public editTrackingTag = (tagId: number, dto: EditTrackingTagDto): Promise<TrackingTag> => {
|
||||
return this.editService.editTrackingTag(tagId, dto);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a tracking tag.
|
||||
* @param {number} tagId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteTrackingTag = (tagId: number): Promise<void> => {
|
||||
return this.deleteService.deleteTrackingTag(tagId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all tracking tags.
|
||||
* @returns {Promise<TrackingTag[]>}
|
||||
*/
|
||||
public getTrackingTags = (): Promise<TrackingTag[]> => {
|
||||
return this.getTagsService.getTrackingTags();
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves a tracking tag by ID.
|
||||
* @param {number} tagId
|
||||
* @returns {Promise<TrackingTag>}
|
||||
*/
|
||||
public getTrackingTag = (tagId: number): Promise<TrackingTag> => {
|
||||
return this.getTagService.getTrackingTag(tagId);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { TrackingTagsApplication } from './TrackingTags.application';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
CreateTrackingTagDto,
|
||||
EditTrackingTagDto,
|
||||
} from './dtos/TrackingTag.dto';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
|
||||
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
|
||||
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
|
||||
import { AbilitySubject } from '@/modules/Roles/Roles.types';
|
||||
|
||||
@Controller('tracking-tags')
|
||||
@ApiTags('Tracking Tags')
|
||||
@ApiCommonHeaders()
|
||||
@UseGuards(AuthorizationGuard, PermissionGuard)
|
||||
export class TrackingTagsController {
|
||||
constructor(private readonly trackingTagsApplication: TrackingTagsApplication) {}
|
||||
|
||||
@Post()
|
||||
@RequirePermission('Create', AbilitySubject.TrackingTag)
|
||||
@ApiOperation({ summary: 'Create a new tracking tag.' })
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'The tracking tag has been successfully created.',
|
||||
})
|
||||
public createTrackingTag(@Body() dto: CreateTrackingTagDto) {
|
||||
return this.trackingTagsApplication.createTrackingTag(dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@RequirePermission('Edit', AbilitySubject.TrackingTag)
|
||||
@ApiOperation({ summary: 'Edit the given tracking tag.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The tracking tag has been successfully updated.',
|
||||
})
|
||||
public editTrackingTag(
|
||||
@Param('id') tagId: number,
|
||||
@Body() dto: EditTrackingTagDto,
|
||||
) {
|
||||
return this.trackingTagsApplication.editTrackingTag(tagId, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('Delete', AbilitySubject.TrackingTag)
|
||||
@ApiOperation({ summary: 'Delete the given tracking tag.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The tracking tag has been successfully deleted.',
|
||||
})
|
||||
public deleteTrackingTag(@Param('id') tagId: number) {
|
||||
return this.trackingTagsApplication.deleteTrackingTag(tagId);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@RequirePermission('View', AbilitySubject.TrackingTag)
|
||||
@ApiOperation({ summary: 'Retrieves all tracking tags.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The tracking tags have been successfully retrieved.',
|
||||
})
|
||||
public getTrackingTags() {
|
||||
return this.trackingTagsApplication.getTrackingTags();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RequirePermission('View', AbilitySubject.TrackingTag)
|
||||
@ApiOperation({ summary: 'Retrieves the tracking tag details.' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The tracking tag details have been successfully retrieved.',
|
||||
})
|
||||
public getTrackingTag(@Param('id') tagId: number) {
|
||||
return this.trackingTagsApplication.getTrackingTag(tagId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TrackingTagsController } from './TrackingTags.controller';
|
||||
import { TrackingTagsApplication } from './TrackingTags.application';
|
||||
import { CreateTrackingTagService } from './commands/CreateTrackingTag.service';
|
||||
import { EditTrackingTagService } from './commands/EditTrackingTag.service';
|
||||
import { DeleteTrackingTagService } from './commands/DeleteTrackingTag.service';
|
||||
import { GetTrackingTagsService } from './queries/GetTrackingTags.service';
|
||||
import { GetTrackingTagService } from './queries/GetTrackingTag.service';
|
||||
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
|
||||
import { TrackingTag } from './models/TrackingTag';
|
||||
import { TrackingTagOption } from './models/TrackingTagOption';
|
||||
import { ItemEntryTrackingTag } from './models/ItemEntryTrackingTag';
|
||||
import { ManualJournalEntryTrackingTag } from './models/ManualJournalEntryTrackingTag';
|
||||
import { AccountTransactionTrackingTag } from './models/AccountTransactionTrackingTag';
|
||||
|
||||
const models = [
|
||||
RegisterTenancyModel(TrackingTag),
|
||||
RegisterTenancyModel(TrackingTagOption),
|
||||
RegisterTenancyModel(ItemEntryTrackingTag),
|
||||
RegisterTenancyModel(ManualJournalEntryTrackingTag),
|
||||
RegisterTenancyModel(AccountTransactionTrackingTag),
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [...models],
|
||||
controllers: [TrackingTagsController],
|
||||
providers: [
|
||||
TrackingTagsApplication,
|
||||
CreateTrackingTagService,
|
||||
EditTrackingTagService,
|
||||
DeleteTrackingTagService,
|
||||
GetTrackingTagsService,
|
||||
GetTrackingTagService,
|
||||
],
|
||||
exports: [
|
||||
GetTrackingTagsService,
|
||||
GetTrackingTagService,
|
||||
...models,
|
||||
],
|
||||
})
|
||||
export class TrackingTagsModule {}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TrackingTag } from '../models/TrackingTag';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { CreateTrackingTagDto } from '../dtos/TrackingTag.dto';
|
||||
|
||||
@Injectable()
|
||||
export class CreateTrackingTagService {
|
||||
constructor(
|
||||
@Inject(TrackingTag.name)
|
||||
private trackingTagModel: TenantModelProxy<typeof TrackingTag>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a new tracking tag with options.
|
||||
* @param {CreateTrackingTagDto} dto
|
||||
* @returns {Promise<TrackingTag>}
|
||||
*/
|
||||
public createTrackingTag = async (dto: CreateTrackingTagDto): Promise<TrackingTag> => {
|
||||
const tag = await this.trackingTagModel()
|
||||
.query()
|
||||
.insertGraphAndFetch({
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
active: dto.active,
|
||||
options: dto.options.map((opt) => ({
|
||||
name: opt.name,
|
||||
active: opt.active,
|
||||
})),
|
||||
});
|
||||
|
||||
return tag;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TrackingTag } from '../models/TrackingTag';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteTrackingTagService {
|
||||
constructor(
|
||||
@Inject(TrackingTag.name)
|
||||
private trackingTagModel: TenantModelProxy<typeof TrackingTag>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deletes a tracking tag.
|
||||
* @param {number} tagId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public deleteTrackingTag = async (tagId: number): Promise<void> => {
|
||||
const tag = await this.trackingTagModel()
|
||||
.query()
|
||||
.findById(tagId)
|
||||
.throwIfNotFound();
|
||||
|
||||
await this.trackingTagModel().query().deleteById(tagId);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TrackingTag } from '../models/TrackingTag';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { EditTrackingTagDto } from '../dtos/TrackingTag.dto';
|
||||
|
||||
@Injectable()
|
||||
export class EditTrackingTagService {
|
||||
constructor(
|
||||
@Inject(TrackingTag.name)
|
||||
private trackingTagModel: TenantModelProxy<typeof TrackingTag>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Edits a tracking tag.
|
||||
* @param {number} tagId
|
||||
* @param {EditTrackingTagDto} dto
|
||||
* @returns {Promise<TrackingTag>}
|
||||
*/
|
||||
public editTrackingTag = async (
|
||||
tagId: number,
|
||||
dto: EditTrackingTagDto,
|
||||
): Promise<TrackingTag> => {
|
||||
const tag = await this.trackingTagModel()
|
||||
.query()
|
||||
.findById(tagId)
|
||||
.throwIfNotFound();
|
||||
|
||||
const updatePayload: any = {};
|
||||
|
||||
if (dto.name !== undefined) updatePayload.name = dto.name;
|
||||
if (dto.description !== undefined) updatePayload.description = dto.description;
|
||||
if (dto.active !== undefined) updatePayload.active = dto.active;
|
||||
|
||||
if (dto.options) {
|
||||
updatePayload.options = dto.options.map((opt) => ({
|
||||
...(opt.id ? { id: opt.id } : {}),
|
||||
name: opt.name,
|
||||
active: opt.active,
|
||||
}));
|
||||
}
|
||||
|
||||
await this.trackingTagModel()
|
||||
.query()
|
||||
.upsertGraphAndFetch({
|
||||
id: tagId,
|
||||
...updatePayload,
|
||||
});
|
||||
|
||||
const updatedTag = await this.trackingTagModel()
|
||||
.query()
|
||||
.findById(tagId)
|
||||
.withGraphFetched('options')
|
||||
.throwIfNotFound();
|
||||
|
||||
return updatedTag;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsInt, IsNotEmpty, IsOptional, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class TrackingTagAssignmentDto {
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ description: 'Tag ID', example: 1 })
|
||||
tagId: number;
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Option ID', example: 5 })
|
||||
optionId?: number;
|
||||
}
|
||||
|
||||
export class AssignTrackingTagsDto {
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TrackingTagAssignmentDto)
|
||||
@ApiProperty({ description: 'Tracking tag assignments', type: [TrackingTagAssignmentDto] })
|
||||
assignments: TrackingTagAssignmentDto[];
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class TrackingTagOptionDto {
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Option ID (for updates)', required: false })
|
||||
id?: number;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ description: 'Option name', example: 'New York' })
|
||||
name: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Whether the option is active', required: false, example: true })
|
||||
active?: boolean = true;
|
||||
}
|
||||
|
||||
export class CreateTrackingTagDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ description: 'Tag name', example: 'Location' })
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Tag description', required: false, example: 'Business location tracking' })
|
||||
description?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Whether the tag is active', required: false, example: true })
|
||||
active?: boolean = true;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TrackingTagOptionDto)
|
||||
@ApiProperty({ description: 'Tag options', type: [TrackingTagOptionDto] })
|
||||
options: TrackingTagOptionDto[];
|
||||
}
|
||||
|
||||
export class EditTrackingTagDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Tag name', required: false, example: 'Location' })
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Tag description', required: false, example: 'Business location tracking' })
|
||||
description?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Whether the tag is active', required: false, example: true })
|
||||
active?: boolean;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TrackingTagOptionDto)
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Tag options', type: [TrackingTagOptionDto], required: false })
|
||||
options?: TrackingTagOptionDto[];
|
||||
}
|
||||
|
||||
export class CreateTrackingTagOptionDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ description: 'Option name', example: 'New York' })
|
||||
name: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Whether the option is active', required: false, example: true })
|
||||
active?: boolean = true;
|
||||
}
|
||||
|
||||
export class EditTrackingTagOptionDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Option name', required: false, example: 'New York' })
|
||||
name?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'Whether the option is active', required: false, example: true })
|
||||
active?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { TrackingTag } from './TrackingTag';
|
||||
import { TrackingTagOption } from './TrackingTagOption';
|
||||
import { AccountTransaction } from '@/modules/Accounts/models/AccountTransaction.model';
|
||||
|
||||
export class AccountTransactionTrackingTag extends BaseModel {
|
||||
public accountTransactionId!: number;
|
||||
public tagId!: number;
|
||||
public optionId!: number;
|
||||
|
||||
public tag!: TrackingTag;
|
||||
public option!: TrackingTagOption;
|
||||
public accountTransaction!: AccountTransaction;
|
||||
|
||||
static get tableName() {
|
||||
return 'account_transaction_tracking_tags';
|
||||
}
|
||||
|
||||
static get idColumn() {
|
||||
return ['accountTransactionId', 'tagId'];
|
||||
}
|
||||
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const { TrackingTag } = require('./TrackingTag');
|
||||
const { TrackingTagOption } = require('./TrackingTagOption');
|
||||
const { AccountTransaction } = require('../../Accounts/models/AccountTransaction.model');
|
||||
|
||||
return {
|
||||
tag: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTag,
|
||||
join: {
|
||||
from: 'account_transaction_tracking_tags.tagId',
|
||||
to: 'tracking_tags.id',
|
||||
},
|
||||
},
|
||||
option: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTagOption,
|
||||
join: {
|
||||
from: 'account_transaction_tracking_tags.optionId',
|
||||
to: 'tracking_tag_options.id',
|
||||
},
|
||||
},
|
||||
accountTransaction: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: AccountTransaction,
|
||||
join: {
|
||||
from: 'account_transaction_tracking_tags.accountTransactionId',
|
||||
to: 'accounts_transactions.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { TrackingTag } from './TrackingTag';
|
||||
import { TrackingTagOption } from './TrackingTagOption';
|
||||
import { ItemEntry } from '@/modules/TransactionItemEntry/models/ItemEntry';
|
||||
|
||||
export class ItemEntryTrackingTag extends BaseModel {
|
||||
public itemEntryId!: number;
|
||||
public tagId!: number;
|
||||
public optionId!: number;
|
||||
|
||||
public tag!: TrackingTag;
|
||||
public option!: TrackingTagOption;
|
||||
public itemEntry!: ItemEntry;
|
||||
|
||||
static get tableName() {
|
||||
return 'item_entry_tracking_tags';
|
||||
}
|
||||
|
||||
static get idColumn() {
|
||||
return ['itemEntryId', 'tagId'];
|
||||
}
|
||||
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const { TrackingTag } = require('./TrackingTag');
|
||||
const { TrackingTagOption } = require('./TrackingTagOption');
|
||||
const { ItemEntry } = require('../../TransactionItemEntry/models/ItemEntry');
|
||||
|
||||
return {
|
||||
tag: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTag,
|
||||
join: {
|
||||
from: 'item_entry_tracking_tags.tagId',
|
||||
to: 'tracking_tags.id',
|
||||
},
|
||||
},
|
||||
option: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTagOption,
|
||||
join: {
|
||||
from: 'item_entry_tracking_tags.optionId',
|
||||
to: 'tracking_tag_options.id',
|
||||
},
|
||||
},
|
||||
itemEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ItemEntry,
|
||||
join: {
|
||||
from: 'item_entry_tracking_tags.itemEntryId',
|
||||
to: 'items_entries.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { TrackingTag } from './TrackingTag';
|
||||
import { TrackingTagOption } from './TrackingTagOption';
|
||||
import { ManualJournalEntry } from '@/modules/ManualJournals/models/ManualJournalEntry';
|
||||
|
||||
export class ManualJournalEntryTrackingTag extends BaseModel {
|
||||
public manualJournalEntryId!: number;
|
||||
public tagId!: number;
|
||||
public optionId!: number;
|
||||
|
||||
public tag!: TrackingTag;
|
||||
public option!: TrackingTagOption;
|
||||
public manualJournalEntry!: ManualJournalEntry;
|
||||
|
||||
static get tableName() {
|
||||
return 'manual_journal_entry_tracking_tags';
|
||||
}
|
||||
|
||||
static get idColumn() {
|
||||
return ['manualJournalEntryId', 'tagId'];
|
||||
}
|
||||
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const { TrackingTag } = require('./TrackingTag');
|
||||
const { TrackingTagOption } = require('./TrackingTagOption');
|
||||
const { ManualJournalEntry } = require('../../ManualJournals/models/ManualJournalEntry');
|
||||
|
||||
return {
|
||||
tag: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTag,
|
||||
join: {
|
||||
from: 'manual_journal_entry_tracking_tags.tagId',
|
||||
to: 'tracking_tags.id',
|
||||
},
|
||||
},
|
||||
option: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTagOption,
|
||||
join: {
|
||||
from: 'manual_journal_entry_tracking_tags.optionId',
|
||||
to: 'tracking_tag_options.id',
|
||||
},
|
||||
},
|
||||
manualJournalEntry: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: ManualJournalEntry,
|
||||
join: {
|
||||
from: 'manual_journal_entry_tracking_tags.manualJournalEntryId',
|
||||
to: 'manual_journals_entries.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Model } from 'objection';
|
||||
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
|
||||
import { TrackingTagOption } from './TrackingTagOption';
|
||||
|
||||
export class TrackingTag extends TenantBaseModel {
|
||||
public name!: string;
|
||||
public description!: string | null;
|
||||
public active!: boolean;
|
||||
|
||||
public options!: TrackingTagOption[];
|
||||
|
||||
static get tableName() {
|
||||
return 'tracking_tags';
|
||||
}
|
||||
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const { TrackingTagOption } = require('./TrackingTagOption');
|
||||
|
||||
return {
|
||||
options: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: TrackingTagOption,
|
||||
join: {
|
||||
from: 'tracking_tags.id',
|
||||
to: 'tracking_tag_options.tagId',
|
||||
},
|
||||
filter(query) {
|
||||
query.where('active', true);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Model } from 'objection';
|
||||
import { BaseModel } from '@/models/Model';
|
||||
import { TrackingTag } from './TrackingTag';
|
||||
|
||||
export class TrackingTagOption extends BaseModel {
|
||||
public tagId!: number;
|
||||
public name!: string;
|
||||
public active!: boolean;
|
||||
|
||||
public tag!: TrackingTag;
|
||||
|
||||
static get tableName() {
|
||||
return 'tracking_tag_options';
|
||||
}
|
||||
|
||||
static get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
const { TrackingTag } = require('./TrackingTag');
|
||||
|
||||
return {
|
||||
tag: {
|
||||
relation: Model.BelongsToOneRelation,
|
||||
modelClass: TrackingTag,
|
||||
join: {
|
||||
from: 'tracking_tag_options.tagId',
|
||||
to: 'tracking_tags.id',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TrackingTag } from '../models/TrackingTag';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetTrackingTagService {
|
||||
constructor(
|
||||
@Inject(TrackingTag.name)
|
||||
private trackingTagModel: TenantModelProxy<typeof TrackingTag>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves a tracking tag by ID with its options.
|
||||
* @param {number} tagId
|
||||
* @returns {Promise<TrackingTag>}
|
||||
*/
|
||||
public getTrackingTag = async (tagId: number): Promise<TrackingTag> => {
|
||||
const tag = await this.trackingTagModel()
|
||||
.query()
|
||||
.findById(tagId)
|
||||
.withGraphFetched('options');
|
||||
|
||||
if (!tag) {
|
||||
throw new NotFoundException('Tracking tag not found.');
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { TrackingTag } from '../models/TrackingTag';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetTrackingTagsService {
|
||||
constructor(
|
||||
@Inject(TrackingTag.name)
|
||||
private trackingTagModel: TenantModelProxy<typeof TrackingTag>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves all tracking tags with their options.
|
||||
* @returns {Promise<TrackingTag[]>}
|
||||
*/
|
||||
public getTrackingTags = async (): Promise<TrackingTag[]> => {
|
||||
return this.trackingTagModel()
|
||||
.query()
|
||||
.withGraphFetched('options');
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { ToNumber } from '@/common/decorators/Validators';
|
||||
import { DiscountType } from '@/common/types/Discount';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsIn,
|
||||
IsInt,
|
||||
@@ -9,7 +10,10 @@ import {
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { TrackingTagAssignmentDto } from '@/modules/TrackingTags/dtos/AssignTrackingTags.dto';
|
||||
|
||||
export class ItemEntryDto {
|
||||
@IsInt()
|
||||
@@ -153,4 +157,15 @@ export class ItemEntryDto {
|
||||
example: 1021,
|
||||
})
|
||||
costAccountId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => TrackingTagAssignmentDto)
|
||||
@ApiProperty({
|
||||
description: 'The tracking tags of the item entry',
|
||||
type: [TrackingTagAssignmentDto],
|
||||
required: false,
|
||||
})
|
||||
trackingTags?: TrackingTagAssignmentDto[];
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ export class ItemEntry extends BaseModel {
|
||||
|
||||
item: Item;
|
||||
allocatedCostEntries: BillLandedCostEntry[];
|
||||
trackingTagAssociations: any[];
|
||||
|
||||
/**
|
||||
* Table name.
|
||||
@@ -190,6 +191,7 @@ export class ItemEntry extends BaseModel {
|
||||
const { SaleReceipt } = require('../../SaleReceipts/models/SaleReceipt');
|
||||
const { SaleEstimate } = require('../../SaleEstimates/models/SaleEstimate');
|
||||
const { TaxRateModel } = require('../../TaxRates/models/TaxRate.model');
|
||||
const { ItemEntryTrackingTag } = require('../../TrackingTags/models/ItemEntryTrackingTag');
|
||||
// const { Expense } = require('../../Expenses/models/Expense.model');
|
||||
// const ProjectTask = require('models/Task');
|
||||
|
||||
@@ -297,6 +299,18 @@ export class ItemEntry extends BaseModel {
|
||||
to: 'tax_rates.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracking tag associations.
|
||||
*/
|
||||
trackingTagAssociations: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: ItemEntryTrackingTag,
|
||||
join: {
|
||||
from: 'items_entries.id',
|
||||
to: 'item_entry_tracking_tags.itemEntryId',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// @ts-nocheck
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { useRequestQuery } from '../useQueryRequest';
|
||||
import QUERY_TYPES from './types';
|
||||
import useApiRequest from '../useRequest';
|
||||
|
||||
// Common invalidate queries.
|
||||
const commonInvalidateQueries = (queryClient) => {
|
||||
queryClient.invalidateQueries(QUERY_TYPES.TRACKING_TAGS);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves tracking tags.
|
||||
*/
|
||||
export function useTrackingTags(props) {
|
||||
return useRequestQuery(
|
||||
[QUERY_TYPES.TRACKING_TAGS],
|
||||
{
|
||||
method: 'get',
|
||||
url: `tracking-tags`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data,
|
||||
defaultData: [],
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves tracking tag.
|
||||
* @param {number} tagId - Tracking tag id.
|
||||
*/
|
||||
export function useTrackingTag(tagId: string, props) {
|
||||
return useRequestQuery(
|
||||
[QUERY_TYPES.TRACKING_TAGS, tagId],
|
||||
{
|
||||
method: 'get',
|
||||
url: `tracking-tags/${tagId}`,
|
||||
},
|
||||
{
|
||||
select: (res) => res.data,
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tracking tag.
|
||||
*/
|
||||
export function useCreateTrackingTag(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((values) => apiRequest.post('tracking-tags', values), {
|
||||
onSuccess: () => {
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given tracking tag.
|
||||
*/
|
||||
export function useEditTrackingTag(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) => apiRequest.put(`tracking-tags/${id}`, values),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
commonInvalidateQueries(queryClient);
|
||||
queryClient.invalidateQueries([QUERY_TYPES.TRACKING_TAGS, id]);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given tracking tag.
|
||||
*/
|
||||
export function useDeleteTrackingTag(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((id) => apiRequest.delete(`tracking-tags/${id}`), {
|
||||
onSuccess: () => {
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
@@ -250,6 +250,11 @@ const CUSTOM_FIELDS = {
|
||||
CUSTOM_FIELD: 'CUSTOM_FIELD',
|
||||
};
|
||||
|
||||
const TRACKING_TAGS = {
|
||||
TRACKING_TAGS: 'TRACKING_TAGS',
|
||||
TRACKING_TAG: 'TRACKING_TAG',
|
||||
};
|
||||
|
||||
export default {
|
||||
...Authentication,
|
||||
...ACCOUNTS,
|
||||
@@ -287,4 +292,5 @@ export default {
|
||||
...EXCHANGE_RATE,
|
||||
...API_KEYS,
|
||||
...CUSTOM_FIELDS,
|
||||
...TRACKING_TAGS,
|
||||
};
|
||||
|
||||
@@ -15341,6 +15341,26 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -17016,6 +17036,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -17190,6 +17230,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -18870,6 +18930,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -19605,6 +19685,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -19741,6 +19841,26 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -19796,6 +19916,26 @@
|
||||
"example": "1",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -21099,6 +21239,26 @@
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -21644,6 +21804,26 @@
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -22077,6 +22257,26 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Tag ID",
|
||||
"name": "tagId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 1,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Option ID",
|
||||
"name": "optionId",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": 5,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "accept",
|
||||
"required": true,
|
||||
@@ -24127,6 +24327,219 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/tracking-tags": {
|
||||
"post": {
|
||||
"operationId": "TrackingTagsController_createTrackingTag",
|
||||
"summary": "Create a new tracking tag.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "Bearer bc_1234567890abcdef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization-id",
|
||||
"in": "header",
|
||||
"description": "Required if Authorization is a JWT token. The organization ID to operate within.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CreateTrackingTagDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "The tracking tag has been successfully created."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Tracking Tags"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "TrackingTagsController_getTrackingTags",
|
||||
"summary": "Retrieves all tracking tags.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "Bearer bc_1234567890abcdef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization-id",
|
||||
"in": "header",
|
||||
"description": "Required if Authorization is a JWT token. The organization ID to operate within.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The tracking tags have been successfully retrieved."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Tracking Tags"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/tracking-tags/{id}": {
|
||||
"put": {
|
||||
"operationId": "TrackingTagsController_editTrackingTag",
|
||||
"summary": "Edit the given tracking tag.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "Bearer bc_1234567890abcdef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization-id",
|
||||
"in": "header",
|
||||
"description": "Required if Authorization is a JWT token. The organization ID to operate within.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/EditTrackingTagDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The tracking tag has been successfully updated."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Tracking Tags"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "TrackingTagsController_deleteTrackingTag",
|
||||
"summary": "Delete the given tracking tag.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "Bearer bc_1234567890abcdef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization-id",
|
||||
"in": "header",
|
||||
"description": "Required if Authorization is a JWT token. The organization ID to operate within.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The tracking tag has been successfully deleted."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Tracking Tags"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "TrackingTagsController_getTrackingTag",
|
||||
"summary": "Retrieves the tracking tag details.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"example": "Bearer bc_1234567890abcdef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "organization-id",
|
||||
"in": "header",
|
||||
"description": "Required if Authorization is a JWT token. The organization ID to operate within.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The tracking tag details have been successfully retrieved."
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Tracking Tags"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/exchange-rates/latest": {
|
||||
"get": {
|
||||
"operationId": "ExchangeRatesController_getLatestExchangeRate",
|
||||
@@ -26480,6 +26893,25 @@
|
||||
"defaultTemplateId"
|
||||
]
|
||||
},
|
||||
"TrackingTagAssignmentDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tagId": {
|
||||
"type": "number",
|
||||
"description": "Tag ID",
|
||||
"example": 1
|
||||
},
|
||||
"optionId": {
|
||||
"type": "number",
|
||||
"description": "Option ID",
|
||||
"example": 5
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"tagId",
|
||||
"optionId"
|
||||
]
|
||||
},
|
||||
"ItemEntryDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -26562,6 +26994,13 @@
|
||||
"type": "number",
|
||||
"description": "The cost account id of the item entry",
|
||||
"example": 1021
|
||||
},
|
||||
"trackingTags": {
|
||||
"description": "The tracking tags of the item entry",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TrackingTagAssignmentDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -31659,6 +32098,13 @@
|
||||
"description": "The cost account id of the item entry",
|
||||
"example": 1021
|
||||
},
|
||||
"trackingTags": {
|
||||
"description": "The tracking tags of the item entry",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TrackingTagAssignmentDto"
|
||||
}
|
||||
},
|
||||
"landedCost": {
|
||||
"type": "boolean",
|
||||
"description": "Flag indicating whether the entry contributes to landed cost",
|
||||
@@ -32131,6 +32577,13 @@
|
||||
"projectId": {
|
||||
"type": "number",
|
||||
"description": "Project ID"
|
||||
},
|
||||
"trackingTags": {
|
||||
"description": "The tracking tags of the manual journal entry",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TrackingTagAssignmentDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -40639,6 +41092,86 @@
|
||||
"password"
|
||||
]
|
||||
},
|
||||
"TrackingTagOptionDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"description": "Option ID (for updates)"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Option name",
|
||||
"example": "New York"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the option is active",
|
||||
"example": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"CreateTrackingTagDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Tag name",
|
||||
"example": "Location"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Tag description",
|
||||
"example": "Business location tracking"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the tag is active",
|
||||
"example": true
|
||||
},
|
||||
"options": {
|
||||
"description": "Tag options",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TrackingTagOptionDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"options"
|
||||
]
|
||||
},
|
||||
"EditTrackingTagDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Tag name",
|
||||
"example": "Location"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Tag description",
|
||||
"example": "Business location tracking"
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the tag is active",
|
||||
"example": true
|
||||
},
|
||||
"options": {
|
||||
"description": "Tag options",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/TrackingTagOptionDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExchangeRateLatestResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -20,6 +20,7 @@ export * from './import';
|
||||
export * from './manual-journals';
|
||||
export * from './roles';
|
||||
export * from './custom-fields';
|
||||
export * from './tracking-tags';
|
||||
export * from './users';
|
||||
export * from './dashboard';
|
||||
export * from './settings';
|
||||
|
||||
+283
-7
@@ -4714,6 +4714,43 @@ export interface paths {
|
||||
patch: operations["ContactsController_inactivateContact"];
|
||||
trace?: never;
|
||||
};
|
||||
"/api/tracking-tags": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Retrieves all tracking tags. */
|
||||
get: operations["TrackingTagsController_getTrackingTags"];
|
||||
put?: never;
|
||||
/** Create a new tracking tag. */
|
||||
post: operations["TrackingTagsController_createTrackingTag"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/tracking-tags/{id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Retrieves the tracking tag details. */
|
||||
get: operations["TrackingTagsController_getTrackingTag"];
|
||||
/** Edit the given tracking tag. */
|
||||
put: operations["TrackingTagsController_editTrackingTag"];
|
||||
post?: never;
|
||||
/** Delete the given tracking tag. */
|
||||
delete: operations["TrackingTagsController_deleteTrackingTag"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/exchange-rates/latest": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -6349,6 +6386,18 @@ export interface components {
|
||||
*/
|
||||
defaultTemplateId: number | null;
|
||||
};
|
||||
TrackingTagAssignmentDto: {
|
||||
/**
|
||||
* @description Tag ID
|
||||
* @example 1
|
||||
*/
|
||||
tagId: number;
|
||||
/**
|
||||
* @description Option ID
|
||||
* @example 5
|
||||
*/
|
||||
optionId: number;
|
||||
};
|
||||
ItemEntryDto: {
|
||||
/**
|
||||
* @description The index of the item entry
|
||||
@@ -6430,6 +6479,8 @@ export interface components {
|
||||
* @example 1021
|
||||
*/
|
||||
costAccountId: number;
|
||||
/** @description The tracking tags of the item entry */
|
||||
trackingTags?: components["schemas"]["TrackingTagAssignmentDto"][];
|
||||
};
|
||||
AttachmentLinkDto: Record<string, never>;
|
||||
PaymentMethodDto: {
|
||||
@@ -10050,6 +10101,8 @@ export interface components {
|
||||
* @example 1021
|
||||
*/
|
||||
costAccountId: number;
|
||||
/** @description The tracking tags of the item entry */
|
||||
trackingTags?: components["schemas"]["TrackingTagAssignmentDto"][];
|
||||
/**
|
||||
* @description Flag indicating whether the entry contributes to landed cost
|
||||
* @example true
|
||||
@@ -10397,6 +10450,8 @@ export interface components {
|
||||
branchId?: number;
|
||||
/** @description Project ID */
|
||||
projectId?: number;
|
||||
/** @description The tracking tags of the manual journal entry */
|
||||
trackingTags?: components["schemas"]["TrackingTagAssignmentDto"][];
|
||||
};
|
||||
CreateManualJournalDto: {
|
||||
/**
|
||||
@@ -14601,6 +14656,58 @@ export interface components {
|
||||
*/
|
||||
password: string;
|
||||
};
|
||||
TrackingTagOptionDto: {
|
||||
/** @description Option ID (for updates) */
|
||||
id?: number;
|
||||
/**
|
||||
* @description Option name
|
||||
* @example New York
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @description Whether the option is active
|
||||
* @example true
|
||||
*/
|
||||
active?: boolean;
|
||||
};
|
||||
CreateTrackingTagDto: {
|
||||
/**
|
||||
* @description Tag name
|
||||
* @example Location
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @description Tag description
|
||||
* @example Business location tracking
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @description Whether the tag is active
|
||||
* @example true
|
||||
*/
|
||||
active?: boolean;
|
||||
/** @description Tag options */
|
||||
options: components["schemas"]["TrackingTagOptionDto"][];
|
||||
};
|
||||
EditTrackingTagDto: {
|
||||
/**
|
||||
* @description Tag name
|
||||
* @example Location
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* @description Tag description
|
||||
* @example Business location tracking
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* @description Whether the tag is active
|
||||
* @example true
|
||||
*/
|
||||
active?: boolean;
|
||||
/** @description Tag options */
|
||||
options?: components["schemas"]["TrackingTagOptionDto"][];
|
||||
};
|
||||
ExchangeRateLatestResponseDto: {
|
||||
/**
|
||||
* @description The base currency code
|
||||
@@ -23238,6 +23345,10 @@ export interface operations {
|
||||
previousYearAmountChange?: boolean;
|
||||
/** @description Whether to show percentage change from previous year */
|
||||
previousYearPercentageChange?: boolean;
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -24645,7 +24756,7 @@ export interface operations {
|
||||
};
|
||||
CustomerBalanceSummaryController_customerBalanceSummary: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description The date as of which the balance summary is calculated */
|
||||
asDate?: string;
|
||||
/** @description Number of decimal places to display */
|
||||
@@ -24666,6 +24777,10 @@ export interface operations {
|
||||
noneZero?: boolean;
|
||||
/** @description Array of customer IDs to filter the summary */
|
||||
customersIds?: number[];
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -24693,7 +24808,7 @@ export interface operations {
|
||||
};
|
||||
VendorBalanceSummaryController_vendorBalanceSummary: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description The date as of which the balance summary is calculated */
|
||||
asDate?: string;
|
||||
/** @description Number of decimal places to display */
|
||||
@@ -24714,6 +24829,10 @@ export interface operations {
|
||||
noneZero?: boolean;
|
||||
/** @description Array of vendor IDs to filter the summary */
|
||||
vendorsIds?: number[];
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -26000,7 +26119,7 @@ export interface operations {
|
||||
};
|
||||
TrialBalanceSheetController_getTrialBalanceSheet: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description Start date for the trial balance sheet */
|
||||
fromDate?: string;
|
||||
/** @description End date for the trial balance sheet */
|
||||
@@ -26025,6 +26144,10 @@ export interface operations {
|
||||
onlyActive?: boolean;
|
||||
/** @description Filter by specific account IDs */
|
||||
accountIds?: number[];
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -26641,7 +26764,7 @@ export interface operations {
|
||||
};
|
||||
TransactionsByVendorController_transactionsByVendor: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description Number of decimal places to display */
|
||||
precision?: number;
|
||||
/** @description Whether to divide the number by 1000 */
|
||||
@@ -26658,6 +26781,10 @@ export interface operations {
|
||||
noneZero?: boolean;
|
||||
/** @description Array of vendor IDs to include */
|
||||
vendorsIds?: string[];
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -26685,7 +26812,7 @@ export interface operations {
|
||||
};
|
||||
TransactionsByCustomerController_transactionsByCustomer: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description Number of decimal places to display */
|
||||
precision?: number;
|
||||
/** @description Whether to divide the number by 1000 */
|
||||
@@ -26700,6 +26827,10 @@ export interface operations {
|
||||
noneTransactions?: boolean;
|
||||
/** @description Whether to exclude zero values */
|
||||
noneZero?: boolean;
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -26732,6 +26863,10 @@ export interface operations {
|
||||
referenceType: string;
|
||||
/** @description The ID of the reference */
|
||||
referenceId: number;
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
@@ -27420,7 +27555,7 @@ export interface operations {
|
||||
};
|
||||
JournalSheetController_journalSheet: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description Whether to hide cents in the number format */
|
||||
noCents?: boolean;
|
||||
/** @description Whether to divide numbers by 1000 */
|
||||
@@ -27433,6 +27568,10 @@ export interface operations {
|
||||
fromRange?: number;
|
||||
/** @description End range for filtering */
|
||||
toRange?: number;
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -27785,6 +27924,10 @@ export interface operations {
|
||||
previousYearAmountChange?: boolean;
|
||||
/** @description Whether to show previous year percentage change */
|
||||
previousYearPercentageChange?: boolean;
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -28054,7 +28197,7 @@ export interface operations {
|
||||
};
|
||||
CashflowController_getCashflow: {
|
||||
parameters: {
|
||||
query?: {
|
||||
query: {
|
||||
/** @description Start date for the cash flow statement period */
|
||||
fromDate?: string;
|
||||
/** @description End date for the cash flow statement period */
|
||||
@@ -28079,6 +28222,10 @@ export interface operations {
|
||||
negativeFormat?: "parentheses" | "mines";
|
||||
/** @description Basis for the cash flow statement */
|
||||
basis?: string;
|
||||
/** @description Tag ID */
|
||||
tagId: number;
|
||||
/** @description Option ID */
|
||||
optionId: number;
|
||||
};
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
@@ -29617,6 +29764,135 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
TrackingTagsController_getTrackingTags: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
Authorization: string;
|
||||
/** @description Required if Authorization is a JWT token. The organization ID to operate within. */
|
||||
"organization-id": string;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description The tracking tags have been successfully retrieved. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
TrackingTagsController_createTrackingTag: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
Authorization: string;
|
||||
/** @description Required if Authorization is a JWT token. The organization ID to operate within. */
|
||||
"organization-id": string;
|
||||
};
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["CreateTrackingTagDto"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description The tracking tag has been successfully created. */
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
TrackingTagsController_getTrackingTag: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
Authorization: string;
|
||||
/** @description Required if Authorization is a JWT token. The organization ID to operate within. */
|
||||
"organization-id": string;
|
||||
};
|
||||
path: {
|
||||
id: number;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description The tracking tag details have been successfully retrieved. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
TrackingTagsController_editTrackingTag: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
Authorization: string;
|
||||
/** @description Required if Authorization is a JWT token. The organization ID to operate within. */
|
||||
"organization-id": string;
|
||||
};
|
||||
path: {
|
||||
id: number;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["EditTrackingTagDto"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description The tracking tag has been successfully updated. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
TrackingTagsController_deleteTrackingTag: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header: {
|
||||
/** @description Value must be 'Bearer <token>' where <token> is an API key prefixed with 'bc_' or a JWT token. */
|
||||
Authorization: string;
|
||||
/** @description Required if Authorization is a JWT token. The organization ID to operate within. */
|
||||
"organization-id": string;
|
||||
};
|
||||
path: {
|
||||
id: number;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description The tracking tag has been successfully deleted. */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
ExchangeRatesController_getLatestExchangeRate: {
|
||||
parameters: {
|
||||
query?: {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { ApiFetcher } from './fetch-utils';
|
||||
import { paths } from './schema';
|
||||
import { OpForPath, OpQueryParams, OpRequestBody, OpResponseBody } from './utils';
|
||||
|
||||
export const TRACKING_TAGS_ROUTES = {
|
||||
LIST: '/api/tracking-tags',
|
||||
BY_ID: '/api/tracking-tags/{id}',
|
||||
} as const satisfies Record<string, keyof paths>;
|
||||
|
||||
export type TrackingTagsList = OpResponseBody<OpForPath<typeof TRACKING_TAGS_ROUTES.LIST, 'get'>>;
|
||||
export type TrackingTag = OpResponseBody<OpForPath<typeof TRACKING_TAGS_ROUTES.BY_ID, 'get'>>;
|
||||
export type CreateTrackingTagBody = OpRequestBody<OpForPath<typeof TRACKING_TAGS_ROUTES.LIST, 'post'>>;
|
||||
export type EditTrackingTagBody = OpRequestBody<OpForPath<typeof TRACKING_TAGS_ROUTES.BY_ID, 'put'>>;
|
||||
|
||||
export async function fetchTrackingTags(
|
||||
fetcher: ApiFetcher,
|
||||
): Promise<TrackingTagsList> {
|
||||
const get = fetcher.path(TRACKING_TAGS_ROUTES.LIST).method('get').create();
|
||||
const { data } = await get({});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchTrackingTag(
|
||||
fetcher: ApiFetcher,
|
||||
id: number,
|
||||
): Promise<TrackingTag> {
|
||||
const get = fetcher.path(TRACKING_TAGS_ROUTES.BY_ID).method('get').create();
|
||||
const { data } = await get({ id });
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function createTrackingTag(
|
||||
fetcher: ApiFetcher,
|
||||
values: CreateTrackingTagBody,
|
||||
): Promise<TrackingTag> {
|
||||
const post = fetcher.path(TRACKING_TAGS_ROUTES.LIST).method('post').create();
|
||||
const { data } = await post(values);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function editTrackingTag(
|
||||
fetcher: ApiFetcher,
|
||||
id: number,
|
||||
values: EditTrackingTagBody,
|
||||
): Promise<TrackingTag> {
|
||||
const put = fetcher.path(TRACKING_TAGS_ROUTES.BY_ID).method('put').create();
|
||||
const { data } = await put({ id, ...values });
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteTrackingTag(
|
||||
fetcher: ApiFetcher,
|
||||
id: number,
|
||||
): Promise<void> {
|
||||
const del = fetcher.path(TRACKING_TAGS_ROUTES.BY_ID).method('delete').create();
|
||||
await del({ id });
|
||||
}
|
||||
Reference in New Issue
Block a user