feat(sdk-ts): add authentication fetch utils
This commit is contained in:
@@ -14,12 +14,19 @@ import {
|
||||
ApiOperation,
|
||||
ApiBody,
|
||||
ApiParam,
|
||||
ApiExcludeController,
|
||||
ApiResponse,
|
||||
ApiExtraModels,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { PublicRoute } from './guards/jwt.guard';
|
||||
import { AuthenticationApplication } from './AuthApplication.sevice';
|
||||
import { AuthSignupDto } from './dtos/AuthSignup.dto';
|
||||
import { AuthSigninDto } from './dtos/AuthSignin.dto';
|
||||
import { AuthSignupVerifyDto } from './dtos/AuthSignupVerify.dto';
|
||||
import { AuthSendResetPasswordDto } from './dtos/AuthSendResetPassword.dto';
|
||||
import { AuthResetPasswordDto } from './dtos/AuthResetPassword.dto';
|
||||
import { AuthSigninResponseDto } from './dtos/AuthSigninResponse.dto';
|
||||
import { AuthMetaResponseDto } from './dtos/AuthMetaResponse.dto';
|
||||
import { LocalAuthGuard } from './guards/Local.guard';
|
||||
import { AuthSigninService } from './commands/AuthSignin.service';
|
||||
import { TenantModel } from '../System/models/TenantModel';
|
||||
@@ -27,7 +34,7 @@ import { SystemUser } from '../System/models/SystemUser';
|
||||
|
||||
@Controller('/auth')
|
||||
@ApiTags('Auth')
|
||||
@ApiExcludeController()
|
||||
@ApiExtraModels(AuthSigninResponseDto, AuthMetaResponseDto)
|
||||
@PublicRoute()
|
||||
@Throttle({ auth: {} })
|
||||
export class AuthController {
|
||||
@@ -43,10 +50,15 @@ export class AuthController {
|
||||
@UseGuards(LocalAuthGuard)
|
||||
@ApiOperation({ summary: 'Sign in a user' })
|
||||
@ApiBody({ type: AuthSigninDto })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Sign-in successful. Returns access token and tenant/organization IDs.',
|
||||
schema: { $ref: getSchemaPath(AuthSigninResponseDto) },
|
||||
})
|
||||
async signin(
|
||||
@Request() req: Request & { user: SystemUser },
|
||||
@Body() signinDto: AuthSigninDto,
|
||||
) {
|
||||
): Promise<AuthSigninResponseDto> {
|
||||
const { user } = req;
|
||||
const tenant = await this.tenantModel.query().findById(user.tenantId);
|
||||
|
||||
@@ -61,59 +73,47 @@ export class AuthController {
|
||||
@Post('/signup')
|
||||
@ApiOperation({ summary: 'Sign up a new user' })
|
||||
@ApiBody({ type: AuthSignupDto })
|
||||
@ApiResponse({ status: 201, description: 'Sign-up initiated. Check email for confirmation.' })
|
||||
signup(@Request() req: Request, @Body() signupDto: AuthSignupDto) {
|
||||
return this.authApp.signUp(signupDto);
|
||||
}
|
||||
|
||||
@Post('/signup/verify')
|
||||
@ApiOperation({ summary: 'Confirm user signup' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string', example: 'user@example.com' },
|
||||
token: { type: 'string', example: 'confirmation-token' },
|
||||
},
|
||||
},
|
||||
})
|
||||
signupConfirm(@Body('email') email: string, @Body('token') token: string) {
|
||||
return this.authApp.signUpConfirm(email, token);
|
||||
@ApiBody({ type: AuthSignupVerifyDto })
|
||||
@ApiResponse({ status: 200, description: 'Signup confirmed successfully.' })
|
||||
signupConfirm(@Body() body: AuthSignupVerifyDto) {
|
||||
return this.authApp.signUpConfirm(body.email, body.token);
|
||||
}
|
||||
|
||||
@Post('/send_reset_password')
|
||||
@ApiOperation({ summary: 'Send reset password email' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
email: { type: 'string', example: 'user@example.com' },
|
||||
},
|
||||
},
|
||||
})
|
||||
sendResetPassword(@Body('email') email: string) {
|
||||
return this.authApp.sendResetPassword(email);
|
||||
@ApiBody({ type: AuthSendResetPasswordDto })
|
||||
@ApiResponse({ status: 200, description: 'Reset password email sent if the account exists.' })
|
||||
sendResetPassword(@Body() body: AuthSendResetPasswordDto) {
|
||||
return this.authApp.sendResetPassword(body.email);
|
||||
}
|
||||
|
||||
@Post('/reset_password/:token')
|
||||
@ApiOperation({ summary: 'Reset password using token' })
|
||||
@ApiParam({ name: 'token', description: 'Reset password token' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
password: { type: 'string', example: 'new-password' },
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiParam({ name: 'token', description: 'Reset password token from email link' })
|
||||
@ApiBody({ type: AuthResetPasswordDto })
|
||||
@ApiResponse({ status: 200, description: 'Password reset successfully.' })
|
||||
resetPassword(
|
||||
@Param('token') token: string,
|
||||
@Body('password') password: string,
|
||||
@Body() body: AuthResetPasswordDto,
|
||||
) {
|
||||
return this.authApp.resetPassword(token, password);
|
||||
return this.authApp.resetPassword(token, body.password);
|
||||
}
|
||||
|
||||
@Get('/meta')
|
||||
meta() {
|
||||
@ApiOperation({ summary: 'Get auth metadata (e.g. signup disabled)' })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Auth metadata for the login/signup page.',
|
||||
schema: { $ref: getSchemaPath(AuthMetaResponseDto) },
|
||||
})
|
||||
meta(): Promise<AuthMetaResponseDto> {
|
||||
return this.authApp.getAuthMeta();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AuthMetaResponseDto {
|
||||
@ApiProperty({ description: 'Whether signup is disabled' })
|
||||
signupDisabled: boolean;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AuthResetPasswordDto {
|
||||
@ApiProperty({
|
||||
example: 'new-password',
|
||||
description: 'New password',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AuthSendResetPasswordDto {
|
||||
@ApiProperty({
|
||||
example: 'user@example.com',
|
||||
description: 'User email address to send reset link to',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
email: string;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AuthSigninResponseDto {
|
||||
@ApiProperty({ description: 'JWT access token' })
|
||||
accessToken: string;
|
||||
|
||||
@ApiProperty({ description: 'Organization ID' })
|
||||
organizationId: string;
|
||||
|
||||
@ApiProperty({ description: 'Tenant ID' })
|
||||
tenantId: number;
|
||||
|
||||
@ApiProperty({ description: 'User ID' })
|
||||
userId: number;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AuthSignupVerifyDto {
|
||||
@ApiProperty({
|
||||
example: 'user@example.com',
|
||||
description: 'User email address',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'confirmation-token',
|
||||
description: 'Signup confirmation token from email',
|
||||
})
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
token: string;
|
||||
}
|
||||
@@ -2,7 +2,12 @@ import { Body, Controller, Delete, Param, Post, Query } from '@nestjs/common';
|
||||
import { castArray, omit } from 'lodash';
|
||||
import { BankingCategorizeApplication } from './BankingCategorize.application';
|
||||
import { CategorizeBankTransactionRouteDto } from './dtos/CategorizeBankTransaction.dto';
|
||||
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
|
||||
@Controller('banking/categorize')
|
||||
@@ -29,16 +34,27 @@ export class BankingCategorizeController {
|
||||
}
|
||||
|
||||
@Delete('/bulk')
|
||||
@ApiOperation({ summary: 'Uncategorize bank transactions.' })
|
||||
@ApiOperation({ summary: 'Uncategorize bank transactions in bulk.' })
|
||||
@ApiQuery({
|
||||
name: 'uncategorizedTransactionIds',
|
||||
required: true,
|
||||
type: [Number],
|
||||
isArray: true,
|
||||
description: 'Array of uncategorized transaction IDs to uncategorize',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'The bank transactions have been uncategorized successfully.',
|
||||
})
|
||||
public uncategorizeTransactionsBulk(
|
||||
@Query() uncategorizedTransactionIds: number[] | number,
|
||||
@Query('uncategorizedTransactionIds')
|
||||
uncategorizedTransactionIds: number[] | number,
|
||||
) {
|
||||
const ids = castArray(uncategorizedTransactionIds).map((id) =>
|
||||
Number(id),
|
||||
);
|
||||
return this.bankingCategorizeApplication.uncategorizeTransactionsBulk(
|
||||
castArray(uncategorizedTransactionIds),
|
||||
ids,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOperation,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { Body, Controller, Get, Param, Patch, Post, Query } from '@nestjs/common';
|
||||
import { BankingMatchingApplication } from './BankingMatchingApplication';
|
||||
import { GetMatchedTransactionsFilter } from './types';
|
||||
import { MatchBankTransactionDto } from './dtos/MatchBankTransaction.dto';
|
||||
import { GetMatchedTransactionsQueryDto } from './dtos/GetMatchedTransactionsQuery.dto';
|
||||
import { GetMatchedTransactionsResponseDto } from './dtos/GetMatchedTransactionsResponse.dto';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
|
||||
@Controller('banking/matching')
|
||||
@ApiTags('Banking Transactions Matching')
|
||||
@ApiExtraModels(GetMatchedTransactionsResponseDto)
|
||||
@ApiCommonHeaders()
|
||||
export class BankingMatchingController {
|
||||
constructor(
|
||||
@@ -15,13 +24,25 @@ export class BankingMatchingController {
|
||||
|
||||
@Get('matched')
|
||||
@ApiOperation({ summary: 'Retrieves the matched transactions.' })
|
||||
@ApiQuery({
|
||||
name: 'uncategorizedTransactionIds',
|
||||
required: true,
|
||||
type: [Number],
|
||||
isArray: true,
|
||||
description: 'Uncategorized transaction IDs to match',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Matched transactions (perfect and possible matches).',
|
||||
schema: { $ref: getSchemaPath(GetMatchedTransactionsResponseDto) },
|
||||
})
|
||||
async getMatchedTransactions(
|
||||
@Query('uncategorizedTransactionIds') uncategorizedTransactionIds: number[],
|
||||
@Query() filter: GetMatchedTransactionsFilter,
|
||||
@Query() filter: GetMatchedTransactionsQueryDto,
|
||||
) {
|
||||
return this.bankingMatchingApplication.getMatchedTransactions(
|
||||
uncategorizedTransactionIds,
|
||||
filter,
|
||||
uncategorizedTransactionIds ?? [],
|
||||
filter as any,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class GetMatchedTransactionsQueryDto {
|
||||
@ApiPropertyOptional({ description: 'Filter from date', example: '2024-01-01' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
fromDate?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter to date', example: '2024-12-31' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
toDate?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Minimum amount', example: 0 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
minAmount?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Maximum amount', example: 10000 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
@Max(Number.MAX_SAFE_INTEGER)
|
||||
maxAmount?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Transaction type filter',
|
||||
example: 'SaleInvoice',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
transactionType?: string;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class MatchedTransactionItemDto {
|
||||
@ApiProperty({ description: 'Transaction amount', example: 100.5 })
|
||||
amount: number;
|
||||
|
||||
@ApiProperty({ description: 'Formatted amount', example: '$100.50' })
|
||||
amountFormatted: string;
|
||||
|
||||
@ApiProperty({ description: 'Transaction date', example: '2024-01-15' })
|
||||
date: string;
|
||||
|
||||
@ApiProperty({ description: 'Formatted date', example: 'Jan 15, 2024' })
|
||||
dateFormatted: string;
|
||||
|
||||
@ApiProperty({ description: 'Reference number', example: 'REF-001' })
|
||||
referenceNo: string;
|
||||
|
||||
@ApiProperty({ description: 'Transaction number', example: 'TXN-001' })
|
||||
transactionNo: string;
|
||||
|
||||
@ApiProperty({ description: 'Transaction ID', example: 1 })
|
||||
transactionId: number;
|
||||
|
||||
@ApiProperty({ description: 'Transaction type', example: 'SaleInvoice' })
|
||||
transactionType: string;
|
||||
}
|
||||
|
||||
export class GetMatchedTransactionsResponseDto {
|
||||
@ApiProperty({
|
||||
description: 'Perfect matches (amount and date match)',
|
||||
type: [MatchedTransactionItemDto],
|
||||
})
|
||||
perfectMatches: MatchedTransactionItemDto[];
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Possible matches (candidates)',
|
||||
type: [MatchedTransactionItemDto],
|
||||
})
|
||||
possibleMatches: MatchedTransactionItemDto[];
|
||||
|
||||
@ApiProperty({ description: 'Total pending amount', example: 500 })
|
||||
totalPending: number;
|
||||
}
|
||||
@@ -30,7 +30,12 @@ export class MatchTransactionEntryDto {
|
||||
export class MatchBankTransactionDto {
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
uncategorizedTransactions: Array<number>
|
||||
@ApiProperty({
|
||||
description: 'Uncategorized transaction IDs to match',
|
||||
type: [Number],
|
||||
example: [1, 2],
|
||||
})
|
||||
uncategorizedTransactions: Array<number>;
|
||||
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
+18
-14
@@ -5,13 +5,17 @@ import {
|
||||
ApiResponse,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiExtraModels,
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { GetUncategorizedTransactionsQueryDto } from '../dtos/GetUncategorizedTransactionsQuery.dto';
|
||||
import { GetAutofillCategorizeTransactionResponseDto } from '../dtos/GetAutofillCategorizeTransactionResponse.dto';
|
||||
import { BankingTransactionsApplication } from '../BankingTransactionsApplication.service';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
|
||||
@Controller('banking/uncategorized')
|
||||
@ApiTags('Banking Uncategorized Transactions')
|
||||
@ApiExtraModels(GetAutofillCategorizeTransactionResponseDto)
|
||||
@ApiCommonHeaders()
|
||||
export class BankingUncategorizedTransactionsController {
|
||||
constructor(
|
||||
@@ -20,29 +24,29 @@ export class BankingUncategorizedTransactionsController {
|
||||
|
||||
@Get('autofill')
|
||||
@ApiOperation({ summary: 'Get autofill values for categorize transactions' })
|
||||
@ApiQuery({
|
||||
name: 'uncategorizedTransactionIds',
|
||||
required: true,
|
||||
type: [Number],
|
||||
isArray: true,
|
||||
description: 'Uncategorized transaction IDs to get autofill for',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Returns autofill values for categorize transactions',
|
||||
})
|
||||
@ApiParam({
|
||||
name: 'accountId',
|
||||
required: true,
|
||||
type: Number,
|
||||
description: 'Bank account ID',
|
||||
})
|
||||
@ApiQuery({
|
||||
name: 'uncategorizeTransactionsId',
|
||||
required: true,
|
||||
type: Number,
|
||||
description: 'Uncategorize transactions ID',
|
||||
schema: { $ref: getSchemaPath(GetAutofillCategorizeTransactionResponseDto) },
|
||||
})
|
||||
async getAutofillCategorizeTransaction(
|
||||
@Query('uncategorizedTransactionIds')
|
||||
uncategorizedTransactionIds: Array<number> | number,
|
||||
) {
|
||||
console.log(uncategorizedTransactionIds);
|
||||
const ids = Array.isArray(uncategorizedTransactionIds)
|
||||
? uncategorizedTransactionIds
|
||||
: uncategorizedTransactionIds != null
|
||||
? [uncategorizedTransactionIds]
|
||||
: [];
|
||||
return this.bankingTransactionsApplication.getAutofillCategorizeTransaction(
|
||||
uncategorizedTransactionIds,
|
||||
ids,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export class GetAutofillCategorizeTransactionResponseDto {
|
||||
@ApiPropertyOptional({
|
||||
description: 'Assigned credit/debit account ID from recognition',
|
||||
example: 10,
|
||||
})
|
||||
creditAccountId?: number | null;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: 'Bank account ID (debit)',
|
||||
example: 5,
|
||||
})
|
||||
debitAccountId?: number | null;
|
||||
|
||||
@ApiProperty({ description: 'Total amount of uncategorized transactions', example: -150.5 })
|
||||
amount: number;
|
||||
|
||||
@ApiProperty({ description: 'Formatted amount', example: '$150.50' })
|
||||
formattedAmount: string;
|
||||
|
||||
@ApiProperty({ description: 'Transaction date', example: '2024-01-15' })
|
||||
date: string;
|
||||
|
||||
@ApiProperty({ description: 'Formatted date', example: 'Jan 15, 2024' })
|
||||
formattedDate: string;
|
||||
|
||||
@ApiProperty({ description: 'Whether the transaction is recognized by a rule', example: true })
|
||||
isRecognized: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Bank rule ID that recognized the transaction', example: 1 })
|
||||
recognizedByRuleId?: number | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Bank rule name that recognized the transaction', example: 'Salary Rule' })
|
||||
recognizedByRuleName?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Reference number', example: 'REF-001' })
|
||||
referenceNo?: string | null;
|
||||
|
||||
@ApiProperty({ description: 'Transaction type (category)', example: 'other_expense' })
|
||||
transactionType: string;
|
||||
|
||||
@ApiProperty({ description: 'Whether this is a deposit transaction', example: false })
|
||||
isDepositTransaction: boolean;
|
||||
|
||||
@ApiProperty({ description: 'Whether this is a withdrawal transaction', example: true })
|
||||
isWithdrawalTransaction: boolean;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Assigned payee from recognition' })
|
||||
payee?: string | null;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Assigned memo from recognition' })
|
||||
memo?: string | null;
|
||||
}
|
||||
+13
-9
@@ -4,12 +4,10 @@ import {
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Put,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ExcludeBankTransactionsApplication } from './ExcludeBankTransactionsApplication';
|
||||
import { ExcludedBankTransactionsQuery } from './types/BankTransactionsExclude.types';
|
||||
import {
|
||||
ApiExtraModels,
|
||||
ApiOperation,
|
||||
@@ -18,11 +16,13 @@ import {
|
||||
getSchemaPath,
|
||||
} from '@nestjs/swagger';
|
||||
import { GetExcludedBankTransactionResponseDto } from './dtos/GetExcludedBankTransactionResponse.dto';
|
||||
import { ExcludeBankTransactionsBulkDto } from './dtos/ExcludeBankTransactionsBulk.dto';
|
||||
import { GetExcludedBankTransactionsQueryDto } from './dtos/GetExcludedBankTransactionsQuery.dto';
|
||||
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
|
||||
|
||||
@Controller('banking/exclude')
|
||||
@ApiTags('Banking Transactions')
|
||||
@ApiExtraModels(GetExcludedBankTransactionResponseDto)
|
||||
@ApiExtraModels(GetExcludedBankTransactionResponseDto, ExcludeBankTransactionsBulkDto)
|
||||
@ApiCommonHeaders()
|
||||
export class BankingTransactionsExcludeController {
|
||||
constructor(
|
||||
@@ -31,15 +31,19 @@ export class BankingTransactionsExcludeController {
|
||||
|
||||
@Put('bulk')
|
||||
@ApiOperation({ summary: 'Exclude the given bank transactions.' })
|
||||
public excludeBankTransactions(@Body('ids') ids: number[]) {
|
||||
return this.excludeBankTransactionsApplication.excludeBankTransactions(ids);
|
||||
@ApiResponse({ status: 200, description: 'Bank transactions excluded successfully.' })
|
||||
public excludeBankTransactions(@Body() body: ExcludeBankTransactionsBulkDto) {
|
||||
return this.excludeBankTransactionsApplication.excludeBankTransactions(
|
||||
body.ids,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('bulk')
|
||||
@ApiOperation({ summary: 'Unexclude the given bank transactions.' })
|
||||
public unexcludeBankTransactions(@Body('ids') ids: number[]) {
|
||||
@ApiResponse({ status: 200, description: 'Bank transactions unexcluded successfully.' })
|
||||
public unexcludeBankTransactions(@Body() body: ExcludeBankTransactionsBulkDto) {
|
||||
return this.excludeBankTransactionsApplication.unexcludeBankTransactions(
|
||||
ids,
|
||||
body.ids,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,10 +61,10 @@ export class BankingTransactionsExcludeController {
|
||||
},
|
||||
})
|
||||
public getExcludedBankTransactions(
|
||||
@Query() query: ExcludedBankTransactionsQuery,
|
||||
@Query() query: GetExcludedBankTransactionsQueryDto,
|
||||
) {
|
||||
return this.excludeBankTransactionsApplication.getExcludedBankTransactions(
|
||||
query,
|
||||
query as any,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsArray, IsNumber, ArrayMinSize } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class ExcludeBankTransactionsBulkDto {
|
||||
@ApiProperty({
|
||||
description: 'IDs of uncategorized bank transactions to exclude or unexclude',
|
||||
type: [Number],
|
||||
example: [1, 2, 3],
|
||||
})
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@IsNumber({}, { each: true })
|
||||
@Type(() => Number)
|
||||
ids: number[];
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsOptional, IsNumber, IsDateString } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class GetExcludedBankTransactionsQueryDto {
|
||||
@ApiPropertyOptional({ description: 'Page number', example: 1 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
page?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Page size', example: 25 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
pageSize?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Filter by bank account ID', example: 1 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
accountId?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Minimum date (ISO)', example: '2024-01-01' })
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
minDate?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Maximum date (ISO)', example: '2024-12-31' })
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
maxDate?: string;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Minimum amount', example: 0 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
minAmount?: number;
|
||||
|
||||
@ApiPropertyOptional({ description: 'Maximum amount', example: 10000 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsNumber()
|
||||
maxAmount?: number;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ApiExtraModels,
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
getSchemaPath,
|
||||
@@ -155,6 +156,10 @@ export class BillsController {
|
||||
type: Number,
|
||||
description: 'The bill id',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'List of payment transactions for the bill.',
|
||||
})
|
||||
getBillPaymentTransactions(@Param('id') billId: number) {
|
||||
return this.billsApplication.getBillPaymentTransactions(billId);
|
||||
}
|
||||
@@ -195,7 +200,17 @@ export class BillsController {
|
||||
@Get('due')
|
||||
@RequirePermission(BillAction.View, AbilitySubject.Bill)
|
||||
@ApiOperation({ summary: 'Retrieves the due bills.' })
|
||||
getDueBills(@Body('vendorId') vendorId?: number) {
|
||||
@ApiQuery({
|
||||
name: 'vendor_id',
|
||||
required: false,
|
||||
type: Number,
|
||||
description: 'Filter due bills by vendor ID.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'List of due bills (optionally filtered by vendor).',
|
||||
})
|
||||
getDueBills(@Query('vendor_id') vendorId?: number) {
|
||||
return this.billsApplication.getDueBills(vendorId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,10 @@ import {
|
||||
Patch,
|
||||
ParseIntPipe,
|
||||
} from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiParam } from '@nestjs/swagger';
|
||||
import { ApiTags, ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger';
|
||||
import { GetContactsAutoCompleteQuery } from './dtos/GetContactsAutoCompleteQuery.dto';
|
||||
import { GetAutoCompleteContactsService } from './queries/GetAutoCompleteContacts.service';
|
||||
import { GetContactService } from './queries/GetContact.service';
|
||||
import { ActivateContactService } from './commands/ActivateContact.service';
|
||||
import { InactivateContactService } from './commands/InactivateContact.service';
|
||||
|
||||
@@ -17,6 +18,7 @@ import { InactivateContactService } from './commands/InactivateContact.service';
|
||||
export class ContactsController {
|
||||
constructor(
|
||||
private readonly getAutoCompleteService: GetAutoCompleteContactsService,
|
||||
private readonly getContactService: GetContactService,
|
||||
private readonly activateContactService: ActivateContactService,
|
||||
private readonly inactivateContactService: InactivateContactService,
|
||||
) {}
|
||||
@@ -27,6 +29,14 @@ export class ContactsController {
|
||||
return this.getAutoCompleteService.autocompleteContacts(query);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@ApiOperation({ summary: 'Get contact by ID (customer or vendor)' })
|
||||
@ApiParam({ name: 'id', type: Number, description: 'Contact ID' })
|
||||
@ApiResponse({ status: 200, description: 'Contact details (under "customer" key for form/duplicate use)' })
|
||||
getContact(@Param('id', ParseIntPipe) contactId: number) {
|
||||
return this.getContactService.getContact(contactId);
|
||||
}
|
||||
|
||||
@Patch(':id/activate')
|
||||
@ApiOperation({ summary: 'Activate a contact' })
|
||||
@ApiParam({ name: 'id', type: 'number', description: 'Contact ID' })
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GetAutoCompleteContactsService } from './queries/GetAutoCompleteContacts.service';
|
||||
import { GetContactService } from './queries/GetContact.service';
|
||||
import { ContactsController } from './Contacts.controller';
|
||||
import { ActivateContactService } from './commands/ActivateContact.service';
|
||||
import { InactivateContactService } from './commands/InactivateContact.service';
|
||||
@@ -7,6 +8,7 @@ import { InactivateContactService } from './commands/InactivateContact.service';
|
||||
@Module({
|
||||
providers: [
|
||||
GetAutoCompleteContactsService,
|
||||
GetContactService,
|
||||
ActivateContactService,
|
||||
InactivateContactService,
|
||||
],
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Contact } from '../models/Contact';
|
||||
import { ContactTransfromer } from '../Contact.transformer';
|
||||
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
@Injectable()
|
||||
export class GetContactService {
|
||||
constructor(
|
||||
private readonly transformer: TransformerInjectable,
|
||||
@Inject(Contact.name)
|
||||
private readonly contactModel: TenantModelProxy<typeof Contact>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve contact by id (customer or vendor).
|
||||
* Returns transformed contact for duplicate/form use.
|
||||
*/
|
||||
async getContact(contactId: number): Promise<Record<string, unknown>> {
|
||||
const contact = await this.contactModel()
|
||||
.query()
|
||||
.findById(contactId)
|
||||
.throwIfNotFound();
|
||||
|
||||
return this.transformer.transform(contact, new ContactTransfromer());
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"@bigcapital/email-components": "workspace:*",
|
||||
"@bigcapital/pdf-templates": "workspace:*",
|
||||
"@bigcapital/sdk-ts": "workspace:*",
|
||||
"@bigcapital/utils": "workspace:*",
|
||||
"@blueprintjs-formik/core": "^0.3.7",
|
||||
"@blueprintjs-formik/datetime": "^0.4.0",
|
||||
@@ -93,8 +94,8 @@
|
||||
"react-intl-universal": "^2.4.7",
|
||||
"react-loadable": "^5.5.0",
|
||||
"react-plaid-link": "^3.2.1",
|
||||
"react-query": "^3.6.0",
|
||||
"react-query-devtools": "^2.1.1",
|
||||
"@tanstack/react-query": "^5.62.0",
|
||||
"@tanstack/react-query-devtools": "^5.62.0",
|
||||
"react-redux": "^7.2.9",
|
||||
"react-router": "5.3.4",
|
||||
"react-router-breadcrumbs-hoc": "^3.2.10",
|
||||
|
||||
@@ -35933,6 +35933,31 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/contacts/{id}": {
|
||||
"get": {
|
||||
"operationId": "ContactsController_getContact",
|
||||
"summary": "Get contact by ID (customer or vendor)",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Contact ID",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Contact details (under \"customer\" key for form/duplicate use)"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Contacts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/contacts/auto-complete": {
|
||||
"get": {
|
||||
"operationId": "ContactsController_getAutoComplete",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ApiFetcher } from './fetch-utils';
|
||||
import { paths } from './schema';
|
||||
import { OpForPath, OpRequestBody, OpResponseBody } from './utils';
|
||||
import { OpForPath, OpQueryParams, OpRequestBody, OpResponseBody } from './utils';
|
||||
|
||||
export const BANK_RULES_ROUTES = {
|
||||
RULES: '/api/banking/rules',
|
||||
@@ -19,6 +19,7 @@ export const BANK_RULES_ROUTES = {
|
||||
RECOGNIZED_LIST: '/api/banking/recognized',
|
||||
PENDING: '/api/banking/pending',
|
||||
UNCATEGORIZED_AUTOFILL: '/api/banking/uncategorized/autofill',
|
||||
CATEGORIZE_BULK: '/api/banking/categorize/bulk',
|
||||
} as const satisfies Record<string, keyof paths>;
|
||||
|
||||
export type BankRulesListResponse = OpResponseBody<OpForPath<typeof BANK_RULES_ROUTES.RULES, 'get'>>;
|
||||
@@ -27,9 +28,36 @@ export type CreateBankRuleBody = OpRequestBody<OpForPath<typeof BANK_RULES_ROUTE
|
||||
export type EditBankRuleBody = OpRequestBody<OpForPath<typeof BANK_RULES_ROUTES.RULE_BY_ID, 'put'>>;
|
||||
export type CreateBankRuleResponse = OpResponseBody<OpForPath<typeof BANK_RULES_ROUTES.RULES, 'post'>>;
|
||||
|
||||
/** Path params for pause/resume bank account (id = bankAccountId). */
|
||||
/** Path params for pause/resume/disconnect/refresh bank account (id = bankAccountId). */
|
||||
export type PauseBankAccountParams = OpForPath<typeof BANK_RULES_ROUTES.ACCOUNTS_PAUSE, 'post'> extends { parameters: { path: infer P } } ? P : never;
|
||||
export type ResumeBankAccountParams = OpForPath<typeof BANK_RULES_ROUTES.ACCOUNTS_RESUME, 'post'> extends { parameters: { path: infer P } } ? P : never;
|
||||
export type DisconnectBankAccountParams = OpForPath<typeof BANK_RULES_ROUTES.ACCOUNTS_DISCONNECT, 'post'> extends { parameters: { path: infer P } } ? P : never;
|
||||
export type RefreshBankAccountParams = OpForPath<typeof BANK_RULES_ROUTES.ACCOUNTS_REFRESH, 'post'> extends { parameters: { path: infer P } } ? P : never;
|
||||
export type UnmatchMatchedTransactionParams = OpForPath<typeof BANK_RULES_ROUTES.MATCHING_UNMATCH, 'patch'> extends { parameters: { path: infer P } } ? P : never;
|
||||
|
||||
/** Response for GET /api/banking/matching/matched (from server GetMatchedTransactionsResponseDto). */
|
||||
export type MatchedTransactionsResponse = OpResponseBody<OpForPath<typeof BANK_RULES_ROUTES.MATCHING_MATCHED, 'get'>>;
|
||||
|
||||
/** Query params for GET /api/banking/matching/matched. */
|
||||
export type GetMatchedTransactionsQuery = OpQueryParams<OpForPath<typeof BANK_RULES_ROUTES.MATCHING_MATCHED, 'get'>>;
|
||||
|
||||
/** Body for POST /api/banking/matching/match (use referenceType, referenceId - camelCase). */
|
||||
export type MatchTransactionBody = OpRequestBody<OpForPath<typeof BANK_RULES_ROUTES.MATCHING_MATCH, 'post'>>;
|
||||
|
||||
/** Body for PUT/DELETE /api/banking/exclude/bulk (from server ExcludeBankTransactionsBulkDto). */
|
||||
export type ExcludeBankTransactionsBulkBody = OpRequestBody<OpForPath<typeof BANK_RULES_ROUTES.EXCLUDE_BULK, 'put'>>;
|
||||
|
||||
/** Query params for GET /api/banking/exclude. */
|
||||
export type GetExcludedBankTransactionsQuery = OpQueryParams<OpForPath<typeof BANK_RULES_ROUTES.EXCLUDED_LIST, 'get'>>;
|
||||
|
||||
/** Query params for GET /api/banking/pending. */
|
||||
export type GetPendingTransactionsQuery = OpQueryParams<OpForPath<typeof BANK_RULES_ROUTES.PENDING, 'get'>>;
|
||||
|
||||
/** Response for GET /api/banking/uncategorized/autofill (from server GetAutofillCategorizeTransactionResponseDto). */
|
||||
export type AutofillCategorizeTransactionResponse = OpResponseBody<OpForPath<typeof BANK_RULES_ROUTES.UNCATEGORIZED_AUTOFILL, 'get'>>;
|
||||
|
||||
/** Response for GET /api/banking/recognized (single). */
|
||||
export type RecognizedTransactionResponse = OpResponseBody<OpForPath<typeof BANK_RULES_ROUTES.RECOGNIZED, 'get'>>;
|
||||
|
||||
export async function fetchBankRules(fetcher: ApiFetcher): Promise<BankRulesListResponse> {
|
||||
const get = fetcher.path(BANK_RULES_ROUTES.RULES).method('get').create();
|
||||
@@ -115,28 +143,24 @@ export async function resumeBankAccount(
|
||||
|
||||
export async function fetchMatchedTransactions(
|
||||
fetcher: ApiFetcher,
|
||||
uncategorizedTransactionIds: number[]
|
||||
): Promise<unknown> {
|
||||
uncategorizedTransactionIds: number[],
|
||||
query?: GetMatchedTransactionsQuery
|
||||
): Promise<MatchedTransactionsResponse> {
|
||||
const get = fetcher
|
||||
.path(BANK_RULES_ROUTES.MATCHING_MATCHED)
|
||||
.method('get')
|
||||
.create();
|
||||
const ids = uncategorizedTransactionIds.map(String);
|
||||
const { data } = await get({ uncategorizedTransactionIds: ids });
|
||||
const { data } = await get({ uncategorizedTransactionIds: ids, ...query });
|
||||
return data;
|
||||
}
|
||||
|
||||
export type MatchTransactionBody = {
|
||||
uncategorizedTransactions: number[];
|
||||
matchedTransactions: Array<{ reference_type: string; reference_id: number }>;
|
||||
};
|
||||
|
||||
export async function matchTransaction(
|
||||
fetcher: ApiFetcher,
|
||||
body: MatchTransactionBody
|
||||
): Promise<void> {
|
||||
const post = fetcher.path(BANK_RULES_ROUTES.MATCHING_MATCH).method('post').create();
|
||||
await (post as (body: unknown) => Promise<unknown>)(body);
|
||||
await post(body);
|
||||
}
|
||||
|
||||
export async function unmatchMatchedTransaction(
|
||||
@@ -168,24 +192,24 @@ export async function unexcludeBankTransaction(
|
||||
|
||||
export async function excludeBankTransactionsBulk(
|
||||
fetcher: ApiFetcher,
|
||||
ids: Array<number | string>
|
||||
body: ExcludeBankTransactionsBulkBody
|
||||
): Promise<void> {
|
||||
const put = fetcher.path(BANK_RULES_ROUTES.EXCLUDE_BULK).method('put').create();
|
||||
await (put as (body?: { ids?: unknown[] }) => Promise<unknown>)({ ids });
|
||||
await put(body);
|
||||
}
|
||||
|
||||
export async function unexcludeBankTransactionsBulk(
|
||||
fetcher: ApiFetcher,
|
||||
ids: Array<number | string>
|
||||
body: ExcludeBankTransactionsBulkBody
|
||||
): Promise<void> {
|
||||
const del = fetcher.path(BANK_RULES_ROUTES.EXCLUDE_BULK).method('delete').create();
|
||||
await (del as (body?: { ids?: unknown[] }) => Promise<unknown>)({ ids });
|
||||
await del(body);
|
||||
}
|
||||
|
||||
export async function fetchRecognizedTransaction(
|
||||
fetcher: ApiFetcher,
|
||||
recognizedTransactionId: number
|
||||
): Promise<unknown> {
|
||||
): Promise<RecognizedTransactionResponse> {
|
||||
const get = fetcher.path(BANK_RULES_ROUTES.RECOGNIZED).method('get').create();
|
||||
const { data } = await get({ recognizedTransactionId });
|
||||
return data;
|
||||
@@ -196,44 +220,57 @@ export async function fetchRecognizedTransactions(
|
||||
params?: Record<string, unknown>
|
||||
): Promise<unknown> {
|
||||
const get = fetcher.path(BANK_RULES_ROUTES.RECOGNIZED_LIST).method('get').create();
|
||||
const { data } = await (get as (q?: Record<string, unknown>) => Promise<{ data: unknown }>)(
|
||||
params ?? {}
|
||||
);
|
||||
const { data } = await get(params ?? {});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchExcludedBankTransactions(
|
||||
fetcher: ApiFetcher,
|
||||
params?: Record<string, unknown>
|
||||
params?: GetExcludedBankTransactionsQuery
|
||||
): Promise<unknown> {
|
||||
const get = fetcher.path(BANK_RULES_ROUTES.EXCLUDED_LIST).method('get').create();
|
||||
const { data } = await (get as (q?: Record<string, unknown>) => Promise<{ data: unknown }>)(
|
||||
params ?? {}
|
||||
);
|
||||
const { data } = await get(params ?? {});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchPendingTransactions(
|
||||
fetcher: ApiFetcher,
|
||||
params?: Record<string, unknown>
|
||||
params?: GetPendingTransactionsQuery
|
||||
): Promise<unknown> {
|
||||
const get = fetcher.path(BANK_RULES_ROUTES.PENDING).method('get').create();
|
||||
const { data } = await (get as (q?: Record<string, unknown>) => Promise<{ data: unknown }>)(
|
||||
params ?? {}
|
||||
);
|
||||
const { data } = await get(params ?? {});
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchAutofillCategorizeTransaction(
|
||||
fetcher: ApiFetcher,
|
||||
uncategorizedTransactionIds: number[]
|
||||
): Promise<unknown> {
|
||||
): Promise<AutofillCategorizeTransactionResponse> {
|
||||
const get = fetcher
|
||||
.path(BANK_RULES_ROUTES.UNCATEGORIZED_AUTOFILL)
|
||||
.method('get')
|
||||
.create();
|
||||
const { data } = await (get as (q: unknown) => Promise<{ data: unknown }>)({
|
||||
// Server expects uncategorizedTransactionIds (array). Schema types update after openapi regen.
|
||||
const { data } = await get({
|
||||
uncategorizedTransactionIds,
|
||||
});
|
||||
return data;
|
||||
} as never);
|
||||
return data as AutofillCategorizeTransactionResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncategorize bank transactions in bulk (DELETE /api/banking/categorize/bulk with query uncategorizedTransactionIds).
|
||||
*/
|
||||
export async function uncategorizeTransactionsBulk(
|
||||
fetcher: ApiFetcher,
|
||||
uncategorizedTransactionIds: number[],
|
||||
): Promise<void> {
|
||||
const del = fetcher
|
||||
.path(BANK_RULES_ROUTES.CATEGORIZE_BULK)
|
||||
.method('delete')
|
||||
.create();
|
||||
await (del as (params: {
|
||||
query?: { uncategorizedTransactionIds: number[] };
|
||||
}) => Promise<unknown>)({
|
||||
query: { uncategorizedTransactionIds },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@ export type Bill = OpResponseBody<OpForPath<typeof BILLS_ROUTES.BY_ID, 'get'>>;
|
||||
export type CreateBillBody = OpRequestBody<OpForPath<typeof BILLS_ROUTES.LIST, 'post'>>;
|
||||
export type EditBillBody = OpRequestBody<OpForPath<typeof BILLS_ROUTES.BY_ID, 'put'>>;
|
||||
export type GetBillsQuery = OpQueryParams<OpForPath<typeof BILLS_ROUTES.LIST, 'get'>>;
|
||||
export type BulkDeleteBillsBody = OpRequestBody<OpForPath<typeof BILLS_ROUTES.BULK_DELETE, 'post'>>;
|
||||
export type ValidateBulkDeleteBillsResponse = OpResponseBody<
|
||||
OpForPath<typeof BILLS_ROUTES.VALIDATE_BULK_DELETE, 'post'>
|
||||
>;
|
||||
export type GetDueBillsQuery = OpQueryParams<OpForPath<typeof BILLS_ROUTES.DUE, 'get'>>;
|
||||
|
||||
export async function fetchBills(
|
||||
fetcher: ApiFetcher,
|
||||
@@ -56,3 +61,51 @@ export async function deleteBill(fetcher: ApiFetcher, id: number): Promise<void>
|
||||
const del = fetcher.path(BILLS_ROUTES.BY_ID).method('delete').create();
|
||||
await del({ id });
|
||||
}
|
||||
|
||||
export async function openBill(fetcher: ApiFetcher, id: number): Promise<void> {
|
||||
const patch = fetcher.path(BILLS_ROUTES.OPEN).method('patch').create();
|
||||
await patch({ id });
|
||||
}
|
||||
|
||||
export async function bulkDeleteBills(
|
||||
fetcher: ApiFetcher,
|
||||
body: BulkDeleteBillsBody
|
||||
): Promise<void> {
|
||||
const post = fetcher.path(BILLS_ROUTES.BULK_DELETE).method('post').create();
|
||||
await post(body);
|
||||
}
|
||||
|
||||
export async function validateBulkDeleteBills(
|
||||
fetcher: ApiFetcher,
|
||||
body: BulkDeleteBillsBody
|
||||
): Promise<ValidateBulkDeleteBillsResponse> {
|
||||
const post = fetcher
|
||||
.path(BILLS_ROUTES.VALIDATE_BULK_DELETE)
|
||||
.method('post')
|
||||
.create();
|
||||
const { data } = await post(body);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function fetchDueBills(
|
||||
fetcher: ApiFetcher,
|
||||
query?: GetDueBillsQuery
|
||||
): Promise<unknown[]> {
|
||||
const get = fetcher.path(BILLS_ROUTES.DUE).method('get').create();
|
||||
const { data } = await (get as (params?: GetDueBillsQuery) => Promise<{ data: unknown }>)(
|
||||
(query ?? {}) as GetDueBillsQuery
|
||||
);
|
||||
return Array.isArray(data) ? data : [];
|
||||
}
|
||||
|
||||
export async function fetchBillPaymentTransactions(
|
||||
fetcher: ApiFetcher,
|
||||
id: number
|
||||
): Promise<unknown[]> {
|
||||
const get = fetcher
|
||||
.path(BILLS_ROUTES.PAYMENT_TRANSACTIONS)
|
||||
.method('get')
|
||||
.create();
|
||||
const { data } = await get({ id });
|
||||
return (data as unknown[]) ?? [];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,13 @@ export const BANKING_ACCOUNTS_ROUTES = {
|
||||
|
||||
export type BankingAccountsListResponse = OpResponseBody<OpForPath<typeof BANKING_ACCOUNTS_ROUTES.LIST, 'get'>>;
|
||||
|
||||
/** Bank account summary response (schema does not define response body). */
|
||||
export interface BankingAccountSummaryResponse {
|
||||
name: string;
|
||||
totalUncategorizedTransactions: number;
|
||||
totalRecognizedTransactions: number;
|
||||
}
|
||||
|
||||
export async function fetchBankingAccounts(fetcher: ApiFetcher): Promise<BankingAccountsListResponse> {
|
||||
const get = fetcher.path(BANKING_ACCOUNTS_ROUTES.LIST).method('get').create();
|
||||
const { data } = await get({});
|
||||
@@ -18,8 +25,8 @@ export async function fetchBankingAccounts(fetcher: ApiFetcher): Promise<Banking
|
||||
export async function fetchBankingAccountSummary(
|
||||
fetcher: ApiFetcher,
|
||||
bankAccountId: number
|
||||
): Promise<unknown> {
|
||||
): Promise<BankingAccountSummaryResponse> {
|
||||
const get = fetcher.path(BANKING_ACCOUNTS_ROUTES.SUMMARY).method('get').create();
|
||||
const { data } = await get({ bankAccountId });
|
||||
return data;
|
||||
return data as BankingAccountSummaryResponse;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,24 @@ import type { ApiFetcher } from './fetch-utils';
|
||||
import { paths } from './schema';
|
||||
import { OpForPath, OpResponseBody } from './utils';
|
||||
|
||||
export const CONTACTS_ROUTES = {
|
||||
const CONTACTS_ROUTES = {
|
||||
BY_ID: '/api/contacts/{id}',
|
||||
AUTO_COMPLETE: '/api/contacts/auto-complete',
|
||||
ACTIVATE: '/api/contacts/{id}/activate',
|
||||
INACTIVATE: '/api/contacts/{id}/inactivate',
|
||||
} as const satisfies Record<string, keyof paths>;
|
||||
} as const;
|
||||
|
||||
export { CONTACTS_ROUTES };
|
||||
|
||||
export type ContactResponse = OpResponseBody<OpForPath<typeof CONTACTS_ROUTES.BY_ID, 'get'>>;
|
||||
export type ContactsAutoCompleteResponse = OpResponseBody<OpForPath<typeof CONTACTS_ROUTES.AUTO_COMPLETE, 'get'>>;
|
||||
|
||||
export async function fetchContact(fetcher: ApiFetcher, id: number): Promise<ContactResponse> {
|
||||
const get = fetcher.path(CONTACTS_ROUTES.BY_ID as keyof paths).method('get').create();
|
||||
const { data } = await get({ id });
|
||||
return data as ContactResponse;
|
||||
}
|
||||
|
||||
export async function fetchContactsAutoComplete(fetcher: ApiFetcher): Promise<ContactsAutoCompleteResponse> {
|
||||
const get = fetcher.path(CONTACTS_ROUTES.AUTO_COMPLETE).method('get').create();
|
||||
const { data } = await get({});
|
||||
|
||||
@@ -15,6 +15,12 @@ export type Expense = OpResponseBody<OpForPath<typeof EXPENSES_ROUTES.BY_ID, 'ge
|
||||
export type CreateExpenseBody = OpRequestBody<OpForPath<typeof EXPENSES_ROUTES.LIST, 'post'>>;
|
||||
export type EditExpenseBody = OpRequestBody<OpForPath<typeof EXPENSES_ROUTES.BY_ID, 'put'>>;
|
||||
export type GetExpensesQuery = OpQueryParams<OpForPath<typeof EXPENSES_ROUTES.LIST, 'get'>>;
|
||||
export type BulkDeleteExpensesBody = OpRequestBody<
|
||||
OpForPath<typeof EXPENSES_ROUTES.BULK_DELETE, 'post'>
|
||||
>;
|
||||
export type ValidateBulkDeleteExpensesResponse = OpResponseBody<
|
||||
OpForPath<typeof EXPENSES_ROUTES.VALIDATE_BULK_DELETE, 'post'>
|
||||
>;
|
||||
|
||||
export async function fetchExpenses(
|
||||
fetcher: ApiFetcher,
|
||||
@@ -59,3 +65,23 @@ export async function publishExpense(fetcher: ApiFetcher, id: number): Promise<v
|
||||
const post = fetcher.path(EXPENSES_ROUTES.PUBLISH).method('post').create();
|
||||
await post({ id });
|
||||
}
|
||||
|
||||
export async function bulkDeleteExpenses(
|
||||
fetcher: ApiFetcher,
|
||||
body: BulkDeleteExpensesBody
|
||||
): Promise<void> {
|
||||
const post = fetcher.path(EXPENSES_ROUTES.BULK_DELETE).method('post').create();
|
||||
await post(body);
|
||||
}
|
||||
|
||||
export async function validateBulkDeleteExpenses(
|
||||
fetcher: ApiFetcher,
|
||||
body: BulkDeleteExpensesBody
|
||||
): Promise<ValidateBulkDeleteExpensesResponse> {
|
||||
const post = fetcher
|
||||
.path(EXPENSES_ROUTES.VALIDATE_BULK_DELETE)
|
||||
.method('post')
|
||||
.create();
|
||||
const { data } = await post(body);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -4517,6 +4517,23 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/contacts/{id}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get contact by ID (customer or vendor) */
|
||||
get: operations["ContactsController_getContact"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/contacts/auto-complete": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -40037,6 +40054,27 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
ContactsController_getContact: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Contact ID */
|
||||
id: number;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Contact details (under "customer" key for form/duplicate use) */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
ContactsController_getAutoComplete: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
Reference in New Issue
Block a user