From e991215f5a52771cf00eda72ac17675439f7be32 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 14 Jul 2026 21:04:07 +0330 Subject: [PATCH] update order status --- database/migrations/.snapshot-dmenu.json | 535 +++++++++++++++++- ...gration20260714170013_changeOrderStatus.ts | 81 +++ .../orders/interface/order.interface.ts | 3 +- .../orders/listeners/order.listeners.ts | 3 +- .../orders/providers/orders.service.ts | 22 +- .../orders/repositories/order.repository.ts | 4 +- .../payments/services/payments.service.ts | 12 +- .../users/crone/user-restuarant.crone.ts | 2 +- 8 files changed, 608 insertions(+), 54 deletions(-) create mode 100644 database/migrations/Migration20260714170013_changeOrderStatus.ts diff --git a/database/migrations/.snapshot-dmenu.json b/database/migrations/.snapshot-dmenu.json index 13b8606..9bdaade 100644 --- a/database/migrations/.snapshot-dmenu.json +++ b/database/migrations/.snapshot-dmenu.json @@ -961,18 +961,6 @@ "default": "0", "mappedType": "decimal" }, - "credit": { - "name": "credit", - "type": "numeric(10,0)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "precision": 10, - "scale": 0, - "default": "0", - "mappedType": "decimal" - }, "domain": { "name": "domain", "type": "varchar(255)", @@ -2917,6 +2905,263 @@ }, "nativeEnums": {} }, + { + "columns": { + "id": { + "name": "id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "admin_id": { + "name": "admin_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "opened_at": { + "name": "opened_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "closed_at": { + "name": "closed_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "opening_amount": { + "name": "opening_amount", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "precision": 10, + "scale": 0, + "default": "0", + "mappedType": "decimal" + }, + "expected_amount": { + "name": "expected_amount", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "counted_amount": { + "name": "counted_amount", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "difference_amount": { + "name": "difference_amount", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "orders": { + "name": "orders", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "0", + "mappedType": "integer" + }, + "status": { + "name": "status", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'open'", + "enumItems": [ + "open", + "closed" + ], + "mappedType": "enum" + }, + "notes": { + "name": "notes", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + } + }, + "name": "cash_shifts", + "schema": "public", + "indexes": [ + { + "keyName": "cash_shifts_created_at_index", + "columnNames": [ + "created_at" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "cash_shifts_deleted_at_index", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "cash_shifts_admin_id_index", + "columnNames": [ + "admin_id" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "cash_shifts_restaurant_id_status_index", + "columnNames": [ + "restaurant_id", + "status" + ], + "composite": true, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "cash_shifts_restaurant_id_index", + "columnNames": [ + "restaurant_id" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "cash_shifts_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "cash_shifts_admin_id_foreign": { + "constraintName": "cash_shifts_admin_id_foreign", + "columnNames": [ + "admin_id" + ], + "localTableName": "public.cash_shifts", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.admins", + "updateRule": "cascade" + }, + "cash_shifts_restaurant_id_foreign": { + "constraintName": "cash_shifts_restaurant_id_foreign", + "columnNames": [ + "restaurant_id" + ], + "localTableName": "public.cash_shifts", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.restaurants", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, { "columns": { "id": { @@ -2981,16 +3226,6 @@ "length": 255, "mappedType": "string" }, - "groups": { - "name": "groups", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "'[]'", - "mappedType": "json" - }, "sent_type": { "name": "sent_type", "type": "text", @@ -3140,6 +3375,159 @@ }, "nativeEnums": {} }, + { + "columns": { + "id": { + "name": "id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "restaurant_id": { + "name": "restaurant_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "amount": { + "name": "amount", + "type": "numeric(10,0)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "precision": 10, + "scale": 0, + "mappedType": "decimal" + }, + "invoice_id": { + "name": "invoice_id", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 255, + "mappedType": "string" + }, + "status": { + "name": "status", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'pending'", + "enumItems": [ + "pending", + "paid", + "cancelled", + "failed" + ], + "mappedType": "enum" + } + }, + "name": "restaurant_credit_requests", + "schema": "public", + "indexes": [ + { + "keyName": "restaurant_credit_requests_created_at_index", + "columnNames": [ + "created_at" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "restaurant_credit_requests_deleted_at_index", + "columnNames": [ + "deleted_at" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "restaurant_credit_requests_restaurant_id_index", + "columnNames": [ + "restaurant_id" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, + { + "keyName": "restaurant_credit_requests_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "restaurant_credit_requests_restaurant_id_foreign": { + "constraintName": "restaurant_credit_requests_restaurant_id_foreign", + "columnNames": [ + "restaurant_id" + ], + "localTableName": "public.restaurant_credit_requests", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.restaurants", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, { "columns": { "id": { @@ -4734,6 +5122,16 @@ "length": 26, "mappedType": "character" }, + "cash_shift_id": { + "name": "cash_shift_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 26, + "mappedType": "character" + }, "delivery_method_id": { "name": "delivery_method_id", "type": "char(26)", @@ -4940,8 +5338,7 @@ "primary": false, "nullable": false, "enumItems": [ - "pendingPayment", - "paid", + "new", "preparing", "deliveredToWaiter", "deliveredToReceptionist", @@ -4984,6 +5381,16 @@ "primary": false, "unique": false }, + { + "keyName": "orders_cash_shift_id_index", + "columnNames": [ + "cash_shift_id" + ], + "composite": false, + "constraint": false, + "primary": false, + "unique": false + }, { "keyName": "orders_restaurant_id_order_number_index", "columnNames": [ @@ -5054,6 +5461,19 @@ "referencedTableName": "public.restaurants", "updateRule": "cascade" }, + "orders_cash_shift_id_foreign": { + "constraintName": "orders_cash_shift_id_foreign", + "columnNames": [ + "cash_shift_id" + ], + "localTableName": "public.orders", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.cash_shifts", + "deleteRule": "set null", + "updateRule": "cascade" + }, "orders_delivery_method_id_foreign": { "constraintName": "orders_delivery_method_id_foreign", "columnNames": [ @@ -6432,6 +6852,73 @@ }, "nativeEnums": {} }, + { + "columns": { + "campaign_id": { + "name": "campaign_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + }, + "user_group_id": { + "name": "user_group_id", + "type": "char(26)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 26, + "mappedType": "character" + } + }, + "name": "campaign_user_groups", + "schema": "public", + "indexes": [ + { + "keyName": "campaign_user_groups_pkey", + "columnNames": [ + "campaign_id", + "user_group_id" + ], + "composite": true, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "campaign_user_groups_campaign_id_foreign": { + "constraintName": "campaign_user_groups_campaign_id_foreign", + "columnNames": [ + "campaign_id" + ], + "localTableName": "public.campaign_user_groups", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.campaigns", + "updateRule": "cascade" + }, + "campaign_user_groups_user_group_id_foreign": { + "constraintName": "campaign_user_groups_user_group_id_foreign", + "columnNames": [ + "user_group_id" + ], + "localTableName": "public.campaign_user_groups", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.user_groups", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, { "columns": { "id": { diff --git a/database/migrations/Migration20260714170013_changeOrderStatus.ts b/database/migrations/Migration20260714170013_changeOrderStatus.ts new file mode 100644 index 0000000..a4df944 --- /dev/null +++ b/database/migrations/Migration20260714170013_changeOrderStatus.ts @@ -0,0 +1,81 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20260714170013_changeOrderStatus extends Migration { + + public async up(): Promise { + + // remove old constraint + this.addSql(` + ALTER TABLE orders + DROP CONSTRAINT IF EXISTS orders_status_check; + `); + + // migrate data + this.addSql(` + UPDATE orders + SET status='new' + WHERE status='pendingPayment'; + `); + + this.addSql(` + UPDATE orders + SET status='completed' + WHERE status='paid'; + `); + + // create new constraint + this.addSql(` + ALTER TABLE orders + ADD CONSTRAINT orders_status_check + CHECK ( + status IN ( + 'new', + 'preparing', + 'deliveredToWaiter', + 'deliveredToReceptionist', + 'shipped', + 'canceled', + 'completed' + ) + ); + `); + } + + public async down(): Promise { + + this.addSql(` + ALTER TABLE "orders" + DROP CONSTRAINT IF EXISTS "orders_status_check"; + `); + + this.addSql(` + UPDATE "orders" + SET "status" = 'pendingPayment' + WHERE "status" = 'new'; + `); + + this.addSql(` + UPDATE "orders" + SET "status" = 'paid' + WHERE "status" = 'completed'; + `); + + this.addSql(` + ALTER TABLE "orders" + ADD CONSTRAINT "orders_status_check" + CHECK ( + "status" IN ( + 'pendingPayment', + 'preparing', + 'deliveredToWaiter', + 'deliveredToReceptionist', + 'shipped', + 'paid', + 'canceled', + 'completed' + ) + ); + `); + } + +} diff --git a/src/modules/orders/interface/order.interface.ts b/src/modules/orders/interface/order.interface.ts index d2993dc..2637c25 100644 --- a/src/modules/orders/interface/order.interface.ts +++ b/src/modules/orders/interface/order.interface.ts @@ -17,8 +17,7 @@ export interface OrderCarAddress { } export enum OrderStatus { - PENDING_PAYMENT = 'pendingPayment', - PAID = 'paid', + NEW = 'new', PREPARING = 'preparing', DELIVERED_TO_WAITER = 'deliveredToWaiter', DELIVERED_TO_RECEPTIONIST = 'deliveredToReceptionist', diff --git a/src/modules/orders/listeners/order.listeners.ts b/src/modules/orders/listeners/order.listeners.ts index 4efb934..b009c71 100644 --- a/src/modules/orders/listeners/order.listeners.ts +++ b/src/modules/orders/listeners/order.listeners.ts @@ -35,8 +35,7 @@ export class OrderListeners { private getStatusFarsi(status: OrderStatus): string { const statusMap: Record = { - [OrderStatus.PENDING_PAYMENT]: 'در انتظار پرداخت', - [OrderStatus.PAID]: 'پرداخت شده', + [OrderStatus.NEW]: 'جدید', [OrderStatus.PREPARING]: 'در حال آماده‌سازی', [OrderStatus.DELIVERED_TO_RECEPTIONIST]: 'تحویل به پذیرش', [OrderStatus.DELIVERED_TO_WAITER]: 'تحویل به گارسون', diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index ce67788..ac80ee5 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -47,8 +47,7 @@ export class OrdersService { private readonly logger = new Logger(OrdersService.name); private static readonly STATUS_TRANSITIONS: Record = { - [OrderStatus.PENDING_PAYMENT]: [OrderStatus.PAID, OrderStatus.CANCELED, OrderStatus.PREPARING], - [OrderStatus.PAID]: [OrderStatus.PREPARING, OrderStatus.CANCELED], + [OrderStatus.NEW]: [ OrderStatus.CANCELED, OrderStatus.PREPARING], [OrderStatus.PREPARING]: [OrderStatus.DELIVERED_TO_RECEPTIONIST, OrderStatus.DELIVERED_TO_WAITER, OrderStatus.SHIPPED, OrderStatus.CANCELED], [OrderStatus.DELIVERED_TO_WAITER]: [OrderStatus.COMPLETED, OrderStatus.CANCELED], [OrderStatus.DELIVERED_TO_RECEPTIONIST]: [OrderStatus.COMPLETED, OrderStatus.CANCELED], @@ -92,8 +91,8 @@ export class OrdersService { description: cart.description, printInvoice: cart.printInvoice ?? false, tableNumber: cart.tableNumber, - status: OrderStatus.PENDING_PAYMENT, - history: [{ status: OrderStatus.PENDING_PAYMENT, changedAt: new Date() }], + status: OrderStatus.NEW, + history: [{ status: OrderStatus.NEW, changedAt: new Date() }], }); em.persist(order); @@ -206,8 +205,8 @@ export class OrdersService { description: dto.description, printInvoice: false, tableNumber: dto.tableNumber, - status: OrderStatus.PENDING_PAYMENT, - history: [{ status: OrderStatus.PENDING_PAYMENT, changedAt: new Date() }], + status: OrderStatus.NEW, + history: [{ status: OrderStatus.NEW, changedAt: new Date() }], }); em.persist(order); @@ -431,8 +430,8 @@ export class OrdersService { if (!OrdersService.STATUS_TRANSITIONS[from]?.includes(to)) return false; if (to === OrderStatus.CANCELED) { - // only allow orders with status of PENDING_PAYMENT and PAID are allowed to be canceled by user - if (ref === 'user' && ![OrderStatus.PENDING_PAYMENT, OrderStatus.PAID].includes(from)) { + // only allow orders with status of NEW are allowed to be canceled by user + if (ref === 'user' && ![OrderStatus.NEW].includes(from)) { return false; } else if (ref === 'admin') { return true; @@ -441,7 +440,7 @@ export class OrdersService { // only allow orders with status of PENDING_PAYMENT and payment // method of cash are allowed to move to CONFIRMED directly if ( - from == OrderStatus.PENDING_PAYMENT && + from == OrderStatus.NEW && to == OrderStatus.PREPARING && paymentMethod !== PaymentMethodEnum.Cash && paymentMethod !== PaymentMethodEnum.CreditCard @@ -475,10 +474,6 @@ export class OrdersService { return false; } - if (paymentMethod === PaymentMethodEnum.Online) { - if (to === OrderStatus.PREPARING && from !== OrderStatus.PAID) return false; - } - return true; } @@ -709,7 +704,6 @@ export class OrdersService { restaurant: { id: restId }, status: { $in: [ - OrderStatus.PAID, OrderStatus.PREPARING, OrderStatus.DELIVERED_TO_WAITER, OrderStatus.DELIVERED_TO_RECEPTIONIST, diff --git a/src/modules/orders/repositories/order.repository.ts b/src/modules/orders/repositories/order.repository.ts index 93fd2a8..79e1c0e 100644 --- a/src/modules/orders/repositories/order.repository.ts +++ b/src/modules/orders/repositories/order.repository.ts @@ -105,7 +105,7 @@ export class OrderRepository extends EntityRepository { where.$or = searchConditions; } - // Filter: Exclude orders with payment method Online and status pending_payment + // Filter: Exclude online orders that have no paid payment if (excludeOnlinePendingPayment) { const existingConditions = where.$and || []; where.$and = [ @@ -113,7 +113,7 @@ export class OrderRepository extends EntityRepository { { $or: [ { paymentMethod: { method: { $ne: PaymentMethodEnum.Online } } }, - { status: { $ne: OrderStatus.PENDING_PAYMENT } }, + { payments: { status: PaymentStatusEnum.Paid } }, ], }, ]; diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index 124fc38..55ad1c3 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -28,7 +28,7 @@ export class PaymentsService { const ctx = await this.loadAndValidateOrder(orderId); // Idempotency: avoid creating/charging again for already-paid orders - if (ctx.order.status === OrderStatus.PAID) { + if (ctx.order.payments.find(p => p.status === PaymentStatusEnum.Paid)) { return { paymentUrl: null }; } @@ -112,7 +112,7 @@ export class PaymentsService { const order = await em.findOne(Order, { id: ctx.order.id }, { populate: ['user', 'restaurant'] }); if (!order) throw new NotFoundException(OrderMessage.NOT_FOUND); if (!order.user) throw new NotFoundException(OrderMessage.USER_NOT_FOUND); - if (order.status === OrderStatus.PAID) { + if (order.payments.find(p => p.status === PaymentStatusEnum.Paid)) { return; } @@ -152,7 +152,6 @@ export class PaymentsService { payment.status = PaymentStatusEnum.Paid; payment.paidAt = new Date(); - order.status = OrderStatus.PAID; em.persist([ payment, order, newWalletTransaction]); await em.flush(); @@ -232,9 +231,6 @@ export class PaymentsService { } this.markPaid(payment, result.referenceId); - if (payment.order.status === OrderStatus.PENDING_PAYMENT) { - payment.order.status = OrderStatus.PAID; - } await em.flush(); return payment; @@ -275,9 +271,7 @@ export class PaymentsService { if (payment.status === PaymentStatusEnum.Paid) { throw new BadRequestException(PaymentMessage.PAYMENT_ALREADY_PAID); } - if (payment.order.status === OrderStatus.PENDING_PAYMENT) { - payment.order.status = OrderStatus.PAID; - } + payment.status = PaymentStatusEnum.Paid; payment.paidAt = new Date(); em.persist(payment); diff --git a/src/modules/users/crone/user-restuarant.crone.ts b/src/modules/users/crone/user-restuarant.crone.ts index d7a2639..1177fb9 100644 --- a/src/modules/users/crone/user-restuarant.crone.ts +++ b/src/modules/users/crone/user-restuarant.crone.ts @@ -5,7 +5,7 @@ import { EntityManager } from '@mikro-orm/postgresql'; import { OrderStatus } from '../../orders/interface/order.interface'; const COUNTED_ORDER_STATUSES = [ - OrderStatus.PAID, + OrderStatus.NEW, OrderStatus.PREPARING, OrderStatus.DELIVERED_TO_WAITER, OrderStatus.DELIVERED_TO_RECEPTIONIST,