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 {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
district String
|
||||
address String
|
||||
landline String @default("")
|
||||
cellNumber String @default("")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
orders Order[]
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
district String
|
||||
address String
|
||||
landline String @default("")
|
||||
cellNumber String @default("")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
orders Order[]
|
||||
specialOrders SpecialOrder[]
|
||||
}
|
||||
|
||||
model Order {
|
||||
@@ -313,19 +314,36 @@ enum SpecialOrderCategory {
|
||||
|
||||
/// Custom request form (no payment / product link) — shown to admins.
|
||||
model SpecialOrder {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
category SpecialOrderCategory
|
||||
name String
|
||||
description String @default("")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
images SpecialOrderImage[]
|
||||
id String @id @default(cuid())
|
||||
number Int @unique @default(autoincrement())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
category SpecialOrderCategory
|
||||
/// مناسبت سفارش
|
||||
name String
|
||||
description String @default("")
|
||||
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([category])
|
||||
@@index([createdAt])
|
||||
@@index([branchId])
|
||||
}
|
||||
|
||||
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 { SpecialOrderCategory } from '@prisma/client';
|
||||
import { DeliveryType, SpecialOrderCategory } from '@prisma/client';
|
||||
|
||||
export class SpecialOrderImageDto {
|
||||
@IsString()
|
||||
@@ -12,44 +25,82 @@ export class SpecialOrderImageDto {
|
||||
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()
|
||||
@MinLength(1)
|
||||
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 {
|
||||
@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 extends SpecialOrderDetailsDto {}
|
||||
|
||||
@@ -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 { SpecialOrderCategory } from '@prisma/client';
|
||||
import { DeliveryType, SpecialOrderCategory } from '@prisma/client';
|
||||
import { SpecialOrderImageDto } from './create-special-order.dto';
|
||||
|
||||
export class UpdateSpecialOrderDto {
|
||||
@@ -15,13 +27,65 @@ export class UpdateSpecialOrderDto {
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1, { message: 'نام الزامی است' })
|
||||
@MinLength(1, { message: 'مناسبت سفارش الزامی است' })
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
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()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotFoundException,
|
||||
} 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 { PrismaService } from '../prisma/prisma.service';
|
||||
import { SmsService } from '../sms/sms.service';
|
||||
@@ -28,20 +34,28 @@ const specialOrderInclude = {
|
||||
role: true,
|
||||
},
|
||||
},
|
||||
branch: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
district: true,
|
||||
address: true,
|
||||
},
|
||||
},
|
||||
images: {
|
||||
orderBy: { sortOrder: 'asc' as const },
|
||||
},
|
||||
} satisfies Prisma.SpecialOrderInclude;
|
||||
|
||||
const DELIVERY_LABEL: Record<DeliveryType, string> = {
|
||||
pickup: 'دریافت حضوری',
|
||||
shipping: 'ارسال با پیک',
|
||||
};
|
||||
|
||||
type SpecialOrderRow = Prisma.SpecialOrderGetPayload<{
|
||||
include: typeof specialOrderInclude;
|
||||
}>;
|
||||
|
||||
const CATEGORY_LABEL: Record<SpecialOrderCategory, string> = {
|
||||
specialCake: 'کیک ویژه',
|
||||
specialPastry: 'شیرینی ویژه',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SpecialOrdersService {
|
||||
private readonly logger = new Logger(SpecialOrdersService.name);
|
||||
@@ -65,6 +79,17 @@ export class SpecialOrdersService {
|
||||
OR: [
|
||||
{ name: { 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: {
|
||||
OR: [
|
||||
@@ -123,12 +148,26 @@ export class SpecialOrdersService {
|
||||
|
||||
async create(dto: CreateSpecialOrderDto) {
|
||||
await this.ensureUser(dto.userId);
|
||||
const details = await this.resolveDetails(dto);
|
||||
const row = await this.prisma.specialOrder.create({
|
||||
data: {
|
||||
userId: dto.userId,
|
||||
category: dto.category,
|
||||
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: {
|
||||
create: this.imageCreates(dto.images),
|
||||
},
|
||||
@@ -162,10 +201,7 @@ export class SpecialOrdersService {
|
||||
|
||||
return this.create({
|
||||
userId,
|
||||
category: dto.category,
|
||||
name: dto.name,
|
||||
description: dto.description,
|
||||
images: dto.images,
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,6 +209,8 @@ export class SpecialOrdersService {
|
||||
await this.ensureExists(id);
|
||||
if (dto.userId) await this.ensureUser(dto.userId);
|
||||
|
||||
const details = await this.resolveDetails(dto, { partial: true });
|
||||
|
||||
const row = await this.prisma.$transaction(async (tx) => {
|
||||
if (dto.images !== undefined) {
|
||||
await tx.specialOrderImage.deleteMany({
|
||||
@@ -184,11 +222,7 @@ export class SpecialOrdersService {
|
||||
where: { id },
|
||||
data: {
|
||||
...(dto.userId !== undefined ? { userId: dto.userId } : {}),
|
||||
...(dto.category !== undefined ? { category: dto.category } : {}),
|
||||
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
||||
...(dto.description !== undefined
|
||||
? { description: dto.description.trim() }
|
||||
: {}),
|
||||
...details,
|
||||
...(dto.images !== undefined
|
||||
? { images: { create: this.imageCreates(dto.images) } }
|
||||
: {}),
|
||||
@@ -207,12 +241,11 @@ export class SpecialOrdersService {
|
||||
}
|
||||
|
||||
private async notifyCreated(row: SpecialOrderRow) {
|
||||
const categoryLabel = CATEGORY_LABEL[row.category];
|
||||
const customerName = displayName(row.user);
|
||||
const orderCode = `BL-S-${row.number}`;
|
||||
const customerPhone = row.user.cellNumber;
|
||||
|
||||
const userMessage = `بلوط: سفارش ویژه «${row.name}» (${categoryLabel}) ثبت شد. بهزودی برای هماهنگی با شما تماس میگیریم.`;
|
||||
const adminMessage = `بلوط: سفارش ویژه جدید\n${categoryLabel} — ${row.name}\nکاربر: ${customerName}\nموبایل: ${customerPhone}`;
|
||||
const userMessage = `سفارش شما به شماره ی${orderCode} ثبت شد\nاز شما سپاسگزاریم و به زودی با شما تماس خواهیم گرفت`;
|
||||
const adminMessage = `سفارش ویژه ی جدید به شماره سفارش ${orderCode}`;
|
||||
|
||||
try {
|
||||
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) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
@@ -287,10 +442,36 @@ export class SpecialOrdersService {
|
||||
private serialize(row: SpecialOrderRow) {
|
||||
return {
|
||||
id: row.id,
|
||||
number: row.number,
|
||||
code: `BL-S-${row.number}`,
|
||||
userId: row.userId,
|
||||
category: row.category,
|
||||
name: row.name,
|
||||
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(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
images: row.images.map((image) => ({
|
||||
|
||||
Reference in New Issue
Block a user