Initial commit: Hedena Gold monorepo.
First commit of the Hedena Gold product (separate from gold-bank): branding, package names, and deploy defaults aligned to the new brand; legacy Henza trees stay local-only via .gitignore.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { Body, Controller, Delete, Get, HttpCode, Param, Post, Put } from '@nestjs/common';
|
||||
import { AdminPreOrdersService } from './admin-preorders.service';
|
||||
import { TableQueryDto } from '../admin-table/table-query.dto';
|
||||
import { RequirePermission } from '../auth/guards';
|
||||
|
||||
/**
|
||||
* Legacy endpoints (CustomOrderController):
|
||||
* POST get-custom-orders -> the "سفارش های ساختنی" grid
|
||||
* GET get-custom-order/{id}
|
||||
* PUT custom-order/{id}
|
||||
* DELETE custom-order/{id}
|
||||
*/
|
||||
@Controller('admin/pre-orders')
|
||||
export class AdminPreOrdersController {
|
||||
constructor(private readonly preOrders: AdminPreOrdersService) {}
|
||||
|
||||
@Post('list')
|
||||
@HttpCode(200)
|
||||
@RequirePermission('order.makeOrder')
|
||||
list(@Body() query: TableQueryDto) {
|
||||
return this.preOrders.list(query);
|
||||
}
|
||||
|
||||
@Get('status-options')
|
||||
@RequirePermission('order.makeOrder')
|
||||
statusOptions() {
|
||||
return this.preOrders.statusOptions();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@RequirePermission('order.makeOrder')
|
||||
findOne(@Param('id') id: string) {
|
||||
return this.preOrders.findOne(id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@RequirePermission('order.makeOrder')
|
||||
update(@Param('id') id: string, @Body() data: Record<string, unknown>) {
|
||||
return this.preOrders.update(id, data);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@RequirePermission('order.makeOrder')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.preOrders.remove(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from '../../prisma/prisma.module';
|
||||
import { AdminPreOrdersController } from './admin-preorders.controller';
|
||||
import { AdminPreOrdersService } from './admin-preorders.service';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
controllers: [AdminPreOrdersController],
|
||||
providers: [AdminPreOrdersService],
|
||||
})
|
||||
export class AdminPreOrdersModule {}
|
||||
@@ -0,0 +1,159 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PreOrderStatus, Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../prisma/prisma.service';
|
||||
import { TableService } from '../admin-table/table.service';
|
||||
import type { TableQueryDto } from '../admin-table/table-query.dto';
|
||||
|
||||
/**
|
||||
* Custom / made-to-order requests — legacy "سفارش های ساختنی".
|
||||
*
|
||||
* Legacy served these from CustomOrderController (`POST get-custom-orders`,
|
||||
* `PUT custom-order/{id}`, `DELETE custom-order/{id}`). The redesigned schema
|
||||
* models them as PreOrder: a design request that an admin prices and later
|
||||
* converts into a real, debt-bearing Order.
|
||||
*/
|
||||
const PREORDER_STATUS_LABELS: Record<PreOrderStatus, string> = {
|
||||
PLACED: 'ثبت شده',
|
||||
ADMIN_APPROVED: 'تایید مدیر',
|
||||
WORKSHOP_APPROVED: 'تایید کارگاه',
|
||||
READY_TO_SHIP: 'آماده ارسال',
|
||||
DELIVERED: 'تحویل شده',
|
||||
CANCELLED: 'لغو شده',
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AdminPreOrdersService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly table: TableService,
|
||||
) {}
|
||||
|
||||
private static readonly ALLOWED = [
|
||||
'id',
|
||||
'status',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'colorName',
|
||||
'sizeSpec',
|
||||
'stoneColor',
|
||||
'minaColor',
|
||||
'estimatedWeightGrams',
|
||||
'estimatedPriceRial',
|
||||
'customer.fullName',
|
||||
'customer.mobile',
|
||||
];
|
||||
|
||||
/** Legacy `POST get-custom-orders`. */
|
||||
async list(query: TableQueryDto) {
|
||||
this.table.assertNotExport(query);
|
||||
|
||||
const { where, orderBy, skip, take, page, limit } = this.table.build(query, {
|
||||
searchable: ['customer.fullName', 'customer.mobile'],
|
||||
allowed: AdminPreOrdersService.ALLOWED,
|
||||
defaultOrderBy: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.preOrder.findMany({
|
||||
where: where as Prisma.PreOrderWhereInput,
|
||||
orderBy: orderBy as Prisma.PreOrderOrderByWithRelationInput,
|
||||
skip,
|
||||
take,
|
||||
include: { customer: true, city: true, workshop: true },
|
||||
}),
|
||||
this.prisma.preOrder.count({ where: where as Prisma.PreOrderWhereInput }),
|
||||
]);
|
||||
|
||||
return this.table.respond(rows.map((r) => this.toRow(r)), total, query, page, limit);
|
||||
}
|
||||
|
||||
/** Legacy field names (color, size, color_mina, color_stone, ...) preserved. */
|
||||
private toRow(
|
||||
p: Prisma.PreOrderGetPayload<{ include: { customer: true; city: true; workshop: true } }>,
|
||||
) {
|
||||
return {
|
||||
id: p.id,
|
||||
status: p.status,
|
||||
sts_str_fa: PREORDER_STATUS_LABELS[p.status],
|
||||
color: p.colorName,
|
||||
size: p.sizeSpec,
|
||||
color_mina: p.minaColor,
|
||||
color_stone: p.stoneColor,
|
||||
// legacy 'sum' (quantity); a PreOrder models a single design request
|
||||
sum: 1,
|
||||
description: p.description,
|
||||
designImageUrl: p.designImageUrl,
|
||||
estimatedWeightGrams: p.estimatedWeightGrams,
|
||||
estimatedPriceRial: p.estimatedPriceRial,
|
||||
time_creat_fa: p.createdAt,
|
||||
time_update_fa: p.updatedAt,
|
||||
convertedOrderId: p.convertedOrderId,
|
||||
city: p.city ? { id: p.city.id, title: p.city.name } : null,
|
||||
workshop: p.workshop ? { id: p.workshop.id, name: p.workshop.name } : null,
|
||||
customer: {
|
||||
id: p.customer.id,
|
||||
// D0 — real name parts
|
||||
fname: p.customer.firstName ?? p.customer.fullName,
|
||||
lname: p.customer.lastName ?? '',
|
||||
name: p.customer.fullName,
|
||||
fullName: p.customer.fullName,
|
||||
mobile: p.customer.mobile,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async findOne(id: string) {
|
||||
const p = await this.prisma.preOrder.findUnique({
|
||||
where: { id },
|
||||
include: { customer: true, city: true, workshop: true },
|
||||
});
|
||||
if (!p) throw new NotFoundException('سفارش ساختنی یافت نشد');
|
||||
return this.toRow(p);
|
||||
}
|
||||
|
||||
/** Legacy `PUT custom-order/{id}`. */
|
||||
async update(id: string, data: Record<string, unknown>) {
|
||||
const existing = await this.prisma.preOrder.findUnique({ where: { id } });
|
||||
if (!existing) throw new NotFoundException('سفارش ساختنی یافت نشد');
|
||||
|
||||
const patch: Prisma.PreOrderUpdateInput = {};
|
||||
if (data.status !== undefined) patch.status = data.status as PreOrderStatus;
|
||||
if (data.color !== undefined) patch.colorName = data.color as string;
|
||||
if (data.size !== undefined) patch.sizeSpec = data.size as string;
|
||||
if (data.color_mina !== undefined) patch.minaColor = data.color_mina as string;
|
||||
if (data.color_stone !== undefined) patch.stoneColor = data.color_stone as string;
|
||||
if (data.description !== undefined) patch.description = data.description as string;
|
||||
if (data.estimatedWeightGrams !== undefined) {
|
||||
patch.estimatedWeightGrams = new Prisma.Decimal(data.estimatedWeightGrams as string);
|
||||
}
|
||||
if (data.estimatedPriceRial !== undefined) {
|
||||
patch.estimatedPriceRial = new Prisma.Decimal(data.estimatedPriceRial as string);
|
||||
}
|
||||
|
||||
return this.prisma.preOrder.update({ where: { id }, data: patch });
|
||||
}
|
||||
|
||||
/** Legacy `DELETE custom-order/{id}`. */
|
||||
async remove(id: string) {
|
||||
const p = await this.prisma.preOrder.findUnique({ where: { id } });
|
||||
if (!p) throw new NotFoundException('سفارش ساختنی یافت نشد');
|
||||
|
||||
// Once converted into a real Order the design request is part of an
|
||||
// accounting trail; deleting it would orphan that link. Legacy had no guard.
|
||||
if (p.convertedOrderId) {
|
||||
throw new BadRequestException(
|
||||
'حذف ممکن نیست: این سفارش ساختنی قبلاً به سفارش قطعی تبدیل شده است.',
|
||||
);
|
||||
}
|
||||
|
||||
await this.prisma.$transaction([
|
||||
this.prisma.preOrderStatusEvent.deleteMany({ where: { preOrderId: id } }),
|
||||
this.prisma.preOrder.delete({ where: { id } }),
|
||||
]);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
statusOptions() {
|
||||
return Object.entries(PREORDER_STATUS_LABELS).map(([value, title]) => ({ value, title }));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user