diff --git a/database/migrations/.snapshot-negareh.json b/database/migrations/.snapshot-negareh.json index 73e8df8..5e7de2d 100644 --- a/database/migrations/.snapshot-negareh.json +++ b/database/migrations/.snapshot-negareh.json @@ -2850,6 +2850,15 @@ "nullable": true, "mappedType": "integer" }, + "discount_percent": { + "name": "discount_percent", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "integer" + }, "total": { "name": "total", "type": "int", @@ -3048,9 +3057,9 @@ }, "order_number": { "name": "order_number", - "type": "int", - "unsigned": false, - "autoincrement": false, + "type": "serial", + "unsigned": true, + "autoincrement": true, "primary": false, "nullable": false, "mappedType": "integer" @@ -3530,7 +3539,10 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": [], + "enumItems": [ + "withdraw", + "deposit" + ], "mappedType": "enum" } }, @@ -3854,4 +3866,4 @@ } ], "nativeEnums": {} -} +} \ No newline at end of file diff --git a/database/migrations/Migration20260404082727.ts b/database/migrations/Migration20260404082727.ts deleted file mode 100644 index 830c639..0000000 --- a/database/migrations/Migration20260404082727.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260404082727 extends Migration { - - override async up(): Promise { - this.addSql(`alter table "credit_transactions" drop constraint if exists "credit_transactions_type_check";`); - - this.addSql(`alter table "credit_transactions" add constraint "credit_transactions_type_check" check("type" in (''));`); - } - - override async down(): Promise { - this.addSql(`alter table "credit_transactions" drop constraint if exists "credit_transactions_type_check";`); - - this.addSql(`alter table "credit_transactions" add constraint "credit_transactions_type_check" check("type" in (''));`); - } - -} diff --git a/database/migrations/Migration20260404112450.ts b/database/migrations/Migration20260404112450.ts deleted file mode 100644 index 37cae86..0000000 --- a/database/migrations/Migration20260404112450.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260404112450 extends Migration { - - override async up(): Promise { - this.addSql(`alter table "invoices" alter column "invoice_number" type int using ("invoice_number"::int);`); - this.addSql(`alter table "invoices" alter column "discount" type int using ("discount"::int);`); - this.addSql(`alter table "invoices" alter column "discount" set not null;`); - this.addSql(`alter table "invoices" alter column "sub_total" type int using ("sub_total"::int);`); - this.addSql(`alter table "invoices" alter column "sub_total" set not null;`); - this.addSql(`alter table "invoices" alter column "tax_amount" type int using ("tax_amount"::int);`); - this.addSql(`alter table "invoices" alter column "tax_amount" set not null;`); - this.addSql(`alter table "invoices" alter column "total" type int using ("total"::int);`); - this.addSql(`alter table "invoices" alter column "total" set not null;`); - this.addSql(`alter table "invoices" alter column "balance" drop default;`); - this.addSql(`alter table "invoices" alter column "balance" type int using ("balance"::int);`); - this.addSql(`alter table "invoices" alter column "invoice_number" drop default;`); - this.addSql(`alter table "invoices" add constraint "invoices_invoice_number_unique" unique ("invoice_number");`); - } - - override async down(): Promise { - this.addSql(`alter table "invoices" drop constraint "invoices_invoice_number_unique";`); - - this.addSql(`alter table "invoices" alter column "invoice_number" type int using ("invoice_number"::int);`); - this.addSql(`alter table "invoices" alter column "discount" type int using ("discount"::int);`); - this.addSql(`alter table "invoices" alter column "discount" drop not null;`); - this.addSql(`alter table "invoices" alter column "sub_total" type int using ("sub_total"::int);`); - this.addSql(`alter table "invoices" alter column "sub_total" drop not null;`); - this.addSql(`alter table "invoices" alter column "tax_amount" type int using ("tax_amount"::int);`); - this.addSql(`alter table "invoices" alter column "tax_amount" drop not null;`); - this.addSql(`alter table "invoices" alter column "total" type int using ("total"::int);`); - this.addSql(`alter table "invoices" alter column "total" drop not null;`); - this.addSql(`alter table "invoices" alter column "balance" type int using ("balance"::int);`); - this.addSql(`alter table "invoices" alter column "balance" set default 0;`); - this.addSql(`create sequence if not exists "invoices_invoice_number_seq";`); - this.addSql(`select setval('invoices_invoice_number_seq', (select max("invoice_number") from "invoices"));`); - this.addSql(`alter table "invoices" alter column "invoice_number" set default nextval('invoices_invoice_number_seq');`); - } - -} diff --git a/database/migrations/Migration20260405052112.ts b/database/migrations/Migration20260405052112.ts deleted file mode 100644 index 5e76504..0000000 --- a/database/migrations/Migration20260405052112.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260405052112 extends Migration { - - override async up(): Promise { - this.addSql(`alter table "orders" alter column "order_number" type int using ("order_number"::int);`); - this.addSql(`alter table "orders" alter column "order_number" drop default;`); - this.addSql(`alter table "orders" add constraint "orders_order_number_unique" unique ("order_number");`); - } - - override async down(): Promise { - this.addSql(`alter table "orders" drop constraint "orders_order_number_unique";`); - - this.addSql(`alter table "orders" alter column "order_number" type int using ("order_number"::int);`); - this.addSql(`create sequence if not exists "orders_order_number_seq";`); - this.addSql(`select setval('orders_order_number_seq', (select max("order_number") from "orders"));`); - this.addSql(`alter table "orders" alter column "order_number" set default nextval('orders_order_number_seq');`); - } - -} diff --git a/database/migrations/Migration20260409060704.ts b/database/migrations/Migration20260409060704.ts deleted file mode 100644 index 5c3bbaf..0000000 --- a/database/migrations/Migration20260409060704.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260409060704 extends Migration { - - override async up(): Promise { - this.addSql(`create index "field_entity_id_index" on "field" ("entity_id");`); - - this.addSql(`create index "field_option_field_id_index" on "field_option" ("field_id");`); - - this.addSql(`create index "admins_phone_index" on "admins" ("phone");`); - - this.addSql(`create index "role_permissions_role_id_index" on "role_permissions" ("role_id");`); - - this.addSql(`create index "users_phone_index" on "users" ("phone");`); - - this.addSql(`create index "tickets_user_id_index" on "tickets" ("user_id");`); - - this.addSql(`create index "request_items_request_id_index" on "request_items" ("request_id");`); - - this.addSql(`create index "notifications_user_id_index" on "notifications" ("user_id");`); - - this.addSql(`create index "invoice_items_invoice_id_index" on "invoice_items" ("invoice_id");`); - - this.addSql(`create index "orders_status_index" on "orders" ("status");`); - this.addSql(`create index "orders_designer_id_index" on "orders" ("designer_id");`); - - this.addSql(`create index "chat_user_id_index" on "chat" ("user_id");`); - } - - override async down(): Promise { - this.addSql(`drop index "field_entity_id_index";`); - - this.addSql(`drop index "field_option_field_id_index";`); - - this.addSql(`drop index "admins_phone_index";`); - - this.addSql(`drop index "role_permissions_role_id_index";`); - - this.addSql(`drop index "users_phone_index";`); - - this.addSql(`drop index "tickets_user_id_index";`); - - this.addSql(`drop index "request_items_request_id_index";`); - - this.addSql(`drop index "notifications_user_id_index";`); - - this.addSql(`drop index "invoice_items_invoice_id_index";`); - - this.addSql(`drop index "orders_status_index";`); - this.addSql(`drop index "orders_designer_id_index";`); - - this.addSql(`drop index "chat_user_id_index";`); - } - -} diff --git a/database/migrations/Migration20260422123253.ts b/database/migrations/Migration20260422123253.ts deleted file mode 100644 index 3bb6006..0000000 --- a/database/migrations/Migration20260422123253.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260422123253 extends Migration { - - override async up(): Promise { - // Create sequence for invoice_number - this.addSql(`CREATE SEQUENCE IF NOT EXISTS invoice_number_seq START 1;`); - - // Set the sequence to start from the max existing invoice_number + 1 - this.addSql(`SELECT setval('invoice_number_seq', COALESCE((SELECT MAX(invoice_number) FROM invoices), 0) + 1, false);`); - - // Set default value for invoice_number to use the sequence - this.addSql(`ALTER TABLE invoices ALTER COLUMN invoice_number SET DEFAULT nextval('invoice_number_seq');`); - } - - override async down(): Promise { - // Remove default from invoice_number - this.addSql(`ALTER TABLE invoices ALTER COLUMN invoice_number DROP DEFAULT;`); - - // Drop the sequence - this.addSql(`DROP SEQUENCE IF EXISTS invoice_number_seq;`); - } - -} diff --git a/database/migrations/Migration20260516121012.ts b/database/migrations/Migration20260516121012.ts deleted file mode 100644 index 77716ff..0000000 --- a/database/migrations/Migration20260516121012.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260516121012 extends Migration { - - override async up(): Promise { - this.addSql(` - DO $$ BEGIN - ALTER TABLE "invoice_items" - ADD COLUMN "sub_total" int GENERATED ALWAYS AS (COALESCE(unit_price, 0) * quantity) STORED NOT NULL, - ADD COLUMN "total" int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED NOT NULL; - EXCEPTION - WHEN duplicate_column THEN NULL; - END $$; - `); - } - - override async down(): Promise { - this.addSql(`alter table "invoice_items" drop column if exists "sub_total";`); - this.addSql(`alter table "invoice_items" drop column if exists "total";`); - } - -} diff --git a/database/migrations/Migration20260517190050.ts b/database/migrations/Migration20260517190050.ts deleted file mode 100644 index fe445a6..0000000 --- a/database/migrations/Migration20260517190050.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260517190050 extends Migration { - - override async up(): Promise { - this.addSql(`CREATE SEQUENCE IF NOT EXISTS invoice_number_seq;`); - this.addSql(`SELECT setval('invoice_number_seq', COALESCE((SELECT MAX(invoice_number) FROM invoices), 0) + 1, false);`); - this.addSql(`ALTER TABLE "invoices" ALTER COLUMN "invoice_number" SET DEFAULT nextval('invoice_number_seq');`); - } - - override async down(): Promise { - this.addSql(`ALTER TABLE "invoices" ALTER COLUMN "invoice_number" DROP DEFAULT;`); - } - -} diff --git a/database/migrations/Migration20260518133000.ts b/database/migrations/Migration20260518133000.ts deleted file mode 100644 index 7015a9b..0000000 --- a/database/migrations/Migration20260518133000.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260518133000 extends Migration { - - override async up(): Promise { - this.addSql(`create sequence if not exists "orders_order_number_seq";`); - this.addSql(`select setval('orders_order_number_seq', coalesce((select max("order_number") from "orders"), 1), (select count(*) > 0 from "orders"));`); - this.addSql(`alter table "orders" alter column "order_number" set default nextval('orders_order_number_seq');`); - } - - override async down(): Promise { - this.addSql(`alter table "orders" alter column "order_number" drop default;`); - this.addSql(`drop sequence if exists "orders_order_number_seq";`); - } - -} diff --git a/database/migrations/Migration20260524120000.ts b/database/migrations/Migration20260524120000.ts deleted file mode 100644 index 8f8e1e6..0000000 --- a/database/migrations/Migration20260524120000.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20260524120000 extends Migration { - - override async up(): Promise { - this.addSql(` - CREATE OR REPLACE FUNCTION recalc_invoice_totals() - RETURNS TRIGGER AS $$ - DECLARE - v_invoice_id char(26); - v_subtotal int; - v_discount int; - v_tax int; - v_total int; - v_paid int; - v_enable_tax boolean; - BEGIN - - v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id); - - SELECT - COALESCE(SUM(sub_total),0), - COALESCE(SUM(discount),0) - INTO - v_subtotal, - v_discount - FROM invoice_items - WHERE invoice_id = v_invoice_id - AND deleted_at IS NULL; - - SELECT enable_tax - INTO v_enable_tax - FROM invoices - WHERE id = v_invoice_id; - - IF v_enable_tax THEN - v_tax := (v_subtotal - v_discount) * 0.10; - ELSE - v_tax := 0; - END IF; - - v_total := (v_subtotal - v_discount) + v_tax; - - SELECT COALESCE(SUM(amount),0)::int - INTO v_paid - FROM payments - WHERE invoice_id = v_invoice_id - AND status = 'paid' - AND deleted_at IS NULL; - - UPDATE invoices - SET - sub_total = v_subtotal, - discount = v_discount, - tax_amount = v_tax, - total = v_total, - paid_amount = v_paid, - balance = v_total - v_paid - WHERE id = v_invoice_id; - - RETURN NULL; - END; - $$ LANGUAGE plpgsql; - `); - - this.addSql(` - CREATE OR REPLACE FUNCTION recalc_invoice_payments() - RETURNS TRIGGER AS $$ - DECLARE - v_invoice_id char(26); - v_paid int; - v_total int; - BEGIN - - v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id); - - SELECT COALESCE(SUM(amount),0)::int - INTO v_paid - FROM payments - WHERE invoice_id = v_invoice_id - AND status = 'paid' - AND deleted_at IS NULL; - - SELECT total - INTO v_total - FROM invoices - WHERE id = v_invoice_id; - - UPDATE invoices - SET - paid_amount = v_paid, - balance = v_total - v_paid - WHERE id = v_invoice_id; - - RETURN NULL; - END; - $$ LANGUAGE plpgsql; - `); - - this.addSql(` - UPDATE invoices i - SET - paid_amount = COALESCE(p.paid_sum, 0), - balance = i.total - COALESCE(p.paid_sum, 0) - FROM ( - SELECT invoice_id, SUM(amount)::int AS paid_sum - FROM payments - WHERE status = 'paid' - AND deleted_at IS NULL - GROUP BY invoice_id - ) p - WHERE i.id = p.invoice_id; - `); - } - - override async down(): Promise { - // Restore previous (broken) function bodies from Migration20260404083020 - this.addSql(` - CREATE OR REPLACE FUNCTION recalc_invoice_totals() - RETURNS TRIGGER AS $$ - DECLARE - v_invoice_id char(26); - v_subtotal int; - v_discount int; - v_tax int; - v_total int; - v_paid int; - v_enable_tax boolean; - BEGIN - v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id); - SELECT COALESCE(SUM(sub_total),0), COALESCE(SUM(discount),0) - INTO v_subtotal, v_discount - FROM invoice_items - WHERE invoice_id = v_invoice_id AND deleted_at IS NULL; - SELECT enable_tax INTO v_enable_tax FROM invoices WHERE id = v_invoice_id; - IF v_enable_tax THEN - v_tax := (v_subtotal - v_discount) * 0.10; - ELSE - v_tax := 0; - END IF; - v_total := (v_subtotal - v_discount) + v_tax; - SELECT COALESCE(SUM(amount),0) INTO v_paid - FROM payments - WHERE invoice_id = v_invoice_id AND status = 'SUCCESS'; - UPDATE invoices SET sub_total = v_subtotal, discount = v_discount, tax_amount = v_tax, - total = v_total, paid_amount = v_paid, balance = v_total - v_paid - WHERE id = v_invoice_id; - RETURN NULL; - END; - $$ LANGUAGE plpgsql; - `); - - this.addSql(` - CREATE OR REPLACE FUNCTION recalc_invoice_payments() - RETURNS TRIGGER AS $$ - DECLARE - v_invoice_id char(26); - v_paid int; - v_total int; - BEGIN - v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id); - SELECT COALESCE(SUM(amount),0) INTO v_paid - FROM payments WHERE invoice_id = v_invoice_id AND status = 'SUCCESS'; - SELECT total INTO v_total FROM invoices WHERE id = v_invoice_id; - UPDATE invoices SET paid_amount = v_paid, balance = v_total - v_paid - WHERE id = v_invoice_id; - RETURN NULL; - END; - $$ LANGUAGE plpgsql; - `); - } - -} diff --git a/database/migrations/Migration20260404083020.ts b/database/migrations/Migration20260526120100.ts similarity index 66% rename from database/migrations/Migration20260404083020.ts rename to database/migrations/Migration20260526120100.ts index 40a08ad..6be0a94 100644 --- a/database/migrations/Migration20260404083020.ts +++ b/database/migrations/Migration20260526120100.ts @@ -1,10 +1,9 @@ import { Migration } from '@mikro-orm/migrations'; -export class Migration20260404083020 extends Migration { +export class Migration20260526120100 extends Migration { - override async up(): Promise { - //recalc_invoice_totals - this.addSql(` + override async up(): Promise { + this.addSql(` CREATE OR REPLACE FUNCTION recalc_invoice_totals() RETURNS TRIGGER AS $$ DECLARE @@ -42,7 +41,7 @@ export class Migration20260404083020 extends Migration { v_total := (v_subtotal - v_discount) + v_tax; - SELECT COALESCE(SUM(amount),0) + SELECT COALESCE(SUM(amount),0)::int INTO v_paid FROM payments WHERE invoice_id = v_invoice_id @@ -64,17 +63,7 @@ export class Migration20260404083020 extends Migration { $$ LANGUAGE plpgsql; `); - // trigger - this.addSql(` - CREATE TRIGGER trg_invoice_items_recalc - AFTER INSERT OR UPDATE OR DELETE - ON invoice_items - FOR EACH ROW - EXECUTE FUNCTION recalc_invoice_totals(); - `); - - // recalc_invoice_payments - this.addSql(` + this.addSql(` CREATE OR REPLACE FUNCTION recalc_invoice_payments() RETURNS TRIGGER AS $$ DECLARE @@ -85,7 +74,7 @@ export class Migration20260404083020 extends Migration { v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id); - SELECT COALESCE(SUM(amount),0) + SELECT COALESCE(SUM(amount),0)::int INTO v_paid FROM payments WHERE invoice_id = v_invoice_id @@ -108,44 +97,28 @@ export class Migration20260404083020 extends Migration { $$ LANGUAGE plpgsql; `); - // trigger - this.addSql(` + this.addSql(` + CREATE TRIGGER trg_invoice_items_recalc + AFTER INSERT OR UPDATE OR DELETE + ON invoice_items + FOR EACH ROW + EXECUTE FUNCTION recalc_invoice_totals(); + `); + + this.addSql(` CREATE TRIGGER trg_payments_recalc AFTER INSERT OR UPDATE OR DELETE ON payments FOR EACH ROW EXECUTE FUNCTION recalc_invoice_payments(); `); + } - this.addSql(`CREATE SEQUENCE invoice_number_seq START 1;`); - - this.addSql(` - ALTER TABLE invoices - ALTER COLUMN invoice_number - SET DEFAULT nextval('invoice_number_seq'); - `); - - } - - override async down(): Promise { - - this.addSql(`DROP TRIGGER IF EXISTS trg_invoice_items_recalc ON invoice_items;`); - 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;`); - - - // remove default from invoice_number - this.addSql(` - ALTER TABLE invoices - ALTER COLUMN invoice_number - DROP DEFAULT; - `); - - // drop sequence - this.addSql(`DROP SEQUENCE IF EXISTS invoice_number_seq;`); - - } + override async down(): Promise { + this.addSql(`DROP TRIGGER IF EXISTS trg_invoice_items_recalc ON invoice_items;`); + 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();`); + } } diff --git a/package.json b/package.json index 5cf6407..a9801b0 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ "db:create": "npx mikro-orm schema:create --run --config ./src/config/mikro-orm.config.dev.ts", "db:update": "npx mikro-orm schema:update --run --config ./src/config/mikro-orm.config.dev.ts ", "db:drop": "npx mikro-orm schema:drop --run --config ./src/config/mikro-orm.config.dev.ts", - "db:seed": "npx mikro-orm seeder:run --config ./src/config/mikro-orm.config.dev.ts", - "db:reset": "npm run db:drop && npm run db:create && npm run db:seed", "db:refresh": "npm run db:drop && npm run db:create ", + "db:reset": "npm run db:drop && npm run db:create && npm run db:seed", + "db:seed": "npx mikro-orm seeder:run --config ./src/config/mikro-orm.config.dev.ts", "build": "nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start dist/main", diff --git a/src/modules/invoice/dto/create-invoice.dto.ts b/src/modules/invoice/dto/create-invoice.dto.ts index 0b2e502..eeff00e 100644 --- a/src/modules/invoice/dto/create-invoice.dto.ts +++ b/src/modules/invoice/dto/create-invoice.dto.ts @@ -33,6 +33,12 @@ export class CreateInvoiceItemDto { @ApiPropertyOptional({ example: 0 }) discount?: number; + @IsOptional() + @IsInt() + @Min(0) + @ApiPropertyOptional({ example: 10, description: 'Discount percentage (0–100)' }) + discountPercent?: number; + @IsOptional() @IsString() @ApiPropertyOptional() diff --git a/src/modules/invoice/entities/invoice-item.entity.ts b/src/modules/invoice/entities/invoice-item.entity.ts index d6623ec..d7ea88b 100644 --- a/src/modules/invoice/entities/invoice-item.entity.ts +++ b/src/modules/invoice/entities/invoice-item.entity.ts @@ -8,7 +8,7 @@ import { Order } from 'src/modules/order/entities/order.entity'; @Entity({ tableName: 'invoice_items' }) @Index({ properties: ['invoice'] }) export class InvoiceItem extends BaseEntity { - [OptionalProps]?: 'createdAt' | 'deletedAt' | 'discount' | 'description' | 'confirmedAt'; + [OptionalProps]?: 'createdAt' | 'deletedAt' | 'discount' | 'discountPercent' | 'description' | 'confirmedAt'; @ManyToOne(() => Invoice) invoice!: Invoice; @@ -38,6 +38,9 @@ export class InvoiceItem extends BaseEntity { }) discount?: number; + @Property({ type: 'int', nullable: true, fieldName: 'discount_percent' }) + discountPercent?: number; + @Property({ type: 'int', columnType: 'int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED', diff --git a/src/modules/invoice/invoice.service.ts b/src/modules/invoice/invoice.service.ts index 0b63031..34a54f1 100644 --- a/src/modules/invoice/invoice.service.ts +++ b/src/modules/invoice/invoice.service.ts @@ -1,7 +1,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { CreateInvoiceDto } from './dto/create-invoice.dto'; +import { CreateInvoiceDto, CreateInvoiceItemDto } from './dto/create-invoice.dto'; import { UpdateInvoiceDto } from './dto/update-invoice.dto'; import { FindInvoicesDto } from './dto/find-invoices.dto'; import { Invoice } from './entities/invoice.entity'; @@ -15,6 +15,11 @@ import { RequestStatusEnum } from '../request/enum/request'; const TAX_RATE = 0.1; +type ResolvedItemDiscount = { + discount?: number; + discountPercent?: number; +}; + @Injectable() export class InvoiceService { constructor( @@ -24,6 +29,34 @@ export class InvoiceService { private readonly eventEmitter: EventEmitter2, ) { } + /** + * When both discount and discountPercent are provided, discountPercent wins + * and discount is derived from the line subtotal. + */ + private resolveItemDiscount( + unitPrice: number, + quantity: number, + item: Pick, + ): ResolvedItemDiscount { + const subTotal = unitPrice * quantity; + + if (item.discountPercent != null) { + const discount = Math.round((subTotal * item.discountPercent) / 100); + return { + discount: discount > 0 ? discount : undefined, + discountPercent: item.discountPercent, + }; + } + + if (item.discount != null) { + return { + discount: item.discount > 0 ? item.discount : undefined, + discountPercent: undefined, + }; + } + + return {}; + } async create(dto: CreateInvoiceDto) { let user: User; @@ -75,17 +108,19 @@ export class InvoiceService { for (const item of dto.items) { const product = await this.productService.findOneOrFail(item.productId); - const unitPrice = item.unitPrice; - const itemDiscount = item.discount ?? 0; - // const itemTotal = Math.max(0, unitPrice * item.quantity - itemDiscount); - // subTotal += itemTotal; + const { discount, discountPercent } = this.resolveItemDiscount( + item.unitPrice, + item.quantity, + item, + ); const invoiceItem = em.create(InvoiceItem, { invoice, product, quantity: item.quantity, - unitPrice: unitPrice, - discount: itemDiscount || undefined, + unitPrice: item.unitPrice, + discount, + discountPercent, description: item.description, }); invoice.items.add(invoiceItem); @@ -219,7 +254,18 @@ export class InvoiceService { if (item.quantity !== undefined) existingItem.quantity = item.quantity; if (item.unitPrice !== undefined) existingItem.unitPrice = item.unitPrice; - if (item.discount !== undefined) existingItem.discount = item.discount; + if ( + item.discount !== undefined || + item.discountPercent !== undefined + ) { + const { discount, discountPercent } = this.resolveItemDiscount( + item.unitPrice ?? existingItem.unitPrice, + item.quantity ?? existingItem.quantity, + item, + ); + existingItem.discount = discount; + existingItem.discountPercent = discountPercent; + } if (item.description !== undefined) existingItem.description = item.description; } else { @@ -233,13 +279,18 @@ export class InvoiceService { ); } const product = await this.productService.findOneOrFail(item.productId); - // const itemDiscount = item.discount ?? 0; + const { discount, discountPercent } = this.resolveItemDiscount( + item.unitPrice, + item.quantity, + item, + ); const invoiceItem = this.em.create(InvoiceItem, { invoice, product, quantity: item.quantity, unitPrice: item.unitPrice, - // discount: itemDiscount || undefined,` + discount, + discountPercent, description: item.description, }); invoice.items.add(invoiceItem); diff --git a/src/modules/user/entities/credit-transaction.entity.ts b/src/modules/user/entities/credit-transaction.entity.ts index d6b9159..71555d7 100644 --- a/src/modules/user/entities/credit-transaction.entity.ts +++ b/src/modules/user/entities/credit-transaction.entity.ts @@ -20,7 +20,7 @@ export class CreditTransaction extends BaseEntity { @Property({ type: 'decimal', precision: 10, scale: 0 }) balance!: number; - @Enum(() => CreditTransaction) + @Enum(() => CreditTransactionType) type!: CreditTransactionType; }