remove unused
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,27 +0,0 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20260105085439 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table "point_transactions" drop constraint if exists "point_transactions_type_check";`);
|
||||
|
||||
this.addSql(`alter table "categories" add column "order" int null;`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" add constraint "point_transactions_type_check" check("type" in (''));`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`create table "user_wallets" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "restaurant_id" char(26) not null, "user_id" char(26) not null, "wallet" int4 not null default 0, "points" int4 not null default 0, constraint "user_wallets_pkey" primary key ("id"));`);
|
||||
this.addSql(`alter table "user_wallets" add constraint "unique_user_restaurant" unique ("user_id", "restaurant_id");`);
|
||||
this.addSql(`create index "user_wallets_created_at_index" on "user_wallets" ("created_at");`);
|
||||
this.addSql(`create index "user_wallets_deleted_at_index" on "user_wallets" ("deleted_at");`);
|
||||
this.addSql(`create index "user_wallets_restaurant_id_index" on "user_wallets" ("restaurant_id");`);
|
||||
this.addSql(`create index "user_wallets_user_id_index" on "user_wallets" ("user_id");`);
|
||||
this.addSql(`create index "user_wallets_user_id_restaurant_id_index" on "user_wallets" ("user_id", "restaurant_id");`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" drop constraint if exists "point_transactions_type_check";`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" add constraint "point_transactions_type_check" check("type" in (''));`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20260106054151 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_id" type varchar(255) using ("subscription_id"::varchar(255));`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_id" drop not null;`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_end_date" type timestamptz using ("subscription_end_date"::timestamptz);`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_end_date" drop not null;`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_start_date" type timestamptz using ("subscription_start_date"::timestamptz);`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_start_date" drop not null;`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_id" type varchar(255) using ("subscription_id"::varchar(255));`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_id" set not null;`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_end_date" type timestamptz using ("subscription_end_date"::timestamptz);`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_end_date" set not null;`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_start_date" type timestamptz using ("subscription_start_date"::timestamptz);`);
|
||||
this.addSql(`alter table "restaurants" alter column "subscription_start_date" set not null;`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20260106192946 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table "contacts" add column "restaurant_id" char(26) null;`);
|
||||
this.addSql(`alter table "contacts" add constraint "contacts_restaurant_id_foreign" foreign key ("restaurant_id") references "restaurants" ("id") on update cascade on delete set null;`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`alter table "contacts" drop constraint "contacts_restaurant_id_foreign";`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20260226063544 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table "deliveries" alter column "delivery_fee" type int using ("delivery_fee"::int);`);
|
||||
this.addSql(`alter table "deliveries" alter column "per_kilometer_fee" type int using ("per_kilometer_fee"::int);`);
|
||||
this.addSql(`alter table "deliveries" alter column "distance_based_min_cost" type int using ("distance_based_min_cost"::int);`);
|
||||
this.addSql(`alter table "deliveries" alter column "min_order_price" type int using ("min_order_price"::int);`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`alter table "deliveries" alter column "delivery_fee" type numeric(10,0) using ("delivery_fee"::numeric(10,0));`);
|
||||
this.addSql(`alter table "deliveries" alter column "per_kilometer_fee" type numeric(10,0) using ("per_kilometer_fee"::numeric(10,0));`);
|
||||
this.addSql(`alter table "deliveries" alter column "distance_based_min_cost" type numeric(10,0) using ("distance_based_min_cost"::numeric(10,0));`);
|
||||
this.addSql(`alter table "deliveries" alter column "min_order_price" type numeric(10,0) using ("min_order_price"::numeric(10,0));`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20260309112630 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`alter table "point_transactions" drop constraint if exists "point_transactions_type_check";`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" add constraint "point_transactions_type_check" check("type" in (''));`);
|
||||
}
|
||||
|
||||
override async down(): Promise<void> {
|
||||
this.addSql(`create table "icon_groups" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "name" varchar(255) not null, constraint "icon_groups_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "icon_groups_created_at_index" on "icon_groups" ("created_at");`);
|
||||
this.addSql(`create index "icon_groups_deleted_at_index" on "icon_groups" ("deleted_at");`);
|
||||
|
||||
this.addSql(`create table "icons" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "url" varchar(255) not null, "group_id" char(26) not null, constraint "icons_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "icons_created_at_index" on "icons" ("created_at");`);
|
||||
this.addSql(`create index "icons_deleted_at_index" on "icons" ("deleted_at");`);
|
||||
this.addSql(`create index "icons_group_id_index" on "icons" ("group_id");`);
|
||||
|
||||
this.addSql(`create table "inventory" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "food_id" char(26) null, "total_stock" int4 not null, "available_stock" int4 not null, constraint "inventory_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "inventory_created_at_index" on "inventory" ("created_at");`);
|
||||
this.addSql(`create index "inventory_deleted_at_index" on "inventory" ("deleted_at");`);
|
||||
this.addSql(`alter table "inventory" add constraint "inventory_food_id_unique" unique ("food_id");`);
|
||||
|
||||
this.addSql(`create table "order_items" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "order_id" char(26) not null, "food_id" char(26) not null, "quantity" int4 not null, "unit_price" numeric(10,0) not null, "discount" numeric(10,0) not null default 0, "total_price" numeric(10,0) not null, constraint "order_items_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "order_items_created_at_index" on "order_items" ("created_at");`);
|
||||
this.addSql(`create index "order_items_deleted_at_index" on "order_items" ("deleted_at");`);
|
||||
this.addSql(`create index "order_items_food_id_index" on "order_items" ("food_id");`);
|
||||
this.addSql(`create index "order_items_order_id_index" on "order_items" ("order_id");`);
|
||||
|
||||
this.addSql(`create table "orders" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "user_id" char(26) not null, "restaurant_id" char(26) not null, "delivery_method_id" char(26) not null, "user_address" jsonb null, "car_address" jsonb null, "payment_method_id" char(26) not null, "order_number" int4 null, "coupon_discount" numeric(10,0) not null default 0, "coupon_detail" jsonb null, "items_discount" numeric(10,0) not null default 0, "total_discount" numeric(10,0) not null default 0, "sub_total" numeric(10,0) not null, "tax" numeric(10,0) not null default 0, "delivery_fee" numeric(10,0) not null default 0, "total" numeric(10,0) not null, "total_items" int4 not null default 0, "description" text null, "table_number" varchar(255) null, "status" text check ("status" in ('pendingPayment', 'paid', 'preparing', 'deliveredToWaiter', 'deliveredToReceptionist', 'shipped', 'completed', 'canceled')) not null, "history" jsonb null, constraint "orders_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "orders_created_at_index" on "orders" ("created_at");`);
|
||||
this.addSql(`create index "orders_deleted_at_index" on "orders" ("deleted_at");`);
|
||||
this.addSql(`create index "orders_restaurant_id_order_number_index" on "orders" ("restaurant_id", "order_number");`);
|
||||
this.addSql(`alter table "orders" add constraint "orders_restaurant_id_order_number_unique" unique ("restaurant_id", "order_number");`);
|
||||
this.addSql(`create index "orders_restaurant_id_status_index" on "orders" ("restaurant_id", "status");`);
|
||||
this.addSql(`create index "orders_status_index" on "orders" ("status");`);
|
||||
this.addSql(`create index "orders_user_id_status_index" on "orders" ("user_id", "status");`);
|
||||
|
||||
this.addSql(`create table "pagers" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "cookie_id" varchar(255) not null, "restaurant_id" char(26) not null, "table_number" varchar(255) not null, "user_id" char(26) null, "staff_id" char(26) null, "message" varchar(255) null, "status" text check ("status" in ('pending', 'acknowledged', 'rejected', 'completed')) not null default 'pending', constraint "pagers_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "pagers_created_at_index" on "pagers" ("created_at");`);
|
||||
this.addSql(`create index "pagers_deleted_at_index" on "pagers" ("deleted_at");`);
|
||||
|
||||
this.addSql(`create table "payment_methods" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "restaurant_id" char(26) not null, "method" text check ("method" in ('Online', 'Cash', 'Wallet')) not null, "gateway" text check ("gateway" in ('zarinpal')) null, "description" varchar(255) null, "enabled" bool not null default true, "order" int4 not null default 0, "merchant_id" varchar(255) null, constraint "payment_methods_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "payment_methods_created_at_index" on "payment_methods" ("created_at");`);
|
||||
this.addSql(`create index "payment_methods_deleted_at_index" on "payment_methods" ("deleted_at");`);
|
||||
this.addSql(`create index "payment_methods_restaurant_id_enabled_index" on "payment_methods" ("restaurant_id", "enabled");`);
|
||||
this.addSql(`alter table "payment_methods" add constraint "payment_methods_restaurant_id_method_unique" unique ("restaurant_id", "method");`);
|
||||
|
||||
this.addSql(`create table "payments" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "order_id" char(26) not null, "amount" numeric(10,0) not null, "reference_id" varchar(255) null, "method" text check ("method" in ('Online', 'Cash', 'Wallet')) not null, "gateway" text check ("gateway" in ('zarinpal')) null, "transaction_id" varchar(255) null, "status" text check ("status" in ('pending', 'paid', 'failed', 'refunded')) not null, "card_pan" varchar(255) null, "verify_response" jsonb null, "paid_at" varchar(255) null, "failed_at" varchar(255) null, "description" varchar(255) null, constraint "payments_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "payments_created_at_index" on "payments" ("created_at");`);
|
||||
this.addSql(`create index "payments_deleted_at_index" on "payments" ("deleted_at");`);
|
||||
this.addSql(`create index "payments_order_id_index" on "payments" ("order_id");`);
|
||||
this.addSql(`alter table "payments" add constraint "payments_reference_id_unique" unique ("reference_id");`);
|
||||
|
||||
this.addSql(`create table "reviews" ("id" char(26) not null, "created_at" timestamptz(6) not null default now(), "updated_at" timestamptz(6) not null default now(), "deleted_at" timestamptz(6) null, "order_id" char(26) not null, "food_id" char(26) not null, "user_id" char(26) not null, "comment" text null, "rating" int4 not null default 0, "positive_points" jsonb null, "negative_points" jsonb null, "status" text check ("status" in ('pending', 'rejected', 'approved')) not null default 'pending', constraint "reviews_pkey" primary key ("id"));`);
|
||||
this.addSql(`create index "reviews_created_at_index" on "reviews" ("created_at");`);
|
||||
this.addSql(`create index "reviews_deleted_at_index" on "reviews" ("deleted_at");`);
|
||||
this.addSql(`create index "reviews_food_id_status_index" on "reviews" ("food_id", "status");`);
|
||||
this.addSql(`alter table "reviews" add constraint "reviews_order_id_food_id_user_id_unique" unique ("order_id", "food_id", "user_id");`);
|
||||
this.addSql(`create index "reviews_order_id_index" on "reviews" ("order_id");`);
|
||||
this.addSql(`create index "reviews_user_id_index" on "reviews" ("user_id");`);
|
||||
|
||||
this.addSql(`alter table "icons" add constraint "icons_group_id_foreign" foreign key ("group_id") references "icon_groups" ("id") on update cascade on delete no action;`);
|
||||
|
||||
this.addSql(`alter table "inventory" add constraint "inventory_food_id_foreign" foreign key ("food_id") references "foods" ("id") on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table "order_items" add constraint "order_items_food_id_foreign" foreign key ("food_id") references "foods" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "order_items" add constraint "order_items_order_id_foreign" foreign key ("order_id") references "orders" ("id") on update cascade on delete no action;`);
|
||||
|
||||
this.addSql(`alter table "orders" add constraint "orders_delivery_method_id_foreign" foreign key ("delivery_method_id") references "deliveries" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "orders" add constraint "orders_payment_method_id_foreign" foreign key ("payment_method_id") references "payment_methods" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "orders" add constraint "orders_restaurant_id_foreign" foreign key ("restaurant_id") references "restaurants" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "orders" add constraint "orders_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade on delete no action;`);
|
||||
|
||||
this.addSql(`alter table "pagers" add constraint "pagers_restaurant_id_foreign" foreign key ("restaurant_id") references "restaurants" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "pagers" add constraint "pagers_staff_id_foreign" foreign key ("staff_id") references "admins" ("id") on update cascade on delete set null;`);
|
||||
this.addSql(`alter table "pagers" add constraint "pagers_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade on delete set null;`);
|
||||
|
||||
this.addSql(`alter table "payment_methods" add constraint "payment_methods_restaurant_id_foreign" foreign key ("restaurant_id") references "restaurants" ("id") on update cascade on delete no action;`);
|
||||
|
||||
this.addSql(`alter table "payments" add constraint "payments_order_id_foreign" foreign key ("order_id") references "orders" ("id") on update cascade on delete no action;`);
|
||||
|
||||
this.addSql(`alter table "reviews" add constraint "reviews_food_id_foreign" foreign key ("food_id") references "foods" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "reviews" add constraint "reviews_order_id_foreign" foreign key ("order_id") references "orders" ("id") on update cascade on delete no action;`);
|
||||
this.addSql(`alter table "reviews" add constraint "reviews_user_id_foreign" foreign key ("user_id") references "users" ("id") on update cascade on delete no action;`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" drop constraint if exists "point_transactions_type_check";`);
|
||||
|
||||
this.addSql(`alter table "point_transactions" add constraint "point_transactions_type_check" check("type" in (''));`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,8 +12,6 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { RestaurantsModule } from './modules/restaurants/restaurants.module';
|
||||
import { FoodModule } from './modules/foods/food.module';
|
||||
import { RolesModule } from './modules/roles/roles.module';
|
||||
import { DeliveryModule } from './modules/delivery/delivery.module';
|
||||
import { CouponModule } from './modules/coupons/coupon.module';
|
||||
import { NotificationsModule } from './modules/notifications/notifications.module';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { ContactModule } from './modules/contact/contact.module';
|
||||
@@ -42,8 +40,6 @@ import { CatalogueModule } from './modules/catalogue/catalogue.module';
|
||||
RestaurantsModule,
|
||||
FoodModule,
|
||||
RolesModule,
|
||||
DeliveryModule,
|
||||
CouponModule,
|
||||
NotificationsModule,
|
||||
EventEmitterModule.forRoot(),
|
||||
ContactModule,
|
||||
|
||||
@@ -18,8 +18,10 @@ export class ResponseInterceptor implements NestInterceptor {
|
||||
return next.handle();
|
||||
}
|
||||
|
||||
|
||||
return next.handle().pipe(
|
||||
map(data => {
|
||||
console.log('data',data)
|
||||
// If data is already wrapped or doesn't need wrapping, return as-is
|
||||
if (data && typeof data === 'object' && 'data' in data && 'success' in data) {
|
||||
console.log('data', data)
|
||||
|
||||
@@ -9,8 +9,8 @@ export class CatalogueController {
|
||||
constructor(private readonly catalogueService: CatalogueService) { }
|
||||
|
||||
@Post()
|
||||
create(@Body() createCatalogueDto: CreateCatalogueDto) {
|
||||
return this.catalogueService.create(createCatalogueDto);
|
||||
create(@Body() dto: CreateCatalogueDto) {
|
||||
return this.catalogueService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@@ -24,8 +24,8 @@ export class CatalogueController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
update(@Param('id') id: string, @Body() updateCatalogueDto: UpdateCatalogueDto) {
|
||||
return this.catalogueService.update(id, updateCatalogueDto);
|
||||
update(@Param('id') id: string, @Body() dto: UpdateCatalogueDto) {
|
||||
return this.catalogueService.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
|
||||
@@ -22,7 +22,10 @@ export class CatalogueService {
|
||||
}
|
||||
const catalogue = this.em.create(Catalogue,
|
||||
{ ...dto, business, status: CatalogueStatus.PENDING })
|
||||
return this.em.persistAndFlush(catalogue)
|
||||
|
||||
await this.em.persistAndFlush(catalogue)
|
||||
|
||||
return catalogue
|
||||
}
|
||||
|
||||
findAll(dto: FindCataloguesDto) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CatalogueRepository } from "../repositories/catalogue.repository";
|
||||
|
||||
@Entity({ repository: () => CatalogueRepository })
|
||||
export class Catalogue extends BaseEntity {
|
||||
@Property({ type: "varchar", length: 255, unique: true })
|
||||
@Property({ type: "varchar", length: 255})
|
||||
name!: string;
|
||||
|
||||
@Property({ type: "json" })
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
|
||||
import { CouponService } from '../providers/coupon.service';
|
||||
import { CreateCouponDto } from '../dto/create-coupon.dto';
|
||||
import { UpdateCouponDto } from '../dto/update-coupon.dto';
|
||||
import { FindCouponsDto } from '../dto/find-coupons.dto';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiCreatedResponse,
|
||||
ApiOkResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
ApiParam,
|
||||
ApiBearerAuth,
|
||||
ApiHeader,
|
||||
} from '@nestjs/swagger';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||
import { RestId } from 'src/common/decorators';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||
|
||||
@ApiTags('coupons')
|
||||
@Controller()
|
||||
export class CouponController {
|
||||
constructor(private readonly couponService: CouponService) { }
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Get('public/coupons/me')
|
||||
@ApiOperation({ summary: 'Get paginated list of restaurant coupons' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
@ApiQuery({ name: 'search', required: false, type: String })
|
||||
@ApiQuery({ name: 'orderBy', required: false, type: String })
|
||||
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
|
||||
@ApiQuery({ name: 'type', required: false, enum: ['PERCENTAGE', 'FIXED'] })
|
||||
@ApiQuery({ name: 'isActive', required: false, type: Boolean })
|
||||
getMyCoupons(@Query() dto: FindCouponsDto, @RestId() restId: string) {
|
||||
return this.couponService.findAll(restId, dto);
|
||||
}
|
||||
/*** Admin ***/
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_COUPONS)
|
||||
@Post('admin/coupons')
|
||||
@ApiOperation({ summary: 'Create a new coupon' })
|
||||
@ApiCreatedResponse({ description: 'The coupon has been successfully created.', type: CreateCouponDto })
|
||||
@ApiBody({ type: CreateCouponDto })
|
||||
create(@Body() createCouponDto: CreateCouponDto, @RestId() restId: string) {
|
||||
return this.couponService.create(restId, createCouponDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_COUPONS)
|
||||
@Get('admin/coupons')
|
||||
@ApiOperation({ summary: 'Get a paginated list of coupons' })
|
||||
@ApiQuery({ name: 'page', required: false, type: Number })
|
||||
@ApiQuery({ name: 'limit', required: false, type: Number })
|
||||
@ApiQuery({ name: 'search', required: false, type: String })
|
||||
@ApiQuery({ name: 'orderBy', required: false, type: String })
|
||||
@ApiQuery({ name: 'order', required: false, enum: ['asc', 'desc'] })
|
||||
@ApiQuery({ name: 'type', required: false, enum: ['PERCENTAGE', 'FIXED'] })
|
||||
@ApiQuery({ name: 'isActive', required: false, type: Boolean })
|
||||
findAll(@Query() dto: FindCouponsDto, @RestId() restId: string) {
|
||||
return this.couponService.findAll(restId, dto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_COUPONS)
|
||||
@Get('admin/coupons/:id')
|
||||
@ApiOperation({ summary: 'Get a coupon by id' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
findById(@Param('id') id: string) {
|
||||
return this.couponService.findById(id);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_COUPONS)
|
||||
@Patch('admin/coupons/:id')
|
||||
@ApiOperation({ summary: 'Update a coupon' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
@ApiBody({ type: UpdateCouponDto })
|
||||
update(@Param('id') id: string, @Body() updateCouponDto: UpdateCouponDto) {
|
||||
return this.couponService.update(id, updateCouponDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_COUPONS)
|
||||
@Delete('admin/coupons/:id')
|
||||
@ApiOperation({ summary: 'Delete (soft) a coupon' })
|
||||
@ApiParam({ name: 'id', required: true })
|
||||
remove(@Param('id') id: string) {
|
||||
return this.couponService.remove(id);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_COUPONS)
|
||||
@Get('admin/coupons/generate-code')
|
||||
@ApiOperation({ summary: 'generate a coupon code' })
|
||||
generateCouponCode(@RestId() restId: string) {
|
||||
return this.couponService.generateCouponCode(restId);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CouponService } from './providers/coupon.service';
|
||||
import { CouponController } from './controllers/coupon.controller';
|
||||
import { CouponRepository } from './repositories/coupon.repository';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { Coupon } from './entities/coupon.entity';
|
||||
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
@Module({
|
||||
imports: [MikroOrmModule.forFeature([Coupon]), RestaurantsModule, AuthModule, JwtModule],
|
||||
controllers: [CouponController],
|
||||
providers: [CouponService, CouponRepository],
|
||||
exports: [CouponRepository, CouponService],
|
||||
})
|
||||
export class CouponModule {}
|
||||
@@ -1,115 +0,0 @@
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsEnum,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
Min,
|
||||
IsArray,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { CouponType } from '../interface/coupon';
|
||||
|
||||
export class CreateCouponDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ example: 'DISCOUNT10', description: 'Unique coupon code' })
|
||||
code!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ example: '10% Off', description: 'Coupon name' })
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({ example: 'Get 10% off on your order', description: 'Coupon description' })
|
||||
description?: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsEnum(CouponType)
|
||||
@ApiProperty({ enum: CouponType, example: CouponType.PERCENTAGE, description: 'Coupon type' })
|
||||
type!: CouponType;
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
@ApiProperty({ example: 10, description: 'Discount value (amount or percentage)' })
|
||||
value!: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 50000, description: 'Maximum discount amount (for percentage coupons)' })
|
||||
maxDiscount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 100000, description: 'Minimum order amount to use coupon' })
|
||||
minOrderAmount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 100, description: 'Maximum number of times coupon can be used' })
|
||||
maxUses?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 1, description: 'Maximum uses per user' })
|
||||
maxUsesPerUser?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
@ApiPropertyOptional({ example: '2024-01-01T00:00:00Z', description: 'Coupon validity start date' })
|
||||
startDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
@ApiPropertyOptional({ example: '2024-12-31T23:59:59Z', description: 'Coupon validity end date' })
|
||||
endDate?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
@ApiPropertyOptional({ example: true, description: 'Whether coupon is active' })
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@ApiPropertyOptional({
|
||||
example: ['category-id-1', 'category-id-2'],
|
||||
description: 'Array of food category IDs. If empty, coupon applies to all categories.',
|
||||
type: [String],
|
||||
})
|
||||
foodCategories?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@ApiPropertyOptional({
|
||||
example: ['food-id-1', 'food-id-2'],
|
||||
description: 'Array of food IDs. If empty, coupon applies to all foods.',
|
||||
type: [String],
|
||||
})
|
||||
foods?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({
|
||||
example: '09123456789',
|
||||
description: 'Phone number of the user who can use this coupon. If empty, coupon can be used by any user.',
|
||||
})
|
||||
userPhone?: string;
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { IsOptional, IsString, IsBoolean, IsNumber, IsEnum } from 'class-validator';
|
||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { CouponType } from '../interface/coupon';
|
||||
|
||||
export class FindCouponsDto {
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 1, description: 'Page number' })
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 10, description: 'Items per page' })
|
||||
limit?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({ example: 'DISCOUNT', description: 'Search term for code or name' })
|
||||
search?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(CouponType)
|
||||
@ApiPropertyOptional({ enum: CouponType, description: 'Filter by coupon type' })
|
||||
type?: CouponType;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
@ApiPropertyOptional({ example: true, description: 'Filter by active status' })
|
||||
isActive?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({ example: 'createdAt', description: 'Field to order by' })
|
||||
orderBy?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(['asc', 'desc'])
|
||||
@ApiPropertyOptional({ enum: ['asc', 'desc'], example: 'desc', description: 'Sort order' })
|
||||
order?: 'asc' | 'desc';
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateCouponDto } from './create-coupon.dto';
|
||||
|
||||
export class UpdateCouponDto extends PartialType(CreateCouponDto) {}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { IsNotEmpty, IsString, IsNumber, IsOptional } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class ValidateCouponDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ApiProperty({ example: 'DISCOUNT10', description: 'Coupon code to validate' })
|
||||
code!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
@ApiPropertyOptional({ example: 150000, description: 'Order total amount for validation' })
|
||||
orderAmount?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({ example: 'user-id', description: 'User ID for per-user usage limits' })
|
||||
userId?: string;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { Entity, Index, ManyToOne, Property, Enum, Unique } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { normalizePhone } from '../../utils/phone.util';
|
||||
import { CouponType } from '../interface/coupon';
|
||||
|
||||
@Entity({ tableName: 'coupons' })
|
||||
@Unique({ properties: ['code', 'restaurant'] })
|
||||
@Index({ properties: ['restaurant', 'code', 'isActive'] })
|
||||
@Index({ properties: ['restaurant', 'isActive'] })
|
||||
@Index({ properties: ['code'] })
|
||||
export class Coupon extends BaseEntity {
|
||||
@ManyToOne(() => Restaurant)
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@Property()
|
||||
code!: string;
|
||||
|
||||
@Property()
|
||||
name!: string;
|
||||
|
||||
@Property({ type: 'text', nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Enum(() => CouponType)
|
||||
type!: CouponType;
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 2 })
|
||||
value!: number; // Discount amount or percentage
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
maxDiscount?: number; // Maximum discount for percentage coupons
|
||||
|
||||
@Property({ type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
minOrderAmount?: number; // Minimum order amount to use coupon
|
||||
|
||||
@Property({ type: 'int', nullable: true })
|
||||
maxUses?: number; // Maximum number of times coupon can be used
|
||||
|
||||
@Property({ type: 'int', default: 0 })
|
||||
usedCount: number = 0; // Number of times coupon has been used
|
||||
|
||||
@Property({ type: 'int', nullable: true })
|
||||
maxUsesPerUser?: number; // Maximum uses per user
|
||||
|
||||
@Property({ type: 'timestamptz', nullable: true })
|
||||
startDate?: Date; // Coupon validity start date
|
||||
|
||||
@Property({ type: 'timestamptz', nullable: true })
|
||||
endDate?: Date; // Coupon validity end date
|
||||
|
||||
@Property({ type: 'boolean', default: true })
|
||||
isActive: boolean = true;
|
||||
|
||||
@Property({ type: 'json', nullable: true })
|
||||
foodCategories?: string[]; // Array of category IDs
|
||||
|
||||
@Property({ type: 'json', nullable: true })
|
||||
foods?: string[]; // Array of food IDs
|
||||
|
||||
private _userPhone?: string;
|
||||
|
||||
@Property({ nullable: true })
|
||||
get userPhone(): string | undefined {
|
||||
return this._userPhone;
|
||||
}
|
||||
|
||||
set userPhone(value: string | undefined) {
|
||||
this._userPhone = value ? normalizePhone(value) : undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export enum CouponType {
|
||||
PERCENTAGE = 'PERCENTAGE',
|
||||
FIXED = 'FIXED',
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { CreateCouponDto } from '../dto/create-coupon.dto';
|
||||
import { UpdateCouponDto } from '../dto/update-coupon.dto';
|
||||
import { FindCouponsDto } from '../dto/find-coupons.dto';
|
||||
import { CouponRepository } from '../repositories/coupon.repository';
|
||||
import { RestRepository } from '../../restaurants/repositories/rest.repository';
|
||||
import { EntityManager } from '@mikro-orm/postgresql';
|
||||
import { RequiredEntityData } from '@mikro-orm/core';
|
||||
import { Coupon } from '../entities/coupon.entity';
|
||||
import { CouponType } from '../interface/coupon';
|
||||
import { CouponMessage, RestMessage } from 'src/common/enums/message.enum';
|
||||
import { randomBytes } from 'node:crypto';
|
||||
|
||||
@Injectable()
|
||||
export class CouponService {
|
||||
constructor(
|
||||
private readonly couponRepository: CouponRepository,
|
||||
private readonly restRepository: RestRepository,
|
||||
private readonly em: EntityManager,
|
||||
) { }
|
||||
|
||||
async create(restId: string, createCouponDto: CreateCouponDto): Promise<Coupon> {
|
||||
const restaurant = await this.restRepository.findOne({ id: restId });
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(RestMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if code already exists
|
||||
const existingCoupon = await this.couponRepository.findOne({ code: createCouponDto.code });
|
||||
if (existingCoupon) {
|
||||
throw new BadRequestException(CouponMessage.CODE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
// Validate dates
|
||||
if (createCouponDto.startDate && createCouponDto.endDate) {
|
||||
const startDate = new Date(createCouponDto.startDate);
|
||||
const endDate = new Date(createCouponDto.endDate);
|
||||
if (endDate <= startDate) {
|
||||
throw new BadRequestException(CouponMessage.END_DATE_MUST_BE_AFTER_START_DATE);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate percentage coupon
|
||||
if (createCouponDto.type === CouponType.PERCENTAGE) {
|
||||
if (createCouponDto.value < 0 || createCouponDto.value > 100) {
|
||||
throw new BadRequestException(CouponMessage.PERCENTAGE_MUST_BE_BETWEEN_0_AND_100);
|
||||
}
|
||||
}
|
||||
|
||||
const data: RequiredEntityData<Coupon> = {
|
||||
restaurant,
|
||||
code: createCouponDto.code,
|
||||
name: createCouponDto.name,
|
||||
description: createCouponDto.description,
|
||||
type: createCouponDto.type,
|
||||
value: createCouponDto.value,
|
||||
maxDiscount: createCouponDto.maxDiscount,
|
||||
minOrderAmount: createCouponDto.minOrderAmount,
|
||||
maxUses: createCouponDto.maxUses,
|
||||
maxUsesPerUser: createCouponDto.maxUsesPerUser,
|
||||
startDate: createCouponDto.startDate ? new Date(createCouponDto.startDate) : undefined,
|
||||
endDate: createCouponDto.endDate ? new Date(createCouponDto.endDate) : undefined,
|
||||
isActive: createCouponDto.isActive ?? true,
|
||||
usedCount: 0,
|
||||
foodCategories: createCouponDto.foodCategories,
|
||||
foods: createCouponDto.foods,
|
||||
userPhone: createCouponDto.userPhone,
|
||||
};
|
||||
|
||||
const coupon = this.couponRepository.create(data);
|
||||
if (!coupon) {
|
||||
throw new Error('Failed to create coupon entity');
|
||||
}
|
||||
|
||||
await this.em.persistAndFlush(coupon);
|
||||
return coupon;
|
||||
}
|
||||
|
||||
findAll(restId: string, dto: FindCouponsDto) {
|
||||
return this.couponRepository.findAllPaginated(restId, dto);
|
||||
}
|
||||
|
||||
async findById(id: string): Promise<Coupon> {
|
||||
const coupon = await this.couponRepository.findOne({ id }, { populate: ['restaurant'] });
|
||||
if (!coupon) {
|
||||
throw new NotFoundException(CouponMessage.NOT_FOUND);
|
||||
}
|
||||
return coupon;
|
||||
}
|
||||
|
||||
async findByCode(code: string, restId: string): Promise<Coupon> {
|
||||
const coupon = await this.couponRepository.findOne({ code, restaurant: { id: restId } });
|
||||
if (!coupon) {
|
||||
throw new NotFoundException(CouponMessage.NOT_FOUND);
|
||||
}
|
||||
return coupon;
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateCouponDto): Promise<Coupon> {
|
||||
const coupon = await this.couponRepository.findOne({ id });
|
||||
if (!coupon) {
|
||||
throw new NotFoundException(CouponMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if code is being changed and if it already exists
|
||||
if (dto.code && dto.code !== coupon.code) {
|
||||
const existingCoupon = await this.couponRepository.findOne({ code: dto.code });
|
||||
if (existingCoupon) {
|
||||
throw new BadRequestException(CouponMessage.CODE_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate dates
|
||||
const startDate = dto.startDate ? new Date(dto.startDate) : coupon.startDate;
|
||||
const endDate = dto.endDate ? new Date(dto.endDate) : coupon.endDate;
|
||||
if (startDate && endDate && endDate <= startDate) {
|
||||
throw new BadRequestException(CouponMessage.END_DATE_MUST_BE_AFTER_START_DATE);
|
||||
}
|
||||
|
||||
// Validate percentage coupon
|
||||
if (dto.type === CouponType.PERCENTAGE || coupon.type === CouponType.PERCENTAGE) {
|
||||
const value = dto.value ?? coupon.value;
|
||||
if (value < 0 || value > 100) {
|
||||
throw new BadRequestException(CouponMessage.PERCENTAGE_MUST_BE_BETWEEN_0_AND_100);
|
||||
}
|
||||
}
|
||||
|
||||
// Update fields
|
||||
if (dto.code) coupon.code = dto.code;
|
||||
if (dto.name) coupon.name = dto.name;
|
||||
if (dto.description !== undefined) coupon.description = dto.description;
|
||||
if (dto.type) coupon.type = dto.type;
|
||||
if (dto.value !== undefined) coupon.value = dto.value;
|
||||
if (dto.maxDiscount !== undefined) coupon.maxDiscount = dto.maxDiscount;
|
||||
if (dto.minOrderAmount !== undefined) coupon.minOrderAmount = dto.minOrderAmount;
|
||||
if (dto.maxUses !== undefined) coupon.maxUses = dto.maxUses;
|
||||
if (dto.maxUsesPerUser !== undefined) coupon.maxUsesPerUser = dto.maxUsesPerUser;
|
||||
if (dto.startDate !== undefined) coupon.startDate = dto.startDate ? new Date(dto.startDate) : undefined;
|
||||
if (dto.endDate !== undefined) coupon.endDate = dto.endDate ? new Date(dto.endDate) : undefined;
|
||||
if (dto.isActive !== undefined) coupon.isActive = dto.isActive;
|
||||
if (dto.foodCategories !== undefined) coupon.foodCategories = dto.foodCategories;
|
||||
if (dto.foods !== undefined) coupon.foods = dto.foods;
|
||||
if (dto.userPhone !== undefined) coupon.userPhone = dto.userPhone;
|
||||
|
||||
await this.em.persistAndFlush(coupon);
|
||||
return coupon;
|
||||
}
|
||||
|
||||
async remove(id: string) {
|
||||
const coupon = await this.couponRepository.findOne({ id });
|
||||
if (!coupon) {
|
||||
throw new NotFoundException(CouponMessage.NOT_FOUND);
|
||||
}
|
||||
coupon.deletedAt = new Date();
|
||||
await this.em.persistAndFlush(coupon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate coupon
|
||||
*/
|
||||
async validateCoupon(
|
||||
code: string,
|
||||
restId: string,
|
||||
orderAmount: number,
|
||||
userPhone: string,
|
||||
): Promise<{
|
||||
valid: boolean;
|
||||
coupon?: Coupon;
|
||||
message?: string;
|
||||
}> {
|
||||
const coupon = await this.couponRepository.findOne(
|
||||
{ code, restaurant: { id: restId } },
|
||||
{ populate: ['restaurant'] },
|
||||
);
|
||||
|
||||
if (!coupon) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.NOT_FOUND,
|
||||
};
|
||||
}
|
||||
|
||||
if (coupon.userPhone) {
|
||||
if (userPhone !== coupon.userPhone) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.COUPON_RESTRICTED_TO_USER,
|
||||
};
|
||||
}
|
||||
}
|
||||
// Check if coupon is active
|
||||
if (!coupon.isActive) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.COUPON_INACTIVE,
|
||||
};
|
||||
}
|
||||
|
||||
// Check date validity
|
||||
const now = new Date();
|
||||
if (coupon.startDate && now < coupon.startDate) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.COUPON_NOT_STARTED,
|
||||
};
|
||||
}
|
||||
|
||||
if (coupon.endDate && now > coupon.endDate) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.COUPON_EXPIRED,
|
||||
};
|
||||
}
|
||||
|
||||
// Check max uses
|
||||
if (coupon.maxUses && coupon.usedCount >= coupon.maxUses) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.COUPON_MAX_USES_REACHED,
|
||||
};
|
||||
}
|
||||
|
||||
// Check minimum order amount
|
||||
if (coupon.minOrderAmount && orderAmount < coupon.minOrderAmount) {
|
||||
return {
|
||||
valid: false,
|
||||
message: CouponMessage.MIN_ORDER_AMOUNT_NOT_MET,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
coupon,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment coupon usage count
|
||||
*/
|
||||
async incrementUsage(id: string): Promise<void> {
|
||||
const coupon = await this.couponRepository.findOne({ id });
|
||||
if (coupon) {
|
||||
coupon.usedCount += 1;
|
||||
await this.em.persistAndFlush(coupon);
|
||||
}
|
||||
}
|
||||
|
||||
generateShortCode(restaurantSlug: string, length = 5) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
const bytes = randomBytes(length);
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
result += chars[bytes[i] % chars.length];
|
||||
}
|
||||
const prefix = restaurantSlug.slice(0, 2).toUpperCase();
|
||||
return prefix + '-' + result;
|
||||
}
|
||||
|
||||
async generateCouponCode(restId: string): Promise<{ code: string }> {
|
||||
const restaurant = await this.restRepository.findOne({ id: restId });
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(RestMessage.NOT_FOUND);
|
||||
}
|
||||
const code = this.generateShortCode(restaurant.slug);
|
||||
const existing = await this.couponRepository.findOne({ code, restaurant: { id: restId } });
|
||||
|
||||
if (existing) return this.generateCouponCode(restId);
|
||||
return { code };
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||
import { FilterQuery } from '@mikro-orm/core';
|
||||
import { Coupon } from '../entities/coupon.entity';
|
||||
import { CouponType } from '../interface/coupon';
|
||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||
|
||||
type FindCouponsOpts = {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
search?: string;
|
||||
orderBy?: string;
|
||||
order?: 'asc' | 'desc';
|
||||
type?: CouponType;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CouponRepository extends EntityRepository<Coupon> {
|
||||
constructor(readonly em: EntityManager) {
|
||||
super(em, Coupon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find coupons with pagination and optional filters.
|
||||
* Supports: search (code/name), type, isActive, ordering.
|
||||
*/
|
||||
async findAllPaginated(restId: string, opts: FindCouponsOpts = {}): Promise<PaginatedResult<Coupon>> {
|
||||
const { page = 1, limit = 10, search, orderBy = 'createdAt', order = 'desc', type, isActive } = opts;
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const where: FilterQuery<Coupon> = { restaurant: { id: restId } };
|
||||
|
||||
if (typeof isActive === 'boolean') {
|
||||
where.isActive = isActive;
|
||||
}
|
||||
|
||||
if (type) {
|
||||
where.type = type;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
const pattern = `%${search}%`;
|
||||
where.$or = [{ code: { $ilike: pattern } }, { name: { $ilike: pattern } }];
|
||||
}
|
||||
|
||||
const [data, total] = await this.findAndCount(where, {
|
||||
limit,
|
||||
offset,
|
||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||
});
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiCreatedResponse,
|
||||
ApiOkResponse,
|
||||
ApiNotFoundResponse,
|
||||
ApiParam,
|
||||
ApiBody,
|
||||
ApiBearerAuth,
|
||||
ApiHeader,
|
||||
} from '@nestjs/swagger';
|
||||
import { DeliveryService } from '../providers/delivery.service';
|
||||
import { CreateDeliveryDto } from '../dto/create-delivery.dto';
|
||||
import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
|
||||
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
||||
import { AuthGuard } from 'src/modules/auth/guards/auth.guard';
|
||||
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
|
||||
import { Delivery } from '../entities/delivery.entity';
|
||||
import { API_HEADER_SLUG } from 'src/common/constants';
|
||||
import { Permission } from 'src/common/enums/permission.enum';
|
||||
import { Permissions } from 'src/common/decorators/permissions.decorator';
|
||||
|
||||
@ApiTags('Delivery')
|
||||
@Controller()
|
||||
export class DeliveryController {
|
||||
constructor(private readonly deliveryService: DeliveryService) {}
|
||||
|
||||
@UseGuards(AuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Get('public/delivery-methods/restaurant')
|
||||
@ApiOperation({ summary: 'Get restaurant delivery methods' })
|
||||
@ApiHeader(API_HEADER_SLUG)
|
||||
findAllDeliveryMethods(@RestId() restId: string) {
|
||||
return this.deliveryService.findAllForRestaurantId(restId);
|
||||
}
|
||||
|
||||
/*** Admin ***/
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Post('admin/delivery-methods/restaurant')
|
||||
@ApiOperation({ summary: 'Create a delivery method for a restaurant' })
|
||||
@ApiBody({ type: CreateDeliveryDto })
|
||||
create(@Body() createDto: CreateDeliveryDto, @RestId() restId: string) {
|
||||
return this.deliveryService.create(restId, createDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Get('admin/delivery-methods/restaurant')
|
||||
@ApiOperation({ summary: 'Get the restaurant delivery methods' })
|
||||
findRestaurantDeliveryMethods(@RestId() restId: string) {
|
||||
return this.deliveryService.findAllForRestaurantId(restId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Get('admin/delivery-methods/restaurant/:deliveryId')
|
||||
@ApiOperation({ summary: 'Get a restaurant delivery method by delivery ID' })
|
||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||
findOne(@Param('deliveryId') deliveryId: string, @RestId() restId: string) {
|
||||
return this.deliveryService.findOne(restId, deliveryId);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Patch('admin/delivery-methods/restaurant/:deliveryId')
|
||||
@ApiOperation({ summary: 'Update a restaurant delivery method' })
|
||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||
@ApiBody({ type: UpdateDeliveryDto })
|
||||
update(@Param('deliveryId') deliveryId: string, @Body() updateDto: UpdateDeliveryDto, @RestId() restId: string) {
|
||||
return this.deliveryService.update(restId, deliveryId, updateDto);
|
||||
}
|
||||
|
||||
@UseGuards(AdminAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
@Permissions(Permission.MANAGE_DELIVERY)
|
||||
@Delete('admin/delivery-methods/restaurant/:deliveryId')
|
||||
@ApiOperation({ summary: 'Delete a restaurant delivery method' })
|
||||
@ApiParam({ name: 'deliveryId', description: 'Delivery method ID' })
|
||||
remove(@Param('deliveryId') deliveryId: string, @RestId() restId: string) {
|
||||
return this.deliveryService.remove(restId, deliveryId);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MikroOrmModule } from '@mikro-orm/nestjs';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { DeliveryController } from './controllers/delivery.controller';
|
||||
import { DeliveryService } from './providers/delivery.service';
|
||||
import { RestaurantsModule } from '../restaurants/restaurants.module';
|
||||
import { Delivery } from './entities/delivery.entity';
|
||||
import { DeliveryRepository } from './repositories/delivery.repository';
|
||||
|
||||
@Module({
|
||||
controllers: [DeliveryController],
|
||||
providers: [DeliveryService, DeliveryRepository],
|
||||
exports: [DeliveryService, DeliveryRepository],
|
||||
imports: [MikroOrmModule.forFeature([Delivery]), JwtModule, RestaurantsModule],
|
||||
})
|
||||
export class DeliveryModule {}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { IsNumber, IsBoolean, IsOptional, IsString, Min, IsEnum } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery';
|
||||
|
||||
export class CreateDeliveryDto {
|
||||
@ApiProperty({ example: 'dineIn', description: 'Delivery method name', enum: DeliveryMethodEnum })
|
||||
@IsEnum(DeliveryMethodEnum)
|
||||
method!: DeliveryMethodEnum;
|
||||
|
||||
@ApiProperty({ example: 5000, description: 'Delivery fee' })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
deliveryFee!: number;
|
||||
|
||||
@ApiProperty({ example: 100000, description: 'Minimum order price for free delivery' })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Type(() => Number)
|
||||
minOrderPrice!: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 'توضیحات روش ارسال', description: 'Delivery method description' })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@ApiPropertyOptional({ example: true, description: 'Is this delivery method enabled?' })
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
enabled?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ example: 0, description: 'Display order for sorting delivery methods' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
order?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 0, description: 'Kilometer number' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
distanceBasedMinCost?: number;
|
||||
|
||||
@ApiPropertyOptional({ example: 0, description: 'Delivery fee per kilometer' })
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
perKilometerFee?: number;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
example: DeliveryFeeTypeEnum.FIXED,
|
||||
description: 'Delivery fee type',
|
||||
enum: DeliveryFeeTypeEnum,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsEnum(DeliveryFeeTypeEnum)
|
||||
deliveryFeeType!: DeliveryFeeTypeEnum;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import { PartialType } from '@nestjs/swagger';
|
||||
import { CreateDeliveryDto } from './create-delivery.dto';
|
||||
|
||||
export class UpdateDeliveryDto extends PartialType(CreateDeliveryDto) {}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Entity, Property, Enum, ManyToOne } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||
import { DeliveryFeeTypeEnum, DeliveryMethodEnum } from '../interface/delivery';
|
||||
|
||||
@Entity({ tableName: 'deliveries' })
|
||||
export class Delivery extends BaseEntity {
|
||||
@Enum(() => DeliveryMethodEnum)
|
||||
method!: DeliveryMethodEnum;
|
||||
|
||||
@ManyToOne(() => Restaurant)
|
||||
restaurant!: Restaurant;
|
||||
|
||||
@Property({ type: 'number', default: 0 })
|
||||
deliveryFee: number = 0;
|
||||
|
||||
@Enum(() => DeliveryFeeTypeEnum)
|
||||
deliveryFeeType: DeliveryFeeTypeEnum = DeliveryFeeTypeEnum.FIXED;
|
||||
|
||||
@Property({ type: 'number', default: 0 })
|
||||
perKilometerFee: number | null = null;
|
||||
|
||||
@Property({ type: 'number', default: 0 })
|
||||
distanceBasedMinCost: number | null = null;
|
||||
|
||||
@Property({ type: 'number', default: 0 })
|
||||
minOrderPrice: number = 0;
|
||||
|
||||
@Property({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Property({ default: true })
|
||||
enabled: boolean = true;
|
||||
|
||||
@Property({ type: 'integer', default: 0 })
|
||||
order: number = 0;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export enum DeliveryMethodEnum {
|
||||
DineIn = 'dineIn',
|
||||
CustomerPickup = 'customerPickup',
|
||||
DeliveryCar = 'deliveryCar',
|
||||
DeliveryCourier = 'deliveryCourier',
|
||||
}
|
||||
|
||||
export enum DeliveryFeeTypeEnum {
|
||||
FIXED = 'fixed',
|
||||
DISTANCE_BASED = 'distanceBased',
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
||||
import { EntityManager, RequiredEntityData } from '@mikro-orm/postgresql';
|
||||
import { Delivery } from '../entities/delivery.entity';
|
||||
import { DeliveryRepository } from '../repositories/delivery.repository';
|
||||
import { CreateDeliveryDto } from '../dto/create-delivery.dto';
|
||||
import { UpdateDeliveryDto } from '../dto/update-delivery.dto';
|
||||
import { RestRepository } from '../../restaurants/repositories/rest.repository';
|
||||
import { DeliveryMethodEnum } from '../interface/delivery';
|
||||
import { DeliveryMessage } from 'src/common/enums/message.enum';
|
||||
|
||||
@Injectable()
|
||||
export class DeliveryService {
|
||||
constructor(
|
||||
private readonly deliveryRepository: DeliveryRepository,
|
||||
private readonly restRepository: RestRepository,
|
||||
private readonly em: EntityManager,
|
||||
) {}
|
||||
|
||||
async create(restId: string, createDto: CreateDeliveryDto): Promise<Delivery> {
|
||||
const restaurant = await this.restRepository.findOne({ id: restId });
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(DeliveryMessage.RESTAURANT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if the delivery method with the same name already exists for this restaurant
|
||||
const existing = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
method: createDto.method,
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.');
|
||||
}
|
||||
|
||||
const data: RequiredEntityData<Delivery> = {
|
||||
restaurant,
|
||||
method: createDto.method,
|
||||
deliveryFee: createDto.deliveryFee,
|
||||
minOrderPrice: createDto.minOrderPrice,
|
||||
description: createDto.description,
|
||||
enabled: createDto.enabled ?? true,
|
||||
order: createDto.order ?? 0,
|
||||
deliveryFeeType: createDto.deliveryFeeType,
|
||||
perKilometerFee: createDto.perKilometerFee ?? null,
|
||||
distanceBasedMinCost: createDto.distanceBasedMinCost ?? null,
|
||||
};
|
||||
|
||||
const delivery = this.deliveryRepository.create(data);
|
||||
await this.em.persistAndFlush(delivery);
|
||||
return delivery;
|
||||
}
|
||||
|
||||
async findAllForRestaurantId(restId: string): Promise<Delivery[]> {
|
||||
return this.deliveryRepository.find({ restaurant: { id: restId } }, { orderBy: { order: 'asc' } });
|
||||
}
|
||||
|
||||
async findOne(restId: string, deliveryId: string): Promise<Delivery> {
|
||||
const delivery = await this.deliveryRepository.findOne({
|
||||
id: deliveryId,
|
||||
restaurant: { id: restId },
|
||||
});
|
||||
|
||||
if (!delivery) {
|
||||
throw new NotFoundException(DeliveryMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
|
||||
return delivery;
|
||||
}
|
||||
|
||||
async update(restId: string, deliveryId: string, updateDto: UpdateDeliveryDto): Promise<Delivery> {
|
||||
const delivery = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
id: deliveryId,
|
||||
});
|
||||
|
||||
if (!delivery) {
|
||||
throw new NotFoundException(DeliveryMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
|
||||
// If method is being updated, check for conflicts
|
||||
if (updateDto.method !== undefined && updateDto.method !== delivery.method) {
|
||||
const existing = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
method: updateDto.method,
|
||||
id: { $ne: deliveryId },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
throw new ConflictException('این روش ارسال قبلاً برای این رستوران ثبت شده است.');
|
||||
}
|
||||
}
|
||||
|
||||
this.em.assign(delivery, updateDto);
|
||||
await this.em.persistAndFlush(delivery);
|
||||
|
||||
return delivery;
|
||||
}
|
||||
|
||||
async remove(restId: string, deliveryId: string): Promise<void> {
|
||||
const delivery = await this.deliveryRepository.findOne({
|
||||
restaurant: { id: restId },
|
||||
id: deliveryId,
|
||||
});
|
||||
|
||||
if (!delivery) {
|
||||
throw new NotFoundException(DeliveryMessage.DELIVERY_METHOD_NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.em.removeAndFlush(delivery);
|
||||
}
|
||||
|
||||
findAll(): DeliveryMethodEnum[] {
|
||||
return Object.values(DeliveryMethodEnum);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
||||
import { Delivery } from '../entities/delivery.entity';
|
||||
|
||||
@Injectable()
|
||||
export class DeliveryRepository extends EntityRepository<Delivery> {
|
||||
constructor(readonly em: EntityManager) {
|
||||
super(em, Delivery);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Collection, Entity, Enum, Index, OneToMany, Property } from '@mikro-orm/core';
|
||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
import { PlanEnum } from '../interface/plan.interface';
|
||||
import { PlanEnum } from '../interface/plan.interface';
|
||||
|
||||
@Entity({ tableName: 'restaurants' })
|
||||
@Index({ properties: ['isActive'] })
|
||||
@@ -80,22 +79,9 @@ export class Restaurant extends BaseEntity {
|
||||
@Property()
|
||||
domain!: string;
|
||||
|
||||
// --- روشهای ارسال ---
|
||||
@OneToMany(() => Delivery, delivery => delivery.restaurant)
|
||||
deliveries = new Collection<Delivery>(this);
|
||||
|
||||
|
||||
@Property({ type: 'json', nullable: true })
|
||||
score: {
|
||||
purchaseAmount: string;
|
||||
purchaseScore: string;
|
||||
scoreAmount: string;
|
||||
scoreCredit: string;
|
||||
// bonus score
|
||||
birthdayScore: string;
|
||||
registerScore: string;
|
||||
marriageDateScore: string;
|
||||
referrerScore: string;
|
||||
} | null = null;
|
||||
|
||||
|
||||
@Enum(() => PlanEnum)
|
||||
plan: PlanEnum = PlanEnum.Base;
|
||||
|
||||
@@ -16,11 +16,9 @@ import { normalizePhone } from 'src/modules/utils/phone.util';
|
||||
import slugify from 'slugify';
|
||||
import { NotificationPreference } from '../../notifications/entities/notification-preference.entity';
|
||||
import { notificationPreferencesData } from '../../../seeders/data/notification-preferences.data';
|
||||
import { Coupon } from '../../coupons/entities/coupon.entity';
|
||||
import { Category } from '../../foods/entities/category.entity';
|
||||
import { Category } from '../../foods/entities/category.entity';
|
||||
import { Food } from '../../foods/entities/food.entity';
|
||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||
import { Schedule } from '../entities/schedule.entity';
|
||||
import { Schedule } from '../entities/schedule.entity';
|
||||
import { WalletTransaction } from '../../users/entities/wallet-transaction.entity';
|
||||
import { SmsLog } from '../../notifications/entities/smsLogs.entity';
|
||||
import { Notification } from '../../notifications/entities/notification.entity';
|
||||
@@ -231,8 +229,7 @@ export class RestaurantsService {
|
||||
throw new NotFoundException(RestMessage.NOT_FOUND);
|
||||
}
|
||||
|
||||
// Delete coupons
|
||||
await em.nativeDelete(Coupon, { restaurant: id });
|
||||
|
||||
|
||||
// Delete foods (will cascade to reviews, favorites, and inventory due to cascade settings)
|
||||
await em.nativeDelete(Food, { restaurant: id });
|
||||
@@ -240,8 +237,7 @@ export class RestaurantsService {
|
||||
// Delete categories (foods should be deleted by cascade, but delete explicitly to be safe)
|
||||
await em.nativeDelete(Category, { restaurant: id });
|
||||
|
||||
// Delete deliveries
|
||||
await em.nativeDelete(Delivery, { restaurant: id });
|
||||
|
||||
|
||||
// Delete schedules
|
||||
await em.nativeDelete(Schedule, { restId: id });
|
||||
|
||||
@@ -88,19 +88,11 @@ export class UserService {
|
||||
if (!restaurant) {
|
||||
throw new NotFoundException(`Restaurant not found.`);
|
||||
}
|
||||
const points = Number(restaurant.score?.registerScore || 0);
|
||||
const createData = _raw as unknown as RequiredEntityData<User>;
|
||||
const createData = _raw as unknown as RequiredEntityData<User>;
|
||||
|
||||
const user = this.userRepository.create(createData);
|
||||
const newWalletTransaction = this.walletTransactionRepository.create({
|
||||
user: user,
|
||||
restaurant: restaurant,
|
||||
balance: 0,
|
||||
amount: points,
|
||||
type: WalletTransactionType.CREDIT,
|
||||
reason: WalletTransactionReason.DEPOSIT
|
||||
});
|
||||
await this.em.persistAndFlush([user, newWalletTransaction]);
|
||||
|
||||
await this.em.persistAndFlush([user]);
|
||||
return user;
|
||||
}
|
||||
|
||||
@@ -338,31 +330,8 @@ export class UserService {
|
||||
if (pointsToConvert <= 0) {
|
||||
throw new BadRequestException('Points to convert must be greater than 0.');
|
||||
}
|
||||
|
||||
if (!restaurant.score) {
|
||||
throw new BadRequestException('Restaurant score not found.');
|
||||
}
|
||||
if (!restaurant.score.purchaseScore) {
|
||||
throw new BadRequestException('Restaurant purchase score not found.');
|
||||
}
|
||||
if (!restaurant.score.purchaseAmount) {
|
||||
throw new BadRequestException('Restaurant purchase amount not found.');
|
||||
}
|
||||
const walletIncreaseAmount =
|
||||
(Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore);
|
||||
// Convert points to wallet (1:1 ratio - can be made configurable)
|
||||
|
||||
const newWalletTransaction = em.create(WalletTransaction, {
|
||||
user: user,
|
||||
restaurant: restaurant,
|
||||
amount: walletIncreaseAmount,
|
||||
type: WalletTransactionType.CREDIT,
|
||||
reason: WalletTransactionReason.CONVERT_SCORE_TO_WALLET,
|
||||
balance: walletTransaction.balance + walletIncreaseAmount,
|
||||
});
|
||||
// Update user's wallet and points
|
||||
walletTransaction.balance = walletTransaction.balance + walletIncreaseAmount;
|
||||
em.persist([newWalletTransaction]);
|
||||
|
||||
|
||||
|
||||
await em.flush();
|
||||
|
||||
|
||||
@@ -4,13 +4,10 @@ import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
|
||||
import { PermissionsSeeder } from './permissions.seeder';
|
||||
import { RolesSeeder } from './roles.seeder';
|
||||
import { RestaurantsSeeder } from './restaurants.seeder';
|
||||
import { SchedulesSeeder } from './schedules.seeder';
|
||||
import { FoodsSeeder } from './foods.seeder';
|
||||
import { FoodsSeeder } from './foods.seeder';
|
||||
import { AdminsSeeder } from './admins.seeder';
|
||||
import { UsersSeeder } from './users.seeder';
|
||||
import { UserWalletsSeeder } from './user-wallets.seeder';
|
||||
// import { NotificationsSeeder } from './notifications.seeder';
|
||||
|
||||
|
||||
export class DatabaseSeeder extends Seeder {
|
||||
async run(em: EntityManager) {
|
||||
const TOTAL_STEPS = 15;
|
||||
@@ -60,12 +57,7 @@ export class DatabaseSeeder extends Seeder {
|
||||
const usersSeeder = new UsersSeeder();
|
||||
await usersSeeder.run(em);
|
||||
console.info(`[Seeder] Step 13/${TOTAL_STEPS}: Done`);
|
||||
|
||||
// 14. Create User Wallets
|
||||
console.info(`[Seeder] Step 14/${TOTAL_STEPS}: Creating user wallets`);
|
||||
const userWalletsSeeder = new UserWalletsSeeder();
|
||||
await userWalletsSeeder.run(em);
|
||||
console.info(`[Seeder] Step 14/${TOTAL_STEPS}: Done`);
|
||||
|
||||
|
||||
// 15. Create Notifications for admins and users for all restaurants
|
||||
console.info(`[Seeder] Step 15/${TOTAL_STEPS}: (optional) creating notifications`);
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import type { EntityManager } from '@mikro-orm/core';
|
||||
import { Delivery } from '../modules/delivery/entities/delivery.entity';
|
||||
import type { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
|
||||
import { deliveryMethodsData } from './data/delivery-methods.data';
|
||||
import { DeliveryFeeTypeEnum } from 'src/modules/delivery/interface/delivery';
|
||||
|
||||
export class DeliveryMethodsSeeder {
|
||||
async run(em: EntityManager, restaurants: Restaurant[]): Promise<void> {
|
||||
for (const restaurant of restaurants) {
|
||||
if (!restaurant) continue;
|
||||
|
||||
for (const methodData of deliveryMethodsData) {
|
||||
const existing = await em.findOne(Delivery, {
|
||||
restaurant: { id: restaurant.id },
|
||||
method: methodData.method,
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
const delivery = em.create(Delivery, {
|
||||
restaurant,
|
||||
method: methodData.method,
|
||||
description: methodData.description,
|
||||
deliveryFee: methodData.deliveryFee,
|
||||
minOrderPrice: methodData.minOrderPrice,
|
||||
enabled: methodData.enabled,
|
||||
order: methodData.order,
|
||||
deliveryFeeType: DeliveryFeeTypeEnum.FIXED,
|
||||
perKilometerFee: null,
|
||||
distanceBasedMinCost: null,
|
||||
});
|
||||
em.persist(delivery);
|
||||
} else {
|
||||
// Update existing delivery method if needed
|
||||
if (
|
||||
existing.description !== methodData.description ||
|
||||
existing.deliveryFee !== methodData.deliveryFee ||
|
||||
existing.minOrderPrice !== methodData.minOrderPrice ||
|
||||
existing.order !== methodData.order
|
||||
) {
|
||||
existing.description = methodData.description;
|
||||
existing.deliveryFee = methodData.deliveryFee;
|
||||
existing.minOrderPrice = methodData.minOrderPrice;
|
||||
existing.order = methodData.order;
|
||||
em.persist(existing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await em.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { EntityManager } from '@mikro-orm/core';
|
||||
import { Schedule } from '../modules/restaurants/entities/schedule.entity';
|
||||
import type { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
|
||||
import { timeSlots } from './data/schedules.data';
|
||||
|
||||
export class SchedulesSeeder {
|
||||
async run(em: EntityManager, restaurants: Restaurant[]): Promise<void> {
|
||||
for (const restaurant of restaurants) {
|
||||
if (!restaurant) continue;
|
||||
|
||||
// Create schedules for each day (0 = Sunday, 6 = Saturday)
|
||||
for (let weekDay = 0; weekDay <= 6; weekDay++) {
|
||||
// Create 3 schedules per day
|
||||
for (const timeSlot of timeSlots) {
|
||||
const existing = await em.findOne(Schedule, {
|
||||
restId: restaurant.id,
|
||||
weekDay,
|
||||
openTime: timeSlot.openTime,
|
||||
closeTime: timeSlot.closeTime,
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
const schedule = em.create(Schedule, {
|
||||
restId: restaurant.id,
|
||||
weekDay,
|
||||
openTime: timeSlot.openTime,
|
||||
closeTime: timeSlot.closeTime,
|
||||
isActive: true,
|
||||
});
|
||||
em.persist(schedule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await em.flush();
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import type { EntityManager } from '@mikro-orm/core';
|
||||
import { User } from '../modules/users/entities/user.entity';
|
||||
import { Restaurant } from '../modules/restaurants/entities/restaurant.entity';
|
||||
import { WalletTransaction } from '../modules/users/entities/wallet-transaction.entity';
|
||||
import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet';
|
||||
|
||||
export class UserWalletsSeeder {
|
||||
async run(em: EntityManager): Promise<void> {
|
||||
const users = await em.find(User, {});
|
||||
const restaurants = await em.find(Restaurant, {});
|
||||
|
||||
for (const user of users) {
|
||||
for (const restaurant of restaurants) {
|
||||
// Check if wallet already exists for this user-restaurant combination
|
||||
const existingWallet = await em.findOne(WalletTransaction, {
|
||||
user: { id: user.id },
|
||||
restaurant: { id: restaurant.id },
|
||||
});
|
||||
|
||||
if (!existingWallet) {
|
||||
const wallet = em.create(WalletTransaction, {
|
||||
user,
|
||||
restaurant,
|
||||
balance: 150000000,
|
||||
amount: 150000000,
|
||||
type: WalletTransactionType.CREDIT,
|
||||
reason: WalletTransactionReason.DEPOSIT,
|
||||
});
|
||||
em.persist(wallet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await em.flush();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user