fix: invoice generate sharable link

This commit is contained in:
Ahmed Bouhuolia
2025-06-27 01:59:46 +02:00
parent e7178a6575
commit 0c0e1dc22e
10 changed files with 74 additions and 19 deletions

View File

@@ -1,9 +1,9 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
import { CommandItemCategoryValidatorService } from './CommandItemCategoryValidator.service';
import { ItemCategory } from '../models/ItemCategory.model';
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { IItemCategoryDeletedPayload } from '../ItemCategory.interfaces';
import { Item } from '@/modules/Items/models/Item';
@@ -46,7 +46,10 @@ export class DeleteItemCategoryService {
await this.unassociateItemsWithCategories(itemCategoryId, trx);
// Delete item category.
await ItemCategory.query(trx).findById(itemCategoryId).delete();
await this.itemCategoryModel()
.query(trx)
.findById(itemCategoryId)
.delete();
// Triggers `onItemCategoryDeleted` event.
await this.eventEmitter.emitAsync(events.itemCategory.onDeleted, {

View File

@@ -1,4 +1,5 @@
import * as moment from 'moment';
import { ClsService } from 'nestjs-cls';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
import { SaleInvoice } from '../SaleInvoices/models/SaleInvoice';
@@ -6,8 +7,6 @@ import { TransformerInjectable } from '../Transformer/TransformerInjectable.serv
import { PaymentLink } from './models/PaymentLink';
import { ServiceError } from '../Items/ServiceError';
import { GetInvoicePaymentLinkMetaTransformer } from '../SaleInvoices/queries/GetInvoicePaymentLink.transformer';
import { ClsService } from 'nestjs-cls';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
import { TenantModel } from '../System/models/TenantModel';
@Injectable()
@@ -15,7 +14,6 @@ export class GetInvoicePaymentLinkMetadata {
constructor(
private readonly transformer: TransformerInjectable,
private readonly clsService: ClsService,
private readonly tenancyContext: TenancyContext,
@Inject(SaleInvoice.name)
private readonly saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,

View File

@@ -25,10 +25,7 @@ export class PaymentLinksController {
schema: {
type: 'object',
properties: {
data: {
type: 'object',
description: 'Payment link metadata',
},
data: { type: 'object', description: 'Payment link metadata' },
},
},
})

View File

@@ -22,6 +22,7 @@ import {
CreateSaleInvoiceDto,
EditSaleInvoiceDto,
} from './dtos/SaleInvoice.dto';
import { GenerateShareLink } from './commands/GenerateInvoicePaymentLink.service';
@Injectable()
export class SaleInvoiceApplication {
@@ -39,6 +40,7 @@ export class SaleInvoiceApplication {
private getSaleInvoiceStateService: GetSaleInvoiceState,
private sendSaleInvoiceMailService: SendSaleInvoiceMail,
private getSaleInvoiceMailStateService: GetSaleInvoiceMailState,
private generateShareLinkService: GenerateShareLink,
) {}
/**
@@ -202,4 +204,23 @@ export class SaleInvoiceApplication {
saleInvoiceid,
);
}
/**
* Generate the given sale invoice sharable link.
* @param {number} saleInvoiceId
* @param {string} publicity
* @param {string} expiryTime
* @returns
*/
public generateSaleInvoiceSharableLink(
saleInvoiceId: number,
publicity: string = 'private',
expiryTime: string = '',
) {
return this.generateShareLinkService.generatePaymentLink(
saleInvoiceId,
publicity,
expiryTime,
);
}
}

View File

@@ -38,12 +38,14 @@ import { AcceptType } from '@/constants/accept-type';
import { SaleInvoiceResponseDto } from './dtos/SaleInvoiceResponse.dto';
import { PaginatedResponseDto } from '@/common/dtos/PaginatedResults.dto';
import { SaleInvoiceStateResponseDto } from './dtos/SaleInvoiceState.dto';
import { GenerateSaleInvoiceSharableLinkResponseDto } from './dtos/generateSaleInvoiceSharableLinkResponse.dto';
@Controller('sale-invoices')
@ApiTags('Sale Invoices')
@ApiExtraModels(SaleInvoiceResponseDto)
@ApiExtraModels(PaginatedResponseDto)
@ApiExtraModels(SaleInvoiceStateResponseDto)
@ApiExtraModels(GenerateSaleInvoiceSharableLinkResponseDto)
@ApiHeader({
name: 'organization-id',
description: 'The organization id',
@@ -318,4 +320,25 @@ export class SaleInvoicesController {
): Promise<SaleInvoiceMailState> {
return this.saleInvoiceApplication.getSaleInvoiceMailState(id);
}
@Post(':id/generate-link')
@ApiOperation({
summary: 'Generate sharable sale invoice link (private or public)',
})
@ApiResponse({
status: 201,
description: 'The link has been generated successfully.',
schema: {
$ref: getSchemaPath(GenerateSaleInvoiceSharableLinkResponseDto),
},
})
@ApiParam({
name: 'id',
required: true,
type: Number,
description: 'The sale invoice id',
})
generateSaleInvoiceSharableLink(@Param('id', ParseIntPipe) id: number) {
return this.saleInvoiceApplication.generateSaleInvoiceSharableLink(id);
}
}

View File

@@ -9,6 +9,7 @@ import { events } from '@/common/events/events';
import { PaymentLink } from '@/modules/PaymentLinks/models/PaymentLink';
import { SaleInvoice } from '../models/SaleInvoice';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
@Injectable()
export class GenerateShareLink {
@@ -16,12 +17,13 @@ export class GenerateShareLink {
private uow: UnitOfWork,
private eventPublisher: EventEmitter2,
private transformer: TransformerInjectable,
private tenancyContext: TenancyContext,
@Inject(SaleInvoice.name)
private saleInvoiceModel: TenantModelProxy<typeof SaleInvoice>,
@Inject(PaymentLink.name)
private paymentLinkModel: TenantModelProxy<typeof PaymentLink>,
private paymentLinkModel: typeof PaymentLink,
) {}
/**
@@ -39,10 +41,10 @@ export class GenerateShareLink {
.query()
.findById(saleInvoiceId)
.throwIfNotFound();
const tenant = await this.tenancyContext.getTenant();
// Generate unique uuid for sharable link.
const linkId = uuidv4() as string;
const commonEventPayload = {
saleInvoiceId,
publicity,
@@ -54,11 +56,12 @@ export class GenerateShareLink {
events.saleInvoice.onPublicLinkGenerating,
{ ...commonEventPayload, trx },
);
const paymentLink = await this.paymentLinkModel().query().insert({
const paymentLink = await this.paymentLinkModel.query().insert({
linkId,
publicity,
resourceId: foundInvoice.id,
resourceType: 'SaleInvoice',
tenantId: tenant.id,
});
// Triggers `onPublicSharableLinkGenerated` event.
await this.eventPublisher.emitAsync(

View File

@@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
export class GenerateSaleInvoiceSharableLinkResponseDto {
@ApiProperty({
description: 'Sharable payment link for the sale invoice',
example:
'http://localhost:3000/payment/123e4567-e89b-12d3-a456-426614174000',
})
link: string;
}

View File

@@ -29,7 +29,6 @@ import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice';
import { PaymentReceivedEntry } from '@/modules/PaymentReceived/models/PaymentReceivedEntry';
import { CreditNoteAppliedInvoice } from '@/modules/CreditNotesApplyInvoice/models/CreditNoteAppliedInvoice';
import { CreditNote } from '@/modules/CreditNotes/models/CreditNote';
import { PaymentLink } from '@/modules/PaymentLinks/models/PaymentLink';
import { SaleReceipt } from '@/modules/SaleReceipts/models/SaleReceipt';
import { ManualJournal } from '@/modules/ManualJournals/models/ManualJournal';
import { ManualJournalEntry } from '@/modules/ManualJournals/models/ManualJournalEntry';
@@ -70,7 +69,6 @@ const models = [
CreditNoteAppliedInvoice,
CreditNote,
RefundCreditNote,
PaymentLink,
SaleReceipt,
ManualJournal,
ManualJournalEntry,
@@ -82,7 +80,6 @@ const models = [
TenantUser,
];
/**
* Decorator factory that registers a model with the tenancy system.
* @param model The model class to register

View File

@@ -49,7 +49,7 @@ export const SharePaymentLinkForm = ({
generateShareLink(values)
.then((res) => {
setSubmitting(false);
setUrl(res.link?.link);
setUrl(res.link);
})
.catch(() => {
setSubmitting(false);

View File

@@ -45,7 +45,10 @@ export function useCreatePaymentLink(
return useMutation<CreatePaymentLinkResponse, Error, CreatePaymentLinkValues>(
(values) =>
apiRequest
.post('/payment-links/generate', transfromToSnakeCase(values))
.post(
`/sale-invoices/${values.transactionId}/generate-link`,
transfromToSnakeCase(values),
)
.then((res) => res.data),
{
...options,