diff --git a/database/migrations/Migration20260626120000.ts b/database/migrations/Migration20260626120000.ts deleted file mode 100644 index 4711634..0000000 --- a/database/migrations/Migration20260626120000.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260626120000 extends Migration { - - override async up(): Promise { - this.addSql(`alter table "requests" drop column if exists "status";`); - } - - override async down(): Promise { - this.addSql(`alter table "requests" add column "status" text check ("status" in ('pending', 'invoiced')) not null default 'pending';`); - } - -} diff --git a/database/migrations/Migration20260526120100.ts b/database/migrations/Migration20260626140000.ts similarity index 79% rename from database/migrations/Migration20260526120100.ts rename to database/migrations/Migration20260626140000.ts index 6be0a94..6f25a78 100644 --- a/database/migrations/Migration20260526120100.ts +++ b/database/migrations/Migration20260626140000.ts @@ -1,8 +1,13 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20260526120100 extends Migration { +export class Migration20260626140000 extends Migration { override async up(): Promise { + this.addSql(` + alter table "invoices" + add column "confirm_status" text check ("confirm_status" in ('pending', 'confirmed')) not null default 'pending'; + `); + this.addSql(` CREATE OR REPLACE FUNCTION recalc_invoice_totals() RETURNS TRIGGER AS $$ @@ -14,6 +19,7 @@ export class Migration20260526120100 extends Migration { v_total int; v_paid int; v_enable_tax boolean; + v_confirm_status text; BEGIN v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id); @@ -48,6 +54,18 @@ export class Migration20260526120100 extends Migration { AND status = 'paid' AND deleted_at IS NULL; + SELECT CASE + WHEN EXISTS ( + SELECT 1 + FROM invoice_items + WHERE invoice_id = v_invoice_id + AND deleted_at IS NULL + AND confirmed_at IS NOT NULL + ) THEN 'confirmed' + ELSE 'pending' + END + INTO v_confirm_status; + UPDATE invoices SET sub_total = v_subtotal, @@ -55,7 +73,8 @@ export class Migration20260526120100 extends Migration { tax_amount = v_tax, total = v_total, paid_amount = v_paid, - balance = v_total - v_paid + balance = v_total - v_paid, + confirm_status = v_confirm_status WHERE id = v_invoice_id; RETURN NULL; @@ -119,6 +138,7 @@ export class Migration20260526120100 extends Migration { this.addSql(`DROP TRIGGER IF EXISTS trg_payments_recalc ON payments;`); this.addSql(`DROP FUNCTION IF EXISTS recalc_invoice_totals();`); this.addSql(`DROP FUNCTION IF EXISTS recalc_invoice_payments();`); + this.addSql(`alter table "invoices" drop column if exists "confirm_status";`); } } diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index c02ae97..f09c475 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -35,7 +35,7 @@ export class AuthService { await this.otpService.set(phone, code) - await this.smsService.sendotp(phone, code); + // await this.smsService.sendotp(phone, code); return { code }; } diff --git a/src/modules/invoice/dto/find-invoices.dto.ts b/src/modules/invoice/dto/find-invoices.dto.ts index 4f1d2a7..22614d5 100644 --- a/src/modules/invoice/dto/find-invoices.dto.ts +++ b/src/modules/invoice/dto/find-invoices.dto.ts @@ -7,7 +7,9 @@ import { IsIn, IsDateString, IsBoolean, + IsEnum, } from 'class-validator'; +import { InvoiceConfirmStatusEnum } from '../enum/invoice-confirm-status.enum'; import { Type, Transform } from 'class-transformer'; import { ApiPropertyOptional } from '@nestjs/swagger'; @@ -65,6 +67,14 @@ export class FindInvoicesDto { @IsBoolean() enableTax?: boolean; + @ApiPropertyOptional({ + enum: InvoiceConfirmStatusEnum, + description: 'Filter by invoice confirmation status', + }) + @IsOptional() + @IsEnum(InvoiceConfirmStatusEnum) + confirmStatus?: InvoiceConfirmStatusEnum; + @ApiPropertyOptional({ default: 'createdAt' }) @IsOptional() @IsString() diff --git a/src/modules/invoice/entities/invoice.entity.ts b/src/modules/invoice/entities/invoice.entity.ts index cda9cc4..e360119 100644 --- a/src/modules/invoice/entities/invoice.entity.ts +++ b/src/modules/invoice/entities/invoice.entity.ts @@ -15,7 +15,7 @@ import { User } from '../../user/entities/user.entity'; import { Payment } from 'src/modules/payment/entities/payment.entity'; import { ulid } from 'ulid'; import { BaseEntity } from 'src/common/entities/base.entity'; -import { InvoiceStatusEnum } from '../enum/invoice-status.enum'; +import { InvoiceConfirmStatusEnum } from '../enum/invoice-confirm-status.enum'; import { InvoiceItem } from './invoice-item.entity'; import { Request } from 'src/modules/request/entities/request.entity'; import { IAttachment } from 'src/modules/order/interface/order.interface'; @@ -24,7 +24,7 @@ import { IAttachment } from 'src/modules/order/interface/order.interface'; @Entity({ tableName: 'invoices' }) @Index({ properties: ['user'] }) export class Invoice extends BaseEntity { - [OptionalProps]?: 'createdAt' | 'deletedAt' | 'invoiceNumber' + [OptionalProps]?: 'createdAt' | 'deletedAt' | 'invoiceNumber' | 'confirmStatus' @PrimaryKey({ type: 'string', columnType: 'char(26)' }) id: string = ulid() @@ -80,9 +80,8 @@ export class Invoice extends BaseEntity { balance: number & Opt; - // @Enum(() => InvoiceStatusEnum) - // status: InvoiceStatusEnum = InvoiceStatusEnum.PENDING; - + @Enum({ items: () => InvoiceConfirmStatusEnum, persist: false }) + confirmStatus!: InvoiceConfirmStatusEnum & Opt; @Property({ type: 'boolean', default: false }) enableTax?: boolean = false diff --git a/src/modules/invoice/enum/invoice-confirm-status.enum.ts b/src/modules/invoice/enum/invoice-confirm-status.enum.ts new file mode 100644 index 0000000..1a13d49 --- /dev/null +++ b/src/modules/invoice/enum/invoice-confirm-status.enum.ts @@ -0,0 +1,4 @@ +export enum InvoiceConfirmStatusEnum { + PENDING = 'pending', + CONFIRMED = 'confirmed', +} diff --git a/src/modules/invoice/invoice.service.ts b/src/modules/invoice/invoice.service.ts index d88541a..13e3665 100644 --- a/src/modules/invoice/invoice.service.ts +++ b/src/modules/invoice/invoice.service.ts @@ -69,6 +69,14 @@ export class InvoiceService { if (!requestEntity) { throw new NotFoundException('Request not found'); } + + const existingInvoice = await this.em.findOne(Invoice, { + request: { id: dto.requestId }, + }); + if (existingInvoice) { + throw new BadRequestException('An invoice already exists for this request'); + } + user = requestEntity.user; request = requestEntity; } else if (dto.userId) { diff --git a/src/modules/invoice/repositories/invoice.repository.ts b/src/modules/invoice/repositories/invoice.repository.ts index baffbc8..d21af1f 100644 --- a/src/modules/invoice/repositories/invoice.repository.ts +++ b/src/modules/invoice/repositories/invoice.repository.ts @@ -26,7 +26,8 @@ export class InvoiceRepository extends EntityRepository { from, to, enableTax, - requestId + requestId, + confirmStatus, } = opts; const offset = (page - 1) * limit; @@ -45,6 +46,10 @@ export class InvoiceRepository extends EntityRepository { where.enableTax = enableTax; } + if (confirmStatus) { + where.confirmStatus = confirmStatus; + } + if (from || to) { where.createdAt = {}; if (from) {