diff --git a/packages/server/src/modules/ItemCategories/commands/DeleteItemCategory.service.ts b/packages/server/src/modules/ItemCategories/commands/DeleteItemCategory.service.ts index b7001e79a..c1f8e45e8 100644 --- a/packages/server/src/modules/ItemCategories/commands/DeleteItemCategory.service.ts +++ b/packages/server/src/modules/ItemCategories/commands/DeleteItemCategory.service.ts @@ -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, { diff --git a/packages/server/src/modules/PaymentLinks/GetInvoicePaymentLinkMetadata.ts b/packages/server/src/modules/PaymentLinks/GetInvoicePaymentLinkMetadata.ts index 02691cce9..367a09c47 100644 --- a/packages/server/src/modules/PaymentLinks/GetInvoicePaymentLinkMetadata.ts +++ b/packages/server/src/modules/PaymentLinks/GetInvoicePaymentLinkMetadata.ts @@ -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, diff --git a/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts b/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts index a621bd746..7d69f1bad 100644 --- a/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts +++ b/packages/server/src/modules/PaymentLinks/PaymentLinks.controller.ts @@ -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' }, }, }, }) diff --git a/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts b/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts index d9a761ea3..8c4911c10 100644 --- a/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts +++ b/packages/server/src/modules/SaleInvoices/SaleInvoices.application.ts @@ -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, + ); + } } diff --git a/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts b/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts index c0b364162..1704d1aab 100644 --- a/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts +++ b/packages/server/src/modules/SaleInvoices/SaleInvoices.controller.ts @@ -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 { 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); + } } diff --git a/packages/server/src/modules/SaleInvoices/commands/GenerateInvoicePaymentLink.service.ts b/packages/server/src/modules/SaleInvoices/commands/GenerateInvoicePaymentLink.service.ts index af78aa548..2a181d6d7 100644 --- a/packages/server/src/modules/SaleInvoices/commands/GenerateInvoicePaymentLink.service.ts +++ b/packages/server/src/modules/SaleInvoices/commands/GenerateInvoicePaymentLink.service.ts @@ -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, @Inject(PaymentLink.name) - private paymentLinkModel: TenantModelProxy, + 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( diff --git a/packages/server/src/modules/SaleInvoices/dtos/GenerateSaleInvoiceSharableLinkResponse.dto.ts b/packages/server/src/modules/SaleInvoices/dtos/GenerateSaleInvoiceSharableLinkResponse.dto.ts new file mode 100644 index 000000000..ab911a3de --- /dev/null +++ b/packages/server/src/modules/SaleInvoices/dtos/GenerateSaleInvoiceSharableLinkResponse.dto.ts @@ -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; +} diff --git a/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts b/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts index 66fa82478..fdfd9df1a 100644 --- a/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts +++ b/packages/server/src/modules/Tenancy/TenancyModels/Tenancy.module.ts @@ -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 diff --git a/packages/webapp/src/containers/PaymentLink/dialogs/SharePaymentLinkDialog/SharePaymentLinkForm.tsx b/packages/webapp/src/containers/PaymentLink/dialogs/SharePaymentLinkDialog/SharePaymentLinkForm.tsx index 42b879d1f..84482bec0 100644 --- a/packages/webapp/src/containers/PaymentLink/dialogs/SharePaymentLinkDialog/SharePaymentLinkForm.tsx +++ b/packages/webapp/src/containers/PaymentLink/dialogs/SharePaymentLinkDialog/SharePaymentLinkForm.tsx @@ -49,7 +49,7 @@ export const SharePaymentLinkForm = ({ generateShareLink(values) .then((res) => { setSubmitting(false); - setUrl(res.link?.link); + setUrl(res.link); }) .catch(() => { setSubmitting(false); diff --git a/packages/webapp/src/hooks/query/payment-link.ts b/packages/webapp/src/hooks/query/payment-link.ts index 1011f48bf..2cd78f5a5 100644 --- a/packages/webapp/src/hooks/query/payment-link.ts +++ b/packages/webapp/src/hooks/query/payment-link.ts @@ -45,7 +45,10 @@ export function useCreatePaymentLink( return useMutation( (values) => apiRequest - .post('/payment-links/generate', transfromToSnakeCase(values)) + .post( + `/sale-invoices/${values.transactionId}/generate-link`, + transfromToSnakeCase(values), + ) .then((res) => res.data), { ...options,