diff --git a/packages/server/src/modules/Attachments/LinkAttachment.ts b/packages/server/src/modules/Attachments/LinkAttachment.ts index 4bffeadd5..e1015ac73 100644 --- a/packages/server/src/modules/Attachments/LinkAttachment.ts +++ b/packages/server/src/modules/Attachments/LinkAttachment.ts @@ -1,5 +1,5 @@ import { ModuleRef } from '@nestjs/core'; -import bluebird from 'bluebird'; +import * as bluebird from 'bluebird'; import { Knex } from 'knex'; import { validateLinkModelEntryExists, @@ -53,7 +53,8 @@ export class LinkAttachment { const foundLinkModel = await LinkModel().query(trx).findById(modelId); validateLinkModelEntryExists(foundLinkModel); - const foundLinks = await this.documentLinkModel().query(trx) + const foundLinks = await this.documentLinkModel() + .query(trx) .where('modelRef', modelRef) .where('modelId', modelId) .where('documentId', foundFile.id); @@ -70,7 +71,7 @@ export class LinkAttachment { /** * Links the given file keys to the given model type and id. - * @param {string[]} filekeys - File keys. + * @param {string[]} filekeys - File keys. * @param {string} modelRef - Model reference. * @param {number} modelId - Model id. * @param {Knex.Transaction} trx - Knex transaction. diff --git a/packages/server/src/modules/Auth/Auth.controller.ts b/packages/server/src/modules/Auth/Auth.controller.ts index 9a12562ee..534e66c01 100644 --- a/packages/server/src/modules/Auth/Auth.controller.ts +++ b/packages/server/src/modules/Auth/Auth.controller.ts @@ -65,7 +65,7 @@ export class AuthController { return this.authApp.signUp(signupDto); } - @Post('/signup/confirm') + @Post('/signup/verify') @ApiOperation({ summary: 'Confirm user signup' }) @ApiBody({ schema: { diff --git a/packages/server/src/modules/Auth/Authed.controller.ts b/packages/server/src/modules/Auth/Authed.controller.ts index 79c4a27da..e18b3635c 100644 --- a/packages/server/src/modules/Auth/Authed.controller.ts +++ b/packages/server/src/modules/Auth/Authed.controller.ts @@ -7,17 +7,13 @@ import { import { GetAuthenticatedAccount } from './queries/GetAuthedAccount.service'; import { Controller, Get, Post } from '@nestjs/common'; import { Throttle } from '@nestjs/throttler'; -import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; -import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; +import { TenantAgnosticRoute } from '../Tenancy/TenancyGlobal.guard'; import { AuthenticationApplication } from './AuthApplication.sevice'; -import { TenancyContext } from '../Tenancy/TenancyContext.service'; import { IgnoreUserVerifiedRoute } from './guards/EnsureUserVerified.guard'; @Controller('/auth') @ApiTags('Auth') -@ApiExcludeController() -@IgnoreTenantSeededRoute() -@IgnoreTenantInitializedRoute() +@TenantAgnosticRoute() @IgnoreUserVerifiedRoute() @Throttle({ auth: {} }) export class AuthedController { diff --git a/packages/server/src/modules/Auth/commands/AuthSignup.service.ts b/packages/server/src/modules/Auth/commands/AuthSignup.service.ts index 90188c77d..8cf42390a 100644 --- a/packages/server/src/modules/Auth/commands/AuthSignup.service.ts +++ b/packages/server/src/modules/Auth/commands/AuthSignup.service.ts @@ -13,7 +13,6 @@ import { IAuthSignedUpEventPayload, IAuthSigningUpEventPayload, } from '../Auth.interfaces'; -import { defaultTo } from 'ramda'; import { ERRORS } from '../Auth.constants'; import { hashPassword } from '../Auth.utils'; import { ClsService } from 'nestjs-cls'; @@ -51,10 +50,10 @@ export class AuthSignupService { const signupConfirmation = this.configService.get('signupConfirmation'); const verifyTokenCrypto = crypto.randomBytes(64).toString('hex'); - const verifiedEnabed = defaultTo(signupConfirmation.enabled, false); + const verifiedEnabed = signupConfirmation.enabled ?? false; const verifyToken = verifiedEnabed ? verifyTokenCrypto : ''; const verified = !verifiedEnabed; - + const inviteAcceptedAt = moment().format('YYYY-MM-DD'); // Triggers signin up event. diff --git a/packages/server/src/modules/Auth/commands/AuthSignupConfirmResend.service.ts b/packages/server/src/modules/Auth/commands/AuthSignupConfirmResend.service.ts index 3f923a032..7e0721bab 100644 --- a/packages/server/src/modules/Auth/commands/AuthSignupConfirmResend.service.ts +++ b/packages/server/src/modules/Auth/commands/AuthSignupConfirmResend.service.ts @@ -4,7 +4,6 @@ import { SystemUser } from '@/modules/System/models/SystemUser'; import { ServiceError } from '@/modules/Items/ServiceError'; import { ERRORS } from '../Auth.constants'; import { events } from '@/common/events/events'; -import { ModelObject } from 'objection'; import { ISignUpConfigmResendedEventPayload } from '../Auth.interfaces'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts index 4e96ba443..76f7261a9 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Param, Post, @@ -14,6 +15,10 @@ 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 { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service'; +import { CreditNoteApplyToInvoices } from './commands/CreditNoteApplyToInvoices.service'; +import { DeleteCreditNoteApplyToInvoices } from './commands/DeleteCreditNoteApplyToInvoices.service'; +import { ApplyCreditNoteToInvoicesDto } from './dtos/ApplyCreditNoteToInvoices.dto'; @Controller('credit-notes') @ApiTags('Credit Notes Apply Invoice') @@ -22,6 +27,9 @@ import { CreditNoteAction } from '../CreditNotes/types/CreditNotes.types'; export class CreditNotesApplyInvoiceController { constructor( private readonly getCreditNoteAssociatedAppliedInvoicesService: GetCreditNoteAssociatedAppliedInvoices, + private readonly getCreditNoteAssociatedInvoicesToApplyService: GetCreditNoteAssociatedInvoicesToApply, + private readonly creditNoteApplyToInvoicesService: CreditNoteApplyToInvoices, + private readonly deleteCreditNoteApplyToInvoicesService: DeleteCreditNoteApplyToInvoices, ) {} @Get(':creditNoteId/applied-invoices') @@ -39,6 +47,23 @@ export class CreditNotesApplyInvoiceController { ); } + @Get(':creditNoteId/apply-invoices') + @RequirePermission(CreditNoteAction.View, AbilitySubject.CreditNote) + @ApiOperation({ summary: 'Get credit note associated invoices to apply' }) + @ApiResponse({ + status: 200, + description: 'Credit note associated invoices to apply', + }) + @ApiResponse({ status: 404, description: 'Credit note not found' }) + @ApiResponse({ status: 400, description: 'Invalid input data' }) + getCreditNoteAssociatedInvoicesToApply( + @Param('creditNoteId') creditNoteId: number, + ) { + return this.getCreditNoteAssociatedInvoicesToApplyService.getCreditAssociatedInvoicesToApply( + creditNoteId, + ); + } + @Post(':creditNoteId/apply-invoices') @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) @ApiOperation({ summary: 'Apply credit note to invoices' }) @@ -48,9 +73,32 @@ export class CreditNotesApplyInvoiceController { }) @ApiResponse({ status: 404, description: 'Credit note not found' }) @ApiResponse({ status: 400, description: 'Invalid input data' }) - applyCreditNoteToInvoices(@Param('creditNoteId') creditNoteId: number) { - return this.getCreditNoteAssociatedAppliedInvoicesService.getCreditAssociatedAppliedInvoices( + applyCreditNoteToInvoices( + @Param('creditNoteId') creditNoteId: number, + @Body() applyDto: ApplyCreditNoteToInvoicesDto, + ) { + return this.creditNoteApplyToInvoicesService.applyCreditNoteToInvoices( creditNoteId, + applyDto, + ); + } + + @Delete('applied-invoices/:applyCreditToInvoicesId') + @RequirePermission(CreditNoteAction.Edit, AbilitySubject.CreditNote) + @ApiOperation({ summary: 'Delete applied credit note to invoice' }) + @ApiResponse({ + status: 200, + description: 'Credit note application successfully deleted', + }) + @ApiResponse({ + status: 404, + description: 'Credit note application not found', + }) + deleteApplyCreditNoteToInvoices( + @Param('applyCreditToInvoicesId') applyCreditToInvoicesId: number, + ) { + return this.deleteCreditNoteApplyToInvoicesService.deleteApplyCreditNoteToInvoices( + applyCreditToInvoicesId, ); } } diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts index 7998e3e63..5dab07c49 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/CreditNotesApplyInvoice.module.ts @@ -9,6 +9,8 @@ import { CreditNotesModule } from '../CreditNotes/CreditNotes.module'; import { GetCreditNoteAssociatedAppliedInvoices } from './queries/GetCreditNoteAssociatedAppliedInvoices.service'; import { GetCreditNoteAssociatedInvoicesToApply } from './queries/GetCreditNoteAssociatedInvoicesToApply.service'; import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.controller'; +import { CreditNoteApplySyncCreditSubscriber } from './subscribers/CreditNoteApplySyncCreditSubscriber'; +import { CreditNoteApplySyncInvoicesCreditedAmountSubscriber } from './subscribers/CreditNoteApplySyncInvoicesSubscriber'; @Module({ providers: [ @@ -19,6 +21,8 @@ import { CreditNotesApplyInvoiceController } from './CreditNotesApplyInvoice.con CreditNoteApplySyncCredit, GetCreditNoteAssociatedAppliedInvoices, GetCreditNoteAssociatedInvoicesToApply, + CreditNoteApplySyncCreditSubscriber, + CreditNoteApplySyncInvoicesCreditedAmountSubscriber, ], exports: [DeleteCustomerLinkedCreditNoteService], imports: [PaymentsReceivedModule, forwardRef(() => CreditNotesModule)], diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts index efc14b72f..2629c2a02 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/commands/CreditNoteApplySyncInvoices.service.ts @@ -1,6 +1,6 @@ import { Knex } from 'knex'; import { Injectable, Inject } from '@nestjs/common'; -import Bluebird from 'bluebird'; +import * as Bluebird from 'bluebird'; import { ICreditNoteAppliedToInvoice } from '../types/CreditNoteApplyInvoice.types'; import { SaleInvoice } from '@/modules/SaleInvoices/models/SaleInvoice'; import { CreditNoteAppliedInvoice } from '../models/CreditNoteAppliedInvoice'; diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts b/packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts new file mode 100644 index 000000000..e193b164b --- /dev/null +++ b/packages/server/src/modules/CreditNotesApplyInvoice/dtos/ApplyCreditNoteToInvoices.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + ArrayMinSize, + IsArray, + IsInt, + IsNotEmpty, + IsNumber, + ValidateNested, +} from 'class-validator'; + +export class ApplyCreditNoteInvoiceEntryDto { + @IsNotEmpty() + @IsInt() + @ApiProperty({ description: 'Invoice ID to apply credit to', example: 1 }) + invoiceId: number; + + @IsNotEmpty() + @IsNumber() + @ApiProperty({ description: 'Amount to apply', example: 100.5 }) + amount: number; +} + +export class ApplyCreditNoteToInvoicesDto { + @IsArray() + @ArrayMinSize(1) + @ValidateNested({ each: true }) + @Type(() => ApplyCreditNoteInvoiceEntryDto) + @ApiProperty({ + description: 'Entries of invoice ID and amount to apply', + type: [ApplyCreditNoteInvoiceEntryDto], + example: [ + { invoice_id: 1, amount: 100.5 }, + { invoice_id: 2, amount: 50 }, + ], + }) + entries: ApplyCreditNoteInvoiceEntryDto[]; +} diff --git a/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts b/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts index 5169364a8..ac653c19e 100644 --- a/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts +++ b/packages/server/src/modules/CreditNotesApplyInvoice/subscribers/CreditNoteApplySyncInvoicesSubscriber.ts @@ -8,7 +8,7 @@ import { CreditNoteApplySyncInvoicesCreditedAmount } from '../commands/CreditNot import { events } from '@/common/events/events'; @Injectable() -export default class CreditNoteApplySyncInvoicesCreditedAmountSubscriber { +export class CreditNoteApplySyncInvoicesCreditedAmountSubscriber { constructor( private readonly syncInvoicesWithCreditNote: CreditNoteApplySyncInvoicesCreditedAmount, ) {} diff --git a/packages/server/src/modules/Organization/Organization.controller.ts b/packages/server/src/modules/Organization/Organization.controller.ts index dc38fdfa9..272b531b9 100644 --- a/packages/server/src/modules/Organization/Organization.controller.ts +++ b/packages/server/src/modules/Organization/Organization.controller.ts @@ -28,6 +28,7 @@ import { UpdateOrganizationService } from './commands/UpdateOrganization.service import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; import { IgnoreTenantModelsInitialize } from '../Tenancy/TenancyInitializeModels.guard'; +import { IgnoreUserVerifiedRoute } from '../Auth/guards/EnsureUserVerified.guard'; import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service'; import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; import { @@ -93,6 +94,7 @@ export class OrganizationController { @Get('current') @HttpCode(200) + @IgnoreUserVerifiedRoute() @ApiOperation({ summary: 'Get current organization' }) @ApiResponse({ status: 200, diff --git a/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts b/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts index 01333e2a3..67282b5ee 100644 --- a/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts +++ b/packages/server/src/modules/Tenancy/EnsureTenantIsInitialized.guard.ts @@ -8,6 +8,7 @@ import { import { TenancyContext } from './TenancyContext.service'; import { Reflector } from '@nestjs/core'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; +import { IS_TENANT_AGNOSTIC } from './TenancyGlobal.guard'; export const IS_IGNORE_TENANT_INITIALIZED = 'IS_IGNORE_TENANT_INITIALIZED'; export const IgnoreTenantInitializedRoute = () => @@ -35,8 +36,12 @@ export class EnsureTenantIsInitializedGuard implements CanActivate { IS_PUBLIC_ROUTE, [context.getHandler(), context.getClass()], ); + const isTenantAgnostic = this.reflector.getAllAndOverride( + IS_TENANT_AGNOSTIC, + [context.getHandler(), context.getClass()], + ); // Skip the guard early if the route marked as public or ignored. - if (isPublic || isIgnoreEnsureTenantInitialized) { + if (isPublic || isIgnoreEnsureTenantInitialized || isTenantAgnostic) { return true; } const tenant = await this.tenancyContext.getTenant(); diff --git a/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts b/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts index 8149179bc..cab864c8f 100644 --- a/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts +++ b/packages/server/src/modules/Tenancy/EnsureTenantIsSeeded.guards.ts @@ -9,6 +9,7 @@ import { import { TenancyContext } from './TenancyContext.service'; import { Reflector } from '@nestjs/core'; import { IS_PUBLIC_ROUTE } from '../Auth/Auth.constants'; +import { IS_TENANT_AGNOSTIC } from './TenancyGlobal.guard'; export const IS_IGNORE_TENANT_SEEDED = 'IS_IGNORE_TENANT_SEEDED'; export const IgnoreTenantSeededRoute = () => @@ -36,7 +37,12 @@ export class EnsureTenantIsSeededGuard implements CanActivate { context.getHandler(), context.getClass(), ]); - if (isPublic || isIgnoreEnsureTenantSeeded) { + const isTenantAgnostic = this.reflector.getAllAndOverride( + IS_TENANT_AGNOSTIC, + [context.getHandler(), context.getClass()], + ); + // Skip the guard early if the route marked as public, tenant agnostic or ignored. + if (isPublic || isIgnoreEnsureTenantSeeded || isTenantAgnostic) { return true; } const tenant = await this.tenancyContext.getTenant(); diff --git a/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts b/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts index 60a002ac6..eb22d212c 100644 --- a/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts +++ b/packages/server/src/modules/VendorCreditsApplyBills/command/ApplyVendorCreditSyncBills.service.ts @@ -1,4 +1,4 @@ -import Bluebird from 'bluebird'; +import * as Bluebird from 'bluebird'; import { Inject, Injectable } from '@nestjs/common'; import { Knex } from 'knex'; import { IVendorCreditAppliedBill } from '../types/VendorCreditApplyBills.types'; diff --git a/packages/webapp/src/containers/Authentication/RegisterVerify.module.scss b/packages/webapp/src/containers/Authentication/RegisterVerify.module.scss deleted file mode 100644 index f0754f470..000000000 --- a/packages/webapp/src/containers/Authentication/RegisterVerify.module.scss +++ /dev/null @@ -1,18 +0,0 @@ - -.root { - text-align: center; -} - -.title{ - font-size: 18px; - font-weight: 600; - margin-bottom: 0.5rem; - color: #252A31; -} - -.description{ - margin-bottom: 1rem; - font-size: 15px; - line-height: 1.45; - color: #404854; -} \ No newline at end of file diff --git a/packages/webapp/src/containers/Authentication/RegisterVerify.tsx b/packages/webapp/src/containers/Authentication/RegisterVerify.tsx index 46148a426..d022ff454 100644 --- a/packages/webapp/src/containers/Authentication/RegisterVerify.tsx +++ b/packages/webapp/src/containers/Authentication/RegisterVerify.tsx @@ -1,12 +1,13 @@ // @ts-nocheck import { Button, Intent } from '@blueprintjs/core'; +import { x } from '@xstyled/emotion'; import AuthInsider from './AuthInsider'; import { AuthInsiderCard } from './_components'; -import styles from './RegisterVerify.module.scss'; import { AppToaster, Stack } from '@/components'; import { useAuthActions, useAuthUserVerifyEmail } from '@/hooks/state'; import { useAuthSignUpVerifyResendMail } from '@/hooks/query'; import { AuthContainer } from './AuthContainer'; +import { useIsDarkMode } from '@/hooks/useDarkMode'; export default function RegisterVerify() { const { setLogout } = useAuthActions(); @@ -14,6 +15,7 @@ export default function RegisterVerify() { useAuthSignUpVerifyResendMail(); const emailAddress = useAuthUserVerifyEmail(); + const isDarkMode = useIsDarkMode(); const handleResendMailBtnClick = () => { resendSignUpVerifyMail() @@ -37,12 +39,24 @@ export default function RegisterVerify() { return ( - -

Please verify your email

-

+ + + Please verify your email + + We sent an email to {emailAddress} Click the link inside to get started. -

+