Expand special orders with form details, codes, and SMS copy.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
63253f1877
commit
3cc4c89828
@@ -0,0 +1,17 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "SpecialOrder" ADD COLUMN "neededDate" TIMESTAMP(3),
|
||||||
|
ADD COLUMN "receiveTime" TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN "servings" INTEGER,
|
||||||
|
ADD COLUMN "cakeFlavor" TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN "filling" TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN "cakeText" TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN "decorations" TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN "deliveryType" "DeliveryType",
|
||||||
|
ADD COLUMN "branchId" TEXT,
|
||||||
|
ADD COLUMN "branchName" TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SpecialOrder_branchId_idx" ON "SpecialOrder"("branchId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "SpecialOrder" ADD CONSTRAINT "SpecialOrder_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "SpecialOrder" ADD COLUMN "baloutSuggestedCombo" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "coating" TEXT NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN "designStyle" TEXT NOT NULL DEFAULT '';
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- AlterTable
|
||||||
|
CREATE SEQUENCE "SpecialOrder_number_seq";
|
||||||
|
|
||||||
|
ALTER TABLE "SpecialOrder" ADD COLUMN "number" INTEGER NOT NULL DEFAULT nextval('"SpecialOrder_number_seq"');
|
||||||
|
|
||||||
|
ALTER SEQUENCE "SpecialOrder_number_seq" OWNED BY "SpecialOrder"."number";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "SpecialOrder_number_key" ON "SpecialOrder"("number");
|
||||||
+36
-18
@@ -213,15 +213,16 @@ model ShippingException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Branch {
|
model Branch {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
district String
|
district String
|
||||||
address String
|
address String
|
||||||
landline String @default("")
|
landline String @default("")
|
||||||
cellNumber String @default("")
|
cellNumber String @default("")
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
orders Order[]
|
orders Order[]
|
||||||
|
specialOrders SpecialOrder[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Order {
|
model Order {
|
||||||
@@ -313,19 +314,36 @@ enum SpecialOrderCategory {
|
|||||||
|
|
||||||
/// Custom request form (no payment / product link) — shown to admins.
|
/// Custom request form (no payment / product link) — shown to admins.
|
||||||
model SpecialOrder {
|
model SpecialOrder {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
number Int @unique @default(autoincrement())
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
userId String
|
||||||
category SpecialOrderCategory
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
name String
|
category SpecialOrderCategory
|
||||||
description String @default("")
|
/// مناسبت سفارش
|
||||||
createdAt DateTime @default(now())
|
name String
|
||||||
updatedAt DateTime @updatedAt
|
description String @default("")
|
||||||
images SpecialOrderImage[]
|
neededDate DateTime?
|
||||||
|
receiveTime String @default("")
|
||||||
|
servings Int?
|
||||||
|
cakeFlavor String @default("")
|
||||||
|
filling String @default("")
|
||||||
|
baloutSuggestedCombo Boolean @default(false)
|
||||||
|
coating String @default("")
|
||||||
|
designStyle String @default("")
|
||||||
|
cakeText String @default("")
|
||||||
|
decorations String @default("")
|
||||||
|
deliveryType DeliveryType?
|
||||||
|
branchId String?
|
||||||
|
branch Branch? @relation(fields: [branchId], references: [id], onDelete: SetNull)
|
||||||
|
branchName String @default("")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
images SpecialOrderImage[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([category])
|
@@index([category])
|
||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
|
@@index([branchId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model SpecialOrderImage {
|
model SpecialOrderImage {
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
import { IsArray, IsEnum, IsOptional, IsString, MinLength, ValidateNested } from 'class-validator';
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
|
IsEnum,
|
||||||
|
IsInt,
|
||||||
|
IsISO8601,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Max,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
ValidateIf,
|
||||||
|
ValidateNested,
|
||||||
|
} from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { SpecialOrderCategory } from '@prisma/client';
|
import { DeliveryType, SpecialOrderCategory } from '@prisma/client';
|
||||||
|
|
||||||
export class SpecialOrderImageDto {
|
export class SpecialOrderImageDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -12,44 +25,82 @@ export class SpecialOrderImageDto {
|
|||||||
storageKey!: string;
|
storageKey!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateSpecialOrderDto {
|
class SpecialOrderDetailsDto {
|
||||||
|
@IsEnum(SpecialOrderCategory, { message: 'دستهبندی نامعتبر است' })
|
||||||
|
category!: SpecialOrderCategory;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@MinLength(1, { message: 'مناسبت سفارش الزامی است' })
|
||||||
|
name!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsISO8601({}, { message: 'تاریخ موردنیاز نامعتبر است' })
|
||||||
|
neededDate?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
receiveTime?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt({ message: 'تعداد نفرات باید عدد باشد' })
|
||||||
|
@Min(1, { message: 'تعداد نفرات حداقل ۱ است' })
|
||||||
|
@Max(500, { message: 'تعداد نفرات بیش از حد مجاز است' })
|
||||||
|
servings?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
cakeFlavor?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
filling?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Boolean)
|
||||||
|
@IsBoolean()
|
||||||
|
baloutSuggestedCombo?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
coating?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
designStyle?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
cakeText?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
decorations?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(DeliveryType, { message: 'نوع دریافت نامعتبر است' })
|
||||||
|
deliveryType?: DeliveryType;
|
||||||
|
|
||||||
|
@ValidateIf((dto: SpecialOrderDetailsDto) => dto.deliveryType === DeliveryType.pickup)
|
||||||
|
@IsString({ message: 'انتخاب شعبه الزامی است' })
|
||||||
|
@MinLength(1, { message: 'انتخاب شعبه الزامی است' })
|
||||||
|
branchId?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
@ValidateNested({ each: true })
|
||||||
|
@Type(() => SpecialOrderImageDto)
|
||||||
|
images?: SpecialOrderImageDto[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateSpecialOrderDto extends SpecialOrderDetailsDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(1)
|
@MinLength(1)
|
||||||
userId!: string;
|
userId!: string;
|
||||||
|
|
||||||
@IsEnum(SpecialOrderCategory, { message: 'دستهبندی نامعتبر است' })
|
|
||||||
category!: SpecialOrderCategory;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MinLength(1, { message: 'نام الزامی است' })
|
|
||||||
name!: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => SpecialOrderImageDto)
|
|
||||||
images?: SpecialOrderImageDto[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CreateMySpecialOrderDto {
|
export class CreateMySpecialOrderDto extends SpecialOrderDetailsDto {}
|
||||||
@IsEnum(SpecialOrderCategory, { message: 'دستهبندی نامعتبر است' })
|
|
||||||
category!: SpecialOrderCategory;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@MinLength(1, { message: 'نام الزامی است' })
|
|
||||||
name!: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => SpecialOrderImageDto)
|
|
||||||
images?: SpecialOrderImageDto[];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { IsArray, IsEnum, IsOptional, IsString, MinLength, ValidateNested } from 'class-validator';
|
import {
|
||||||
|
IsArray,
|
||||||
|
IsBoolean,
|
||||||
|
IsEnum,
|
||||||
|
IsInt,
|
||||||
|
IsISO8601,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Max,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
ValidateNested,
|
||||||
|
} from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { SpecialOrderCategory } from '@prisma/client';
|
import { DeliveryType, SpecialOrderCategory } from '@prisma/client';
|
||||||
import { SpecialOrderImageDto } from './create-special-order.dto';
|
import { SpecialOrderImageDto } from './create-special-order.dto';
|
||||||
|
|
||||||
export class UpdateSpecialOrderDto {
|
export class UpdateSpecialOrderDto {
|
||||||
@@ -15,13 +27,65 @@ export class UpdateSpecialOrderDto {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@MinLength(1, { message: 'نام الزامی است' })
|
@MinLength(1, { message: 'مناسبت سفارش الزامی است' })
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsISO8601({}, { message: 'تاریخ موردنیاز نامعتبر است' })
|
||||||
|
neededDate?: string | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
receiveTime?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt({ message: 'تعداد نفرات باید عدد باشد' })
|
||||||
|
@Min(1, { message: 'تعداد نفرات حداقل ۱ است' })
|
||||||
|
@Max(500, { message: 'تعداد نفرات بیش از حد مجاز است' })
|
||||||
|
servings?: number | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
cakeFlavor?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
filling?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Boolean)
|
||||||
|
@IsBoolean()
|
||||||
|
baloutSuggestedCombo?: boolean;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
coating?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
designStyle?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
cakeText?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
decorations?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsEnum(DeliveryType, { message: 'نوع دریافت نامعتبر است' })
|
||||||
|
deliveryType?: DeliveryType | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
branchId?: string | null;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
|
BadRequestException,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
Injectable,
|
Injectable,
|
||||||
Logger,
|
Logger,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Prisma, SpecialOrderCategory, UserRole } from '@prisma/client';
|
import {
|
||||||
|
DeliveryType,
|
||||||
|
Prisma,
|
||||||
|
SpecialOrderCategory,
|
||||||
|
UserRole,
|
||||||
|
} from '@prisma/client';
|
||||||
import { displayName } from '../auth/auth.types';
|
import { displayName } from '../auth/auth.types';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { SmsService } from '../sms/sms.service';
|
import { SmsService } from '../sms/sms.service';
|
||||||
@@ -28,20 +34,28 @@ const specialOrderInclude = {
|
|||||||
role: true,
|
role: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
branch: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
district: true,
|
||||||
|
address: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
images: {
|
images: {
|
||||||
orderBy: { sortOrder: 'asc' as const },
|
orderBy: { sortOrder: 'asc' as const },
|
||||||
},
|
},
|
||||||
} satisfies Prisma.SpecialOrderInclude;
|
} satisfies Prisma.SpecialOrderInclude;
|
||||||
|
|
||||||
|
const DELIVERY_LABEL: Record<DeliveryType, string> = {
|
||||||
|
pickup: 'دریافت حضوری',
|
||||||
|
shipping: 'ارسال با پیک',
|
||||||
|
};
|
||||||
|
|
||||||
type SpecialOrderRow = Prisma.SpecialOrderGetPayload<{
|
type SpecialOrderRow = Prisma.SpecialOrderGetPayload<{
|
||||||
include: typeof specialOrderInclude;
|
include: typeof specialOrderInclude;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const CATEGORY_LABEL: Record<SpecialOrderCategory, string> = {
|
|
||||||
specialCake: 'کیک ویژه',
|
|
||||||
specialPastry: 'شیرینی ویژه',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SpecialOrdersService {
|
export class SpecialOrdersService {
|
||||||
private readonly logger = new Logger(SpecialOrdersService.name);
|
private readonly logger = new Logger(SpecialOrdersService.name);
|
||||||
@@ -65,6 +79,17 @@ export class SpecialOrdersService {
|
|||||||
OR: [
|
OR: [
|
||||||
{ name: { contains: q, mode: 'insensitive' as const } },
|
{ name: { contains: q, mode: 'insensitive' as const } },
|
||||||
{ description: { contains: q, mode: 'insensitive' as const } },
|
{ description: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ cakeFlavor: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ filling: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ coating: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ designStyle: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ cakeText: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ decorations: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
{ branchName: { contains: q, mode: 'insensitive' as const } },
|
||||||
|
...(Number.isFinite(Number(q.replace(/\D/g, ''))) &&
|
||||||
|
q.replace(/\D/g, '')
|
||||||
|
? [{ number: Number(q.replace(/\D/g, '')) }]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
user: {
|
user: {
|
||||||
OR: [
|
OR: [
|
||||||
@@ -123,12 +148,26 @@ export class SpecialOrdersService {
|
|||||||
|
|
||||||
async create(dto: CreateSpecialOrderDto) {
|
async create(dto: CreateSpecialOrderDto) {
|
||||||
await this.ensureUser(dto.userId);
|
await this.ensureUser(dto.userId);
|
||||||
|
const details = await this.resolveDetails(dto);
|
||||||
const row = await this.prisma.specialOrder.create({
|
const row = await this.prisma.specialOrder.create({
|
||||||
data: {
|
data: {
|
||||||
userId: dto.userId,
|
userId: dto.userId,
|
||||||
category: dto.category,
|
category: dto.category,
|
||||||
name: dto.name.trim(),
|
name: dto.name.trim(),
|
||||||
description: dto.description?.trim() ?? '',
|
description: details.description ?? '',
|
||||||
|
neededDate: details.neededDate ?? null,
|
||||||
|
receiveTime: details.receiveTime ?? '',
|
||||||
|
servings: details.servings ?? null,
|
||||||
|
cakeFlavor: details.cakeFlavor ?? '',
|
||||||
|
filling: details.filling ?? '',
|
||||||
|
baloutSuggestedCombo: details.baloutSuggestedCombo ?? false,
|
||||||
|
coating: details.coating ?? '',
|
||||||
|
designStyle: details.designStyle ?? '',
|
||||||
|
cakeText: details.cakeText ?? '',
|
||||||
|
decorations: details.decorations ?? '',
|
||||||
|
deliveryType: details.deliveryType ?? null,
|
||||||
|
branchId: details.branchId ?? null,
|
||||||
|
branchName: details.branchName ?? '',
|
||||||
images: {
|
images: {
|
||||||
create: this.imageCreates(dto.images),
|
create: this.imageCreates(dto.images),
|
||||||
},
|
},
|
||||||
@@ -162,10 +201,7 @@ export class SpecialOrdersService {
|
|||||||
|
|
||||||
return this.create({
|
return this.create({
|
||||||
userId,
|
userId,
|
||||||
category: dto.category,
|
...dto,
|
||||||
name: dto.name,
|
|
||||||
description: dto.description,
|
|
||||||
images: dto.images,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +209,8 @@ export class SpecialOrdersService {
|
|||||||
await this.ensureExists(id);
|
await this.ensureExists(id);
|
||||||
if (dto.userId) await this.ensureUser(dto.userId);
|
if (dto.userId) await this.ensureUser(dto.userId);
|
||||||
|
|
||||||
|
const details = await this.resolveDetails(dto, { partial: true });
|
||||||
|
|
||||||
const row = await this.prisma.$transaction(async (tx) => {
|
const row = await this.prisma.$transaction(async (tx) => {
|
||||||
if (dto.images !== undefined) {
|
if (dto.images !== undefined) {
|
||||||
await tx.specialOrderImage.deleteMany({
|
await tx.specialOrderImage.deleteMany({
|
||||||
@@ -184,11 +222,7 @@ export class SpecialOrdersService {
|
|||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
...(dto.userId !== undefined ? { userId: dto.userId } : {}),
|
...(dto.userId !== undefined ? { userId: dto.userId } : {}),
|
||||||
...(dto.category !== undefined ? { category: dto.category } : {}),
|
...details,
|
||||||
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
|
||||||
...(dto.description !== undefined
|
|
||||||
? { description: dto.description.trim() }
|
|
||||||
: {}),
|
|
||||||
...(dto.images !== undefined
|
...(dto.images !== undefined
|
||||||
? { images: { create: this.imageCreates(dto.images) } }
|
? { images: { create: this.imageCreates(dto.images) } }
|
||||||
: {}),
|
: {}),
|
||||||
@@ -207,12 +241,11 @@ export class SpecialOrdersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async notifyCreated(row: SpecialOrderRow) {
|
private async notifyCreated(row: SpecialOrderRow) {
|
||||||
const categoryLabel = CATEGORY_LABEL[row.category];
|
const orderCode = `BL-S-${row.number}`;
|
||||||
const customerName = displayName(row.user);
|
|
||||||
const customerPhone = row.user.cellNumber;
|
const customerPhone = row.user.cellNumber;
|
||||||
|
|
||||||
const userMessage = `بلوط: سفارش ویژه «${row.name}» (${categoryLabel}) ثبت شد. بهزودی برای هماهنگی با شما تماس میگیریم.`;
|
const userMessage = `سفارش شما به شماره ی${orderCode} ثبت شد\nاز شما سپاسگزاریم و به زودی با شما تماس خواهیم گرفت`;
|
||||||
const adminMessage = `بلوط: سفارش ویژه جدید\n${categoryLabel} — ${row.name}\nکاربر: ${customerName}\nموبایل: ${customerPhone}`;
|
const adminMessage = `سفارش ویژه ی جدید به شماره سفارش ${orderCode}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.sms.send({ to: customerPhone, message: userMessage });
|
await this.sms.send({ to: customerPhone, message: userMessage });
|
||||||
@@ -268,6 +301,128 @@ export class SpecialOrdersService {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async resolveDetails(
|
||||||
|
dto: {
|
||||||
|
category?: SpecialOrderCategory;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
neededDate?: string | null;
|
||||||
|
receiveTime?: string;
|
||||||
|
servings?: number | null;
|
||||||
|
cakeFlavor?: string;
|
||||||
|
filling?: string;
|
||||||
|
baloutSuggestedCombo?: boolean;
|
||||||
|
coating?: string;
|
||||||
|
designStyle?: string;
|
||||||
|
cakeText?: string;
|
||||||
|
decorations?: string;
|
||||||
|
deliveryType?: DeliveryType | null;
|
||||||
|
branchId?: string | null;
|
||||||
|
},
|
||||||
|
options?: { partial?: boolean },
|
||||||
|
) {
|
||||||
|
const partial = options?.partial ?? false;
|
||||||
|
const data: {
|
||||||
|
category?: SpecialOrderCategory;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
neededDate?: Date | null;
|
||||||
|
receiveTime?: string;
|
||||||
|
servings?: number | null;
|
||||||
|
cakeFlavor?: string;
|
||||||
|
filling?: string;
|
||||||
|
baloutSuggestedCombo?: boolean;
|
||||||
|
coating?: string;
|
||||||
|
designStyle?: string;
|
||||||
|
cakeText?: string;
|
||||||
|
decorations?: string;
|
||||||
|
deliveryType?: DeliveryType | null;
|
||||||
|
branchId?: string | null;
|
||||||
|
branchName?: string;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
if (dto.category !== undefined) data.category = dto.category;
|
||||||
|
if (dto.name !== undefined) data.name = dto.name.trim();
|
||||||
|
if (dto.description !== undefined) {
|
||||||
|
data.description = dto.description.trim();
|
||||||
|
} else if (!partial) {
|
||||||
|
data.description = '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.neededDate !== undefined) {
|
||||||
|
data.neededDate =
|
||||||
|
dto.neededDate === null || dto.neededDate === undefined
|
||||||
|
? null
|
||||||
|
: new Date(dto.neededDate);
|
||||||
|
}
|
||||||
|
if (!partial || dto.receiveTime !== undefined) {
|
||||||
|
data.receiveTime = dto.receiveTime?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.servings !== undefined) {
|
||||||
|
data.servings =
|
||||||
|
dto.servings === null || dto.servings === undefined
|
||||||
|
? null
|
||||||
|
: dto.servings;
|
||||||
|
}
|
||||||
|
if (!partial || dto.cakeFlavor !== undefined) {
|
||||||
|
data.cakeFlavor = dto.cakeFlavor?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.filling !== undefined) {
|
||||||
|
data.filling = dto.filling?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.baloutSuggestedCombo !== undefined) {
|
||||||
|
data.baloutSuggestedCombo = Boolean(dto.baloutSuggestedCombo);
|
||||||
|
}
|
||||||
|
if (!partial || dto.coating !== undefined) {
|
||||||
|
data.coating = dto.coating?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.designStyle !== undefined) {
|
||||||
|
data.designStyle = dto.designStyle?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.cakeText !== undefined) {
|
||||||
|
data.cakeText = dto.cakeText?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.decorations !== undefined) {
|
||||||
|
data.decorations = dto.decorations?.trim() ?? '';
|
||||||
|
}
|
||||||
|
if (!partial || dto.deliveryType !== undefined) {
|
||||||
|
data.deliveryType =
|
||||||
|
dto.deliveryType === undefined ? null : dto.deliveryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!partial || dto.branchId !== undefined) {
|
||||||
|
const branchId =
|
||||||
|
dto.branchId === undefined ||
|
||||||
|
dto.branchId === null ||
|
||||||
|
dto.branchId === ''
|
||||||
|
? null
|
||||||
|
: dto.branchId;
|
||||||
|
if (branchId) {
|
||||||
|
const branch = await this.prisma.branch.findUnique({
|
||||||
|
where: { id: branchId },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
if (!branch) throw new NotFoundException('شعبه یافت نشد');
|
||||||
|
data.branchId = branch.id;
|
||||||
|
data.branchName = branch.name;
|
||||||
|
} else {
|
||||||
|
data.branchId = null;
|
||||||
|
data.branchName = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!partial &&
|
||||||
|
dto.deliveryType === DeliveryType.pickup &&
|
||||||
|
!data.branchId
|
||||||
|
) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'انتخاب شعبه برای دریافت حضوری الزامی است',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
private async ensureUser(userId: string) {
|
private async ensureUser(userId: string) {
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
@@ -287,10 +442,36 @@ export class SpecialOrdersService {
|
|||||||
private serialize(row: SpecialOrderRow) {
|
private serialize(row: SpecialOrderRow) {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
|
number: row.number,
|
||||||
|
code: `BL-S-${row.number}`,
|
||||||
userId: row.userId,
|
userId: row.userId,
|
||||||
category: row.category,
|
category: row.category,
|
||||||
name: row.name,
|
name: row.name,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
|
neededDate: row.neededDate?.toISOString() ?? null,
|
||||||
|
receiveTime: row.receiveTime,
|
||||||
|
servings: row.servings,
|
||||||
|
cakeFlavor: row.cakeFlavor,
|
||||||
|
filling: row.filling,
|
||||||
|
baloutSuggestedCombo: row.baloutSuggestedCombo,
|
||||||
|
coating: row.coating,
|
||||||
|
designStyle: row.designStyle,
|
||||||
|
cakeText: row.cakeText,
|
||||||
|
decorations: row.decorations,
|
||||||
|
deliveryType: row.deliveryType,
|
||||||
|
deliveryTypeLabel: row.deliveryType
|
||||||
|
? DELIVERY_LABEL[row.deliveryType]
|
||||||
|
: null,
|
||||||
|
branchId: row.branchId,
|
||||||
|
branchName: row.branchName || row.branch?.name || '',
|
||||||
|
branch: row.branch
|
||||||
|
? {
|
||||||
|
id: row.branch.id,
|
||||||
|
name: row.branch.name,
|
||||||
|
district: row.branch.district,
|
||||||
|
address: row.branch.address,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
createdAt: row.createdAt.toISOString(),
|
createdAt: row.createdAt.toISOString(),
|
||||||
updatedAt: row.updatedAt.toISOString(),
|
updatedAt: row.updatedAt.toISOString(),
|
||||||
images: row.images.map((image) => ({
|
images: row.images.map((image) => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user