Ship deploy build for multi-company order items.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-15 10:38:10 +03:30
co-authored by Cursor
parent 551fd6f06a
commit 0903a1c1e9
17 changed files with 203 additions and 46 deletions
+1
View File
@@ -34,6 +34,7 @@ export declare class ChooseSettlementDto {
checkMode?: CheckRegistrationMode;
}
export declare class AdminCreateOrderDto {
customerId?: string;
userId?: string;
customerPhone?: string;
customerName?: string;
+6
View File
@@ -185,6 +185,7 @@ __decorate([
__metadata("design:type", String)
], ChooseSettlementDto.prototype, "checkMode", void 0);
class AdminCreateOrderDto {
customerId;
userId;
customerPhone;
customerName;
@@ -196,6 +197,11 @@ class AdminCreateOrderDto {
items;
}
exports.AdminCreateOrderDto = AdminCreateOrderDto;
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsString)(),
__metadata("design:type", String)
], AdminCreateOrderDto.prototype, "customerId", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsString)(),
+1 -1
View File
File diff suppressed because one or more lines are too long
+20
View File
@@ -49,6 +49,8 @@ export declare class CustomerOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -152,6 +154,8 @@ export declare class CustomerOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -255,6 +259,8 @@ export declare class CustomerOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -358,6 +364,8 @@ export declare class CustomerOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -461,6 +469,8 @@ export declare class CustomerOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -568,6 +578,8 @@ export declare class AdminOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -671,6 +683,8 @@ export declare class AdminOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -774,6 +788,8 @@ export declare class AdminOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -877,6 +893,8 @@ export declare class AdminOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -980,6 +998,8 @@ export declare class AdminOrdersController {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
+23
View File
@@ -71,6 +71,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -174,6 +176,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -280,6 +284,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -383,6 +389,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -486,6 +494,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -589,6 +599,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -692,6 +704,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -795,6 +809,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -898,6 +914,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -1001,6 +1019,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
@@ -1062,6 +1082,7 @@ export declare class OrdersService {
private mapCheckCreate;
private nextOrderNumber;
private createWithUniqueOrderNumber;
private companiesForOrder;
private getOrder;
mapOrder(order: OrderFull, adminView?: boolean): {
id: string;
@@ -1108,6 +1129,8 @@ export declare class OrdersService {
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
companyId: string;
companyName: string;
}[];
checks: {
id: string;
+111 -36
View File
@@ -101,26 +101,53 @@ let OrdersService = class OrdersService {
return this.mapOrder(order, true);
}
async submitOrder(userId, dto) {
const [company, settings] = await Promise.all([
this.prisma.company.findUnique({ where: { id: dto.companyId } }),
this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
}),
]);
if (!company || !company.isActive) {
throw new common_1.NotFoundException('شرکت یافت نشد یا غیرفعال است');
}
const maxMeterage = (0, meterage_1.resolveMaxMeterage)(company.maxMeteragePerInvoice, settings?.maxMeteragePerInvoice);
const productIds = dto.items.map((i) => i.productId);
const products = await this.prisma.product.findMany({
where: { id: { in: productIds }, companyId: company.id, isActive: true },
include: { catalog: true },
const settings = await this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
});
if (products.length !== productIds.length) {
throw new common_1.BadRequestException('یک یا چند محصول نامعتبر است');
const productIds = dto.items.map((i) => i.productId);
const uniqueProductIds = [...new Set(productIds)];
if (uniqueProductIds.length !== productIds.length) {
throw new common_1.BadRequestException('محصول تکراری در سفارش مجاز نیست');
}
const products = await this.prisma.product.findMany({
where: { id: { in: uniqueProductIds } },
include: { catalog: true, company: true },
});
const productMap = new Map(products.map((p) => [p.id, p]));
for (const item of dto.items) {
const product = productMap.get(item.productId);
if (!product) {
throw new common_1.BadRequestException('یک یا چند محصول نامعتبر است یا دیگر در سیستم وجود ندارد. سبد را خالی کنید و دوباره اضافه کنید.');
}
if (!product.isActive) {
throw new common_1.BadRequestException(`محصول «${product.name}» غیرفعال است و قابل سفارش نیست`);
}
if (!product.company?.isActive) {
throw new common_1.BadRequestException(`شرکت محصول «${product.name}» غیرفعال است`);
}
}
const meterageByCompany = new Map();
for (const item of dto.items) {
const product = productMap.get(item.productId);
const current = meterageByCompany.get(product.companyId);
if (current) {
current.meterage += item.meterage;
}
else {
meterageByCompany.set(product.companyId, {
name: product.company.name,
meterage: item.meterage,
max: product.company.maxMeteragePerInvoice,
});
}
}
for (const row of meterageByCompany.values()) {
const maxMeterageResolved = (0, meterage_1.resolveMaxMeterage)(row.max, settings?.maxMeteragePerInvoice);
if (row.meterage > maxMeterageResolved) {
throw new common_1.BadRequestException(`حداکثر متراژ مجاز فاکتور برای «${row.name}» برابر ${maxMeterageResolved} متر است`);
}
}
let totalMeterage = 0;
const itemsData = dto.items.map((item) => {
const product = productMap.get(item.productId);
@@ -140,18 +167,28 @@ let OrdersService = class OrdersService {
color: product.color,
requestedMeterage: item.meterage,
notes: item.notes?.trim() || null,
companyId: product.companyId,
companyName: product.company.name,
};
});
if (totalMeterage > maxMeterage) {
throw new common_1.BadRequestException(`حداکثر متراژ مجاز هر فاکتور ${maxMeterage} متر است`);
const companyNames = [
...new Set(itemsData.map((item) => item.companyName).filter(Boolean)),
];
const preferredCompanyId = itemsData.some((item) => item.companyId === dto.companyId)
? dto.companyId
: itemsData[0].companyId;
const primaryCompany = products.find((p) => p.companyId === preferredCompanyId)
?.company;
if (!primaryCompany) {
throw new common_1.NotFoundException('شرکت محصولات یافت نشد یا غیرفعال است');
}
const order = await this.createWithUniqueOrderNumber((orderNumber) => this.prisma.$transaction(async (tx) => {
const created = await tx.order.create({
data: {
orderNumber,
userId,
companyId: company.id,
companyName: company.name,
companyId: primaryCompany.id,
companyName: companyNames.join('، '),
settlementType: null,
checkMode: null,
status: client_1.OrderStatus.awaiting_review,
@@ -174,7 +211,17 @@ let OrdersService = class OrdersService {
const cart = await tx.cart.findFirst({ where: { userId } });
if (cart) {
await tx.cartItem.deleteMany({
where: { cartId: cart.id, companyId: company.id },
where: {
cartId: cart.id,
productId: { in: uniqueProductIds },
},
});
const remaining = await tx.cartItem.findFirst({
where: { cartId: cart.id },
});
await tx.cart.update({
where: { id: cart.id },
data: { companyId: remaining?.companyId ?? null },
});
}
return created;
@@ -200,6 +247,9 @@ let OrdersService = class OrdersService {
? client_1.OrderStatus.awaiting_cash_payment
: client_1.OrderStatus.check_in_progress;
(0, order_transitions_1.assertTransition)(order.status, nextStatus);
const cashCompanies = isCash
? await this.companiesForOrder(order)
: [];
await this.prisma.$transaction(async (tx) => {
await tx.order.update({
where: { id },
@@ -209,7 +259,7 @@ let OrdersService = class OrdersService {
? null
: (dto.checkMode ?? client_1.CheckRegistrationMode.full),
cashInvoiceExpiresAt: isCash
? (0, cash_expiry_1.computeCashInvoiceExpiry)(order.company)
? (0, cash_expiry_1.earliestCashInvoiceExpiry)(cashCompanies)
: null,
cashReminderSentAt: null,
status: nextStatus,
@@ -235,8 +285,16 @@ let OrdersService = class OrdersService {
});
if (!company)
throw new common_1.NotFoundException('شرکت یافت نشد');
let userId = dto.userId;
if (!userId && dto.customerPhone) {
let userId = dto.customerId || dto.userId;
if (userId) {
const existing = await this.prisma.customer.findUnique({
where: { id: userId },
});
if (!existing) {
throw new common_1.BadRequestException('مشتری انتخاب‌شده یافت نشد');
}
}
else if (dto.customerPhone) {
const phone = (0, phone_1.normalizePhone)((0, phone_1.toEnglishDigits)(dto.customerPhone));
if (!(0, phone_1.isValidPhone)(phone)) {
throw new common_1.BadRequestException('شماره موبایل نامعتبر است');
@@ -257,7 +315,7 @@ let OrdersService = class OrdersService {
userId = customer.id;
}
if (!userId) {
throw new common_1.BadRequestException('شناسه یا شماره مشتری الزامی است');
throw new common_1.BadRequestException('انتخاب مشتری الزامی است');
}
const totalMeterage = dto.meterage ?? 0;
const ownerId = userId;
@@ -372,6 +430,12 @@ let OrdersService = class OrdersService {
(order.finalAmount ?? 0) <= 0) {
throw new common_1.BadRequestException('ابتدا سفارش را قیمت‌گذاری کنید تا فاکتور قابل ارسال باشد');
}
const needsCashExpiry = (dto.status === client_1.OrderStatus.cash_invoice_issued ||
dto.status === client_1.OrderStatus.awaiting_cash_payment) &&
!order.cashInvoiceExpiresAt;
const cashExpiry = needsCashExpiry
? (0, cash_expiry_1.earliestCashInvoiceExpiry)(await this.companiesForOrder(order))
: null;
await this.prisma.$transaction(async (tx) => {
await tx.order.update({
where: { id },
@@ -389,16 +453,11 @@ let OrdersService = class OrdersService {
reason: dto.reason || null,
},
});
if (dto.status === client_1.OrderStatus.cash_invoice_issued ||
dto.status === client_1.OrderStatus.awaiting_cash_payment) {
if (!order.cashInvoiceExpiresAt) {
await tx.order.update({
where: { id },
data: {
cashInvoiceExpiresAt: (0, cash_expiry_1.computeCashInvoiceExpiry)(order.company),
},
});
}
if (cashExpiry) {
await tx.order.update({
where: { id },
data: { cashInvoiceExpiresAt: cashExpiry },
});
}
});
const event = (0, notification_events_1.eventForStatus)(dto.status);
@@ -515,6 +574,20 @@ let OrdersService = class OrdersService {
}
}
}
async companiesForOrder(order) {
const companyIds = [
...new Set([
order.companyId,
...order.items.map((item) => item.companyId).filter(Boolean),
].filter(Boolean)),
];
if (!companyIds.length)
return order.company ? [order.company] : [];
const companies = await this.prisma.company.findMany({
where: { id: { in: companyIds } },
});
return companies.length ? companies : order.company ? [order.company] : [];
}
async getOrder(id) {
const order = await this.prisma.order.findUnique({
where: { id },
@@ -572,6 +645,8 @@ let OrdersService = class OrdersService {
lineTotal: hidePrices ? null : item.lineTotal,
totalAmount: hidePrices ? null : item.lineTotal,
notes: item.notes ?? undefined,
companyId: item.companyId,
companyName: item.companyName,
})),
checks: order.checks.map((c) => ({
id: c.id,
+1 -1
View File
File diff suppressed because one or more lines are too long