consolidate migrations
This commit is contained in:
@@ -2850,6 +2850,15 @@
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"mappedType": "integer"
|
"mappedType": "integer"
|
||||||
},
|
},
|
||||||
|
"discount_percent": {
|
||||||
|
"name": "discount_percent",
|
||||||
|
"type": "int",
|
||||||
|
"unsigned": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": true,
|
||||||
|
"mappedType": "integer"
|
||||||
|
},
|
||||||
"total": {
|
"total": {
|
||||||
"name": "total",
|
"name": "total",
|
||||||
"type": "int",
|
"type": "int",
|
||||||
@@ -3048,9 +3057,9 @@
|
|||||||
},
|
},
|
||||||
"order_number": {
|
"order_number": {
|
||||||
"name": "order_number",
|
"name": "order_number",
|
||||||
"type": "int",
|
"type": "serial",
|
||||||
"unsigned": false,
|
"unsigned": true,
|
||||||
"autoincrement": false,
|
"autoincrement": true,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"mappedType": "integer"
|
"mappedType": "integer"
|
||||||
@@ -3530,7 +3539,10 @@
|
|||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"enumItems": [],
|
"enumItems": [
|
||||||
|
"withdraw",
|
||||||
|
"deposit"
|
||||||
|
],
|
||||||
"mappedType": "enum"
|
"mappedType": "enum"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3854,4 +3866,4 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"nativeEnums": {}
|
"nativeEnums": {}
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260404082727 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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 (''));`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260404112450 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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');`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260405052112 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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');`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260409060704 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
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";`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260422123253 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
// 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<void> {
|
|
||||||
// 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;`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260516121012 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
this.addSql(`alter table "invoice_items" drop column if exists "sub_total";`);
|
|
||||||
this.addSql(`alter table "invoice_items" drop column if exists "total";`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260517190050 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
this.addSql(`ALTER TABLE "invoices" ALTER COLUMN "invoice_number" DROP DEFAULT;`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260518133000 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
this.addSql(`alter table "orders" alter column "order_number" drop default;`);
|
|
||||||
this.addSql(`drop sequence if exists "orders_order_number_seq";`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
|
||||||
|
|
||||||
export class Migration20260524120000 extends Migration {
|
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
|
||||||
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<void> {
|
|
||||||
// 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;
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+22
-49
@@ -1,10 +1,9 @@
|
|||||||
import { Migration } from '@mikro-orm/migrations';
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
export class Migration20260404083020 extends Migration {
|
export class Migration20260526120100 extends Migration {
|
||||||
|
|
||||||
override async up(): Promise<void> {
|
override async up(): Promise<void> {
|
||||||
//recalc_invoice_totals
|
this.addSql(`
|
||||||
this.addSql(`
|
|
||||||
CREATE OR REPLACE FUNCTION recalc_invoice_totals()
|
CREATE OR REPLACE FUNCTION recalc_invoice_totals()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
DECLARE
|
DECLARE
|
||||||
@@ -42,7 +41,7 @@ export class Migration20260404083020 extends Migration {
|
|||||||
|
|
||||||
v_total := (v_subtotal - v_discount) + v_tax;
|
v_total := (v_subtotal - v_discount) + v_tax;
|
||||||
|
|
||||||
SELECT COALESCE(SUM(amount),0)
|
SELECT COALESCE(SUM(amount),0)::int
|
||||||
INTO v_paid
|
INTO v_paid
|
||||||
FROM payments
|
FROM payments
|
||||||
WHERE invoice_id = v_invoice_id
|
WHERE invoice_id = v_invoice_id
|
||||||
@@ -64,17 +63,7 @@ export class Migration20260404083020 extends Migration {
|
|||||||
$$ LANGUAGE plpgsql;
|
$$ 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();
|
|
||||||
`);
|
|
||||||
|
|
||||||
// recalc_invoice_payments
|
|
||||||
this.addSql(`
|
|
||||||
CREATE OR REPLACE FUNCTION recalc_invoice_payments()
|
CREATE OR REPLACE FUNCTION recalc_invoice_payments()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
DECLARE
|
DECLARE
|
||||||
@@ -85,7 +74,7 @@ export class Migration20260404083020 extends Migration {
|
|||||||
|
|
||||||
v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id);
|
v_invoice_id := COALESCE(NEW.invoice_id, OLD.invoice_id);
|
||||||
|
|
||||||
SELECT COALESCE(SUM(amount),0)
|
SELECT COALESCE(SUM(amount),0)::int
|
||||||
INTO v_paid
|
INTO v_paid
|
||||||
FROM payments
|
FROM payments
|
||||||
WHERE invoice_id = v_invoice_id
|
WHERE invoice_id = v_invoice_id
|
||||||
@@ -108,44 +97,28 @@ export class Migration20260404083020 extends Migration {
|
|||||||
$$ LANGUAGE plpgsql;
|
$$ 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
|
CREATE TRIGGER trg_payments_recalc
|
||||||
AFTER INSERT OR UPDATE OR DELETE
|
AFTER INSERT OR UPDATE OR DELETE
|
||||||
ON payments
|
ON payments
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION recalc_invoice_payments();
|
EXECUTE FUNCTION recalc_invoice_payments();
|
||||||
`);
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
this.addSql(`CREATE SEQUENCE invoice_number_seq START 1;`);
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`DROP TRIGGER IF EXISTS trg_invoice_items_recalc ON invoice_items;`);
|
||||||
this.addSql(`
|
this.addSql(`DROP TRIGGER IF EXISTS trg_payments_recalc ON payments;`);
|
||||||
ALTER TABLE invoices
|
this.addSql(`DROP FUNCTION IF EXISTS recalc_invoice_totals();`);
|
||||||
ALTER COLUMN invoice_number
|
this.addSql(`DROP FUNCTION IF EXISTS recalc_invoice_payments();`);
|
||||||
SET DEFAULT nextval('invoice_number_seq');
|
}
|
||||||
`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override async down(): Promise<void> {
|
|
||||||
|
|
||||||
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;`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-2
@@ -9,9 +9,9 @@
|
|||||||
"db:create": "npx mikro-orm schema:create --run --config ./src/config/mikro-orm.config.dev.ts",
|
"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: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: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: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",
|
"build": "nest build",
|
||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start dist/main",
|
"start": "nest start dist/main",
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export class CreateInvoiceItemDto {
|
|||||||
@ApiPropertyOptional({ example: 0 })
|
@ApiPropertyOptional({ example: 0 })
|
||||||
discount?: number;
|
discount?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(0)
|
||||||
|
@ApiPropertyOptional({ example: 10, description: 'Discount percentage (0–100)' })
|
||||||
|
discountPercent?: number;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Order } from 'src/modules/order/entities/order.entity';
|
|||||||
@Entity({ tableName: 'invoice_items' })
|
@Entity({ tableName: 'invoice_items' })
|
||||||
@Index({ properties: ['invoice'] })
|
@Index({ properties: ['invoice'] })
|
||||||
export class InvoiceItem extends BaseEntity {
|
export class InvoiceItem extends BaseEntity {
|
||||||
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'discount' | 'description' | 'confirmedAt';
|
[OptionalProps]?: 'createdAt' | 'deletedAt' | 'discount' | 'discountPercent' | 'description' | 'confirmedAt';
|
||||||
|
|
||||||
@ManyToOne(() => Invoice)
|
@ManyToOne(() => Invoice)
|
||||||
invoice!: Invoice;
|
invoice!: Invoice;
|
||||||
@@ -38,6 +38,9 @@ export class InvoiceItem extends BaseEntity {
|
|||||||
})
|
})
|
||||||
discount?: number;
|
discount?: number;
|
||||||
|
|
||||||
|
@Property({ type: 'int', nullable: true, fieldName: 'discount_percent' })
|
||||||
|
discountPercent?: number;
|
||||||
|
|
||||||
@Property({
|
@Property({
|
||||||
type: 'int',
|
type: 'int',
|
||||||
columnType: 'int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED',
|
columnType: 'int GENERATED ALWAYS AS ((COALESCE(unit_price, 0) * quantity) - COALESCE(discount, 0)) STORED',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||||
import { EntityManager } from '@mikro-orm/postgresql';
|
import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
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 { UpdateInvoiceDto } from './dto/update-invoice.dto';
|
||||||
import { FindInvoicesDto } from './dto/find-invoices.dto';
|
import { FindInvoicesDto } from './dto/find-invoices.dto';
|
||||||
import { Invoice } from './entities/invoice.entity';
|
import { Invoice } from './entities/invoice.entity';
|
||||||
@@ -15,6 +15,11 @@ import { RequestStatusEnum } from '../request/enum/request';
|
|||||||
|
|
||||||
const TAX_RATE = 0.1;
|
const TAX_RATE = 0.1;
|
||||||
|
|
||||||
|
type ResolvedItemDiscount = {
|
||||||
|
discount?: number;
|
||||||
|
discountPercent?: number;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InvoiceService {
|
export class InvoiceService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -24,6 +29,34 @@ export class InvoiceService {
|
|||||||
private readonly eventEmitter: EventEmitter2,
|
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<CreateInvoiceItemDto, 'discount' | 'discountPercent'>,
|
||||||
|
): 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) {
|
async create(dto: CreateInvoiceDto) {
|
||||||
let user: User;
|
let user: User;
|
||||||
@@ -75,17 +108,19 @@ export class InvoiceService {
|
|||||||
|
|
||||||
for (const item of dto.items) {
|
for (const item of dto.items) {
|
||||||
const product = await this.productService.findOneOrFail(item.productId);
|
const product = await this.productService.findOneOrFail(item.productId);
|
||||||
const unitPrice = item.unitPrice;
|
const { discount, discountPercent } = this.resolveItemDiscount(
|
||||||
const itemDiscount = item.discount ?? 0;
|
item.unitPrice,
|
||||||
// const itemTotal = Math.max(0, unitPrice * item.quantity - itemDiscount);
|
item.quantity,
|
||||||
// subTotal += itemTotal;
|
item,
|
||||||
|
);
|
||||||
|
|
||||||
const invoiceItem = em.create(InvoiceItem, {
|
const invoiceItem = em.create(InvoiceItem, {
|
||||||
invoice,
|
invoice,
|
||||||
product,
|
product,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
unitPrice: unitPrice,
|
unitPrice: item.unitPrice,
|
||||||
discount: itemDiscount || undefined,
|
discount,
|
||||||
|
discountPercent,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
});
|
});
|
||||||
invoice.items.add(invoiceItem);
|
invoice.items.add(invoiceItem);
|
||||||
@@ -219,7 +254,18 @@ export class InvoiceService {
|
|||||||
if (item.quantity !== undefined) existingItem.quantity = item.quantity;
|
if (item.quantity !== undefined) existingItem.quantity = item.quantity;
|
||||||
if (item.unitPrice !== undefined)
|
if (item.unitPrice !== undefined)
|
||||||
existingItem.unitPrice = item.unitPrice;
|
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)
|
if (item.description !== undefined)
|
||||||
existingItem.description = item.description;
|
existingItem.description = item.description;
|
||||||
} else {
|
} else {
|
||||||
@@ -233,13 +279,18 @@ export class InvoiceService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const product = await this.productService.findOneOrFail(item.productId);
|
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, {
|
const invoiceItem = this.em.create(InvoiceItem, {
|
||||||
invoice,
|
invoice,
|
||||||
product,
|
product,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
unitPrice: item.unitPrice,
|
unitPrice: item.unitPrice,
|
||||||
// discount: itemDiscount || undefined,`
|
discount,
|
||||||
|
discountPercent,
|
||||||
description: item.description,
|
description: item.description,
|
||||||
});
|
});
|
||||||
invoice.items.add(invoiceItem);
|
invoice.items.add(invoiceItem);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class CreditTransaction extends BaseEntity {
|
|||||||
@Property({ type: 'decimal', precision: 10, scale: 0 })
|
@Property({ type: 'decimal', precision: 10, scale: 0 })
|
||||||
balance!: number;
|
balance!: number;
|
||||||
|
|
||||||
@Enum(() => CreditTransaction)
|
@Enum(() => CreditTransactionType)
|
||||||
type!: CreditTransactionType;
|
type!: CreditTransactionType;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user