feat: add response DTOs for credit note modules and SDK types

This commit is contained in:
Ahmed Bouhuolia
2026-03-09 07:12:10 +02:00
parent 640f823af4
commit b59f40d295
19 changed files with 330 additions and 16 deletions

View File

@@ -1,4 +1,10 @@
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import {
ApiExtraModels,
ApiOperation,
ApiResponse,
ApiTags,
getSchemaPath,
} from '@nestjs/swagger';
import {
Body,
Controller,
@@ -18,9 +24,11 @@ import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types';
import { RefundCreditNoteResponseDto } from './dto/RefundCreditNoteResponse.dto';
@Controller('credit-notes')
@ApiTags('Credit Note Refunds')
@ApiExtraModels(RefundCreditNoteResponseDto)
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CreditNoteRefundsController {
@@ -31,12 +39,38 @@ export class CreditNoteRefundsController {
@Get(':creditNoteId/refunds')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Retrieve the credit note graph.' })
@ApiResponse({
status: 200,
description: 'Credit note refunds retrieved successfully.',
schema: {
type: 'array',
items: { $ref: getSchemaPath(RefundCreditNoteResponseDto) },
},
})
getCreditNoteRefunds(@Param('creditNoteId') creditNoteId: number) {
return this.creditNotesRefundsApplication.getCreditNoteRefunds(
creditNoteId,
);
}
@Get('refunds/:refundCreditId')
@RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote)
@ApiOperation({ summary: 'Retrieve a refund transaction for the given credit note.' })
@ApiResponse({
status: 200,
description: 'Refund credit note transaction retrieved successfully.',
schema: {
$ref: getSchemaPath(RefundCreditNoteResponseDto),
},
})
getRefundCreditNoteTransaction(
@Param('refundCreditId') refundCreditId: number,
) {
return this.creditNotesRefundsApplication.getRefundCreditNoteTransaction(
refundCreditId,
);
}
/**
* Create a refund credit note.
* @param {number} creditNoteId - The credit note ID.

View File

@@ -7,6 +7,7 @@ import { CreditNotesRefundsApplication } from './CreditNotesRefundsApplication.s
import { CreditNoteRefundsController } from './CreditNoteRefunds.controller';
import { CreditNotesModule } from '../CreditNotes/CreditNotes.module';
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
import { GetRefundCreditNoteTransaction } from './queries/GetRefundCreditNoteTransaction.service';
import { RefundCreditNoteGLEntries } from './commands/RefundCreditNoteGLEntries';
import { RefundCreditNoteGLEntriesSubscriber } from '../CreditNotes/subscribers/RefundCreditNoteGLEntriesSubscriber';
import { LedgerModule } from '../Ledger/Ledger.module';
@@ -21,6 +22,7 @@ import { AccountsModule } from '../Accounts/Accounts.module';
RefundSyncCreditNoteBalanceService,
CreditNotesRefundsApplication,
GetCreditNoteRefundsService,
GetRefundCreditNoteTransaction,
RefundCreditNoteGLEntries,
RefundCreditNoteGLEntriesSubscriber,
],

View File

@@ -6,6 +6,7 @@ import { RefundCreditNoteService } from './commands/RefundCreditNote.service';
import { RefundSyncCreditNoteBalanceService } from './commands/RefundSyncCreditNoteBalance';
import { CreditNoteRefundDto } from './dto/CreditNoteRefund.dto';
import { GetCreditNoteRefundsService } from './queries/GetCreditNoteRefunds.service';
import { GetRefundCreditNoteTransaction } from './queries/GetRefundCreditNoteTransaction.service';
@Injectable()
export class CreditNotesRefundsApplication {
@@ -13,6 +14,7 @@ export class CreditNotesRefundsApplication {
private readonly createRefundCreditNoteService: CreateRefundCreditNoteService,
private readonly deleteRefundCreditNoteService: DeleteRefundCreditNoteService,
private readonly getCreditNoteRefundsService: GetCreditNoteRefundsService,
private readonly getRefundCreditNoteTransactionService: GetRefundCreditNoteTransaction,
private readonly refundCreditNoteService: RefundCreditNoteService,
private readonly refundSyncCreditNoteBalanceService: RefundSyncCreditNoteBalanceService,
) {}
@@ -26,6 +28,12 @@ export class CreditNotesRefundsApplication {
return this.getCreditNoteRefundsService.getCreditNoteRefunds(creditNoteId);
}
public getRefundCreditNoteTransaction(refundCreditId: number) {
return this.getRefundCreditNoteTransactionService.getRefundCreditTransaction(
refundCreditId,
);
}
/**
* Create a refund credit note.
* @param {number} creditNoteId - The credit note ID.

View File

@@ -0,0 +1,46 @@
import { ApiProperty } from '@nestjs/swagger';
class RefundCreditNoteSummaryDto {
@ApiProperty({ example: 1 })
id: number;
@ApiProperty({ example: 'CN-0001' })
creditNoteNumber: string;
}
class RefundCreditAccountDto {
@ApiProperty({ example: 10 })
id: number;
@ApiProperty({ example: 'Cash on Hand' })
name: string;
}
export class RefundCreditNoteResponseDto {
@ApiProperty({ example: 100 })
id: number;
@ApiProperty({ example: '2024-01-15' })
date: string;
@ApiProperty({ example: '2024-01-15' })
formattedDate: string;
@ApiProperty({ example: 250 })
amount: number;
@ApiProperty({ example: '$250.00' })
formttedAmount: string;
@ApiProperty({ example: 'REF-001', required: false, nullable: true })
referenceNo?: string | null;
@ApiProperty({ example: 'Refund issued to customer', required: false, nullable: true })
description?: string | null;
@ApiProperty({ type: RefundCreditAccountDto })
fromAccount: RefundCreditAccountDto;
@ApiProperty({ type: RefundCreditNoteSummaryDto })
creditNote: RefundCreditNoteSummaryDto;
}

View File

@@ -7,7 +7,13 @@ import {
Post,
UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import {
ApiExtraModels,
ApiOperation,
ApiResponse,
ApiTags,
getSchemaPath,
} from '@nestjs/swagger';
import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
@@ -19,9 +25,12 @@ import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteA
import { CreditNoteApplyToInvoices } from './commands/CreditNoteApplyToInvoices.service';
import { DeleteCreditNoteApplyToInvoices } from './commands/DeleteCreditNoteApplyToInvoices.service';
import { ApplyCreditNoteToInvoicesDto } from './dtos/ApplyCreditNoteToInvoices.dto';
import { AppliedCreditNoteInvoiceResponseDto } from './dtos/AppliedCreditNoteInvoiceResponse.dto';
import { CreditNoteInvoiceToApplyResponseDto } from './dtos/CreditNoteInvoiceToApplyResponse.dto';
@Controller('credit-notes')
@ApiTags('Credit Notes Apply Invoice')
@ApiExtraModels(AppliedCreditNoteInvoiceResponseDto, CreditNoteInvoiceToApplyResponseDto)
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class CreditNotesApplyInvoiceController {
@@ -38,6 +47,10 @@ export class CreditNotesApplyInvoiceController {
@ApiResponse({
status: 200,
description: 'Credit note successfully applied to invoices',
schema: {
type: 'array',
items: { $ref: getSchemaPath(AppliedCreditNoteInvoiceResponseDto) },
},
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })
@@ -53,6 +66,10 @@ export class CreditNotesApplyInvoiceController {
@ApiResponse({
status: 200,
description: 'Credit note associated invoices to apply',
schema: {
type: 'array',
items: { $ref: getSchemaPath(CreditNoteInvoiceToApplyResponseDto) },
},
})
@ApiResponse({ status: 404, description: 'Credit note not found' })
@ApiResponse({ status: 400, description: 'Invalid input data' })

View File

@@ -0,0 +1,27 @@
import { ApiProperty } from '@nestjs/swagger';
export class AppliedCreditNoteInvoiceResponseDto {
@ApiProperty({ example: 1 })
id: number;
@ApiProperty({ example: 200 })
amount: number;
@ApiProperty({ example: '$200.00' })
formttedAmount: string;
@ApiProperty({ example: 'CN-0001' })
creditNoteNumber: string;
@ApiProperty({ example: '2024-01-10' })
creditNoteDate: string;
@ApiProperty({ example: '2024-01-10' })
formattedCreditNoteDate: string;
@ApiProperty({ example: 'INV-0001' })
invoiceNumber: string;
@ApiProperty({ example: 'REF-001', required: false, nullable: true })
invoiceReferenceNo?: string | null;
}

View File

@@ -0,0 +1,45 @@
import { ApiProperty } from '@nestjs/swagger';
export class CreditNoteInvoiceToApplyResponseDto {
@ApiProperty({ example: 1 })
id: number;
@ApiProperty({ example: 'INV-0001' })
invoiceNo: string;
@ApiProperty({ example: 'REF-001', required: false, nullable: true })
referenceNo?: string | null;
@ApiProperty({ example: '2024-01-10' })
invoiceDate: string;
@ApiProperty({ example: '2024-01-20' })
dueDate: string;
@ApiProperty({ example: 'USD', required: false, nullable: true })
currencyCode?: string | null;
@ApiProperty({ example: 500 })
balance: number;
@ApiProperty({ example: 500 })
dueAmount: number;
@ApiProperty({ example: 0 })
paymentAmount: number;
@ApiProperty({ example: '2024-01-10' })
formattedInvoiceDate: string;
@ApiProperty({ example: '2024-01-20' })
formattedDueDate: string;
@ApiProperty({ example: '$500.00' })
formatted_amount: string;
@ApiProperty({ example: '$500.00' })
formattedDueAmount: string;
@ApiProperty({ example: '$0.00' })
formattedPaymentAmount: string;
}

View File

@@ -20,6 +20,8 @@ import { InventoryAdjustmentsApplicationService } from './InventoryAdjustmentsAp
import { IInventoryAdjustmentsFilter } from './types/InventoryAdjustments.types';
import { InventoryAdjustment } from './models/InventoryAdjustment';
import { CreateQuickInventoryAdjustmentDto } from './dtos/CreateQuickInventoryAdjustment.dto';
import { InventoryAdjustmentsFilterDto } from './dtos/InventoryAdjustmentsFilter.dto';
import { InventoryAdjustmentsListResponseDto } from './dtos/InventoryAdjustmentsListResponse.dto';
import { InventoryAdjustmentResponseDto } from './dtos/InventoryAdjustmentResponse.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
@@ -31,6 +33,7 @@ import { InventoryAdjustmentAction } from './types/InventoryAdjustments.types';
@Controller('inventory-adjustments')
@ApiTags('Inventory Adjustments')
@ApiExtraModels(InventoryAdjustmentResponseDto)
@ApiExtraModels(InventoryAdjustmentsListResponseDto)
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class InventoryAdjustmentsController {
@@ -75,15 +78,14 @@ export class InventoryAdjustmentsController {
status: 200,
description: 'The inventory adjustments have been successfully retrieved.',
schema: {
type: 'array',
items: { $ref: getSchemaPath(InventoryAdjustmentResponseDto) },
$ref: getSchemaPath(InventoryAdjustmentsListResponseDto),
},
})
public async getInventoryAdjustments(
@Query() filterDTO: IInventoryAdjustmentsFilter,
@Query() filterDTO: InventoryAdjustmentsFilterDto,
) {
return this.inventoryAdjustmentsApplicationService.getInventoryAdjustments(
filterDTO,
filterDTO as IInventoryAdjustmentsFilter,
);
}

View File

@@ -0,0 +1,9 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
export class InventoryAdjustmentsFilterDto {
@ApiPropertyOptional({ example: 1 })
page?: number;
@ApiPropertyOptional({ example: 12 })
pageSize?: number;
}

View File

@@ -0,0 +1,21 @@
import { ApiProperty } from '@nestjs/swagger';
import { InventoryAdjustmentResponseDto } from './InventoryAdjustmentResponse.dto';
class InventoryAdjustmentsPaginationDto {
@ApiProperty({ example: 1 })
page: number;
@ApiProperty({ example: 12 })
pageSize: number;
@ApiProperty({ example: 42 })
total: number;
}
export class InventoryAdjustmentsListResponseDto {
@ApiProperty({ type: [InventoryAdjustmentResponseDto] })
data: InventoryAdjustmentResponseDto[];
@ApiProperty({ type: InventoryAdjustmentsPaginationDto })
pagination: InventoryAdjustmentsPaginationDto;
}

View File

@@ -1,13 +1,22 @@
import { Controller, Get } from '@nestjs/common';
import { GetDateFormatsService } from './queries/GetDateFormats.service';
import { ApiTags } from '@nestjs/swagger';
import { ApiExtraModels, ApiResponse, ApiTags, getSchemaPath } from '@nestjs/swagger';
import { DateFormatResponseDto } from './dtos/DateFormatResponse.dto';
@Controller('/')
@ApiTags('misc')
@ApiExtraModels(DateFormatResponseDto)
export class MiscellaneousController {
constructor(private readonly getDateFormatsSevice: GetDateFormatsService) {}
@Get('/date-formats')
@ApiResponse({
status: 200,
schema: {
type: 'array',
items: { $ref: getSchemaPath(DateFormatResponseDto) },
},
})
getDateFormats() {
return this.getDateFormatsSevice.getDateFormats();
}

View File

@@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class DateFormatResponseDto {
@ApiProperty({ example: '03/09/2026 [MM/DD/YYYY]' })
label: string;
@ApiProperty({ example: 'MM/DD/YYYY' })
key: string;
}

View File

@@ -36,6 +36,7 @@ import {
OrganizationBuiltResponseExample,
} from './Organization.swagger';
import { GetCurrentOrganizationResponseDto } from './dtos/GetCurrentOrganizationResponse.dto';
import { OrganizationBuildJobResponseDto } from './dtos/OrganizationBuildJobResponse.dto';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
@ApiTags('Organization')
@@ -44,6 +45,7 @@ import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
@IgnoreTenantSeededRoute()
@IgnoreTenantModelsInitialize()
@ApiExtraModels(GetCurrentOrganizationResponseDto)
@ApiExtraModels(OrganizationBuildJobResponseDto)
@ApiCommonHeaders()
export class OrganizationController {
constructor(
@@ -88,6 +90,13 @@ export class OrganizationController {
})
@HttpCode(200)
@ApiOperation({ summary: 'Gets the organization build job details' })
@ApiResponse({
status: 200,
description: 'Returns the organization build job details',
schema: {
$ref: getSchemaPath(OrganizationBuildJobResponseDto),
},
})
async buildJob(@Param('buildJobId') buildJobId: string) {
return this.getBuildOrganizationJobService.getJobDetails(buildJobId);
}

View File

@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
export class OrganizationBuildJobResponseDto {
@ApiProperty({ example: '123' })
id: string;
@ApiProperty({ example: 'active' })
state: string;
@ApiProperty({ example: 50 })
progress: number | Record<string, unknown>;
@ApiProperty({ example: false })
isCompleted: boolean;
@ApiProperty({ example: true })
isRunning: boolean;
@ApiProperty({ example: false })
isWaiting: boolean;
@ApiProperty({ example: false })
isFailed: boolean;
}

View File

@@ -24,6 +24,10 @@ export type ValidateBulkDeleteCreditNotesBody = OpRequestBody<OpForPath<typeof C
export type ValidateBulkDeleteCreditNotesResponse = OpResponseBody<OpForPath<typeof CREDIT_NOTES_ROUTES.VALIDATE_BULK_DELETE, 'post'>>;
export type BulkDeleteCreditNotesBody = OpRequestBody<OpForPath<typeof CREDIT_NOTES_ROUTES.BULK_DELETE, 'post'>>;
export type CreateRefundCreditNoteBody = OpRequestBody<OpForPath<typeof CREDIT_NOTES_ROUTES.REFUNDS, 'post'>>;
export type CreditNoteRefundsResponse = OpResponseBody<OpForPath<typeof CREDIT_NOTES_ROUTES.REFUNDS, 'get'>>;
export type RefundCreditNoteTransaction = OpResponseBody<OpForPath<typeof CREDIT_NOTES_ROUTES.REFUND_BY_ID, 'get'>>;
export type AppliedCreditNoteInvoicesResponse = OpResponseBody<OpForPath<typeof CREDIT_NOTES_ROUTES.APPLIED_INVOICES, 'get'>>;
export type CreditNoteInvoicesToApplyResponse = OpResponseBody<OpForPath<typeof CREDIT_NOTES_ROUTES.APPLY_INVOICES, 'get'>>;
export type ApplyCreditNoteToInvoicesBody = OpRequestBody<OpForPath<typeof CREDIT_NOTES_ROUTES.APPLY_INVOICES, 'post'>>;
export type GetCreditNotesQuery = OpQueryParams<OpForPath<typeof CREDIT_NOTES_ROUTES.LIST, 'get'>>;
@@ -96,9 +100,10 @@ export async function bulkDeleteCreditNotes(
export async function fetchCreditNoteRefunds(
fetcher: ApiFetcher,
creditNoteId: number
): Promise<void> {
): Promise<CreditNoteRefundsResponse> {
const getRefunds = fetcher.path(CREDIT_NOTES_ROUTES.REFUNDS).method('get').create();
await getRefunds({ creditNoteId });
const { data } = await getRefunds({ creditNoteId });
return data;
}
export async function createRefundCreditNote(
@@ -121,17 +126,28 @@ export async function deleteRefundCreditNote(
export async function fetchAppliedInvoices(
fetcher: ApiFetcher,
creditNoteId: number
): Promise<void> {
): Promise<AppliedCreditNoteInvoicesResponse> {
const getApplied = fetcher.path(CREDIT_NOTES_ROUTES.APPLIED_INVOICES).method('get').create();
await getApplied({ creditNoteId });
const { data } = await getApplied({ creditNoteId });
return data;
}
export async function fetchCreditNoteAssociatedInvoicesToApply(
fetcher: ApiFetcher,
creditNoteId: number
): Promise<void> {
): Promise<CreditNoteInvoicesToApplyResponse> {
const get = fetcher.path(CREDIT_NOTES_ROUTES.APPLY_INVOICES).method('get').create();
await get({ creditNoteId });
const { data } = await get({ creditNoteId });
return data;
}
export async function fetchRefundCreditNoteTransaction(
fetcher: ApiFetcher,
refundCreditId: number
): Promise<RefundCreditNoteTransaction> {
const get = fetcher.path(CREDIT_NOTES_ROUTES.REFUND_BY_ID).method('get').create();
const { data } = await get({ refundCreditId });
return data;
}
export async function applyCreditNoteToInvoices(

View File

@@ -43,6 +43,7 @@ export * from './landed-cost';
export * from './generic-resource';
export * from './cashflow-accounts';
export * from './bank-rules';
export * from './misc';
export * from './reports';
/**

View File

@@ -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 INVENTORY_ADJUSTMENTS_ROUTES = {
LIST: '/api/inventory-adjustments',
@@ -12,10 +12,16 @@ export const INVENTORY_ADJUSTMENTS_ROUTES = {
export type InventoryAdjustmentsListResponse = OpResponseBody<OpForPath<typeof INVENTORY_ADJUSTMENTS_ROUTES.LIST, 'get'>>;
export type InventoryAdjustment = OpResponseBody<OpForPath<typeof INVENTORY_ADJUSTMENTS_ROUTES.BY_ID, 'get'>>;
export type CreateQuickInventoryAdjustmentBody = OpRequestBody<OpForPath<typeof INVENTORY_ADJUSTMENTS_ROUTES.QUICK, 'post'>>;
export type GetInventoryAdjustmentsQuery = OpQueryParams<OpForPath<typeof INVENTORY_ADJUSTMENTS_ROUTES.LIST, 'get'>>;
export async function fetchInventoryAdjustments(fetcher: ApiFetcher): Promise<InventoryAdjustmentsListResponse> {
export async function fetchInventoryAdjustments(
fetcher: ApiFetcher,
query?: GetInventoryAdjustmentsQuery
): Promise<InventoryAdjustmentsListResponse> {
const get = fetcher.path(INVENTORY_ADJUSTMENTS_ROUTES.LIST).method('get').create();
const { data } = await get({});
const { data } = await (get as (params?: GetInventoryAdjustmentsQuery) => Promise<{ data: InventoryAdjustmentsListResponse }>)(
query ?? {}
);
return data;
}

19
shared/sdk-ts/src/misc.ts Normal file
View File

@@ -0,0 +1,19 @@
import type { ApiFetcher } from './fetch-utils';
import { paths } from './schema';
import { OpForPath, OpResponseBody } from './utils';
export const MISC_ROUTES = {
DATE_FORMATS: '/api/date-formats',
} as const satisfies Record<string, keyof paths>;
export type DateFormatsResponse = OpResponseBody<
OpForPath<typeof MISC_ROUTES.DATE_FORMATS, 'get'>
>;
export async function fetchDateFormats(
fetcher: ApiFetcher,
): Promise<DateFormatsResponse> {
const get = fetcher.path(MISC_ROUTES.DATE_FORMATS).method('get').create();
const { data } = await get({});
return data;
}

View File

@@ -11,6 +11,7 @@ export const ORGANIZATION_ROUTES = {
} as const satisfies Record<string, keyof paths>;
export type OrganizationCurrent = OpResponseBody<OpForPath<typeof ORGANIZATION_ROUTES.CURRENT, 'get'>>;
export type OrganizationBuildJob = OpResponseBody<OpForPath<typeof ORGANIZATION_ROUTES.BUILD_JOB, 'get'>>;
export type UpdateOrganizationBody = OpRequestBody<OpForPath<typeof ORGANIZATION_ROUTES.UPDATE, 'put'>>;
export async function fetchOrganizationCurrent(fetcher: ApiFetcher): Promise<OrganizationCurrent> {
@@ -31,3 +32,12 @@ export type Organization = OrganizationCurrent;
export async function fetchOrganization(fetcher: ApiFetcher): Promise<Organization> {
return fetchOrganizationCurrent(fetcher);
}
export async function fetchOrganizationBuildJob(
fetcher: ApiFetcher,
buildJobId: number | string
): Promise<OrganizationBuildJob> {
const get = fetcher.path(ORGANIZATION_ROUTES.BUILD_JOB).method('get').create();
const { data } = await get({ buildJobId: Number(buildJobId) });
return data;
}