From 84184f45684b7895bd2ee88baf1e1cb977ff7e78 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev Date: Mon, 6 Apr 2026 12:22:33 +0700 Subject: [PATCH] Simplify manager order status editing --- src/resolvers.js | 253 +++++++++++++++++++-------------------------- src/schema.graphql | 14 +-- 2 files changed, 109 insertions(+), 158 deletions(-) diff --git a/src/resolvers.js b/src/resolvers.js index e6cf145..acf1cc8 100644 --- a/src/resolvers.js +++ b/src/resolvers.js @@ -162,6 +162,87 @@ async function createReferralBonusTransaction(prisma, order) { }; } +function getManualOrderStatusPatch(status) { + if (['NEW', 'MANAGER_PROCESSING', 'WAITING_DOUBLE_CONFIRM', 'MANAGER_BLOCKED'].includes(status)) { + return { + clientApproved: null, + managerApproved: null, + blockReason: null, + }; + } + + if (status === 'CLIENT_REJECTED') { + return { + clientApproved: false, + blockReason: null, + }; + } + + if (status === 'MANAGER_REJECTED') { + return { + managerApproved: false, + blockReason: null, + }; + } + + if (['CONFIRMED', 'IN_PROGRESS', 'COMPLETED'].includes(status)) { + return { + clientApproved: true, + managerApproved: true, + blockReason: null, + }; + } + + return { + blockReason: null, + }; +} + +async function applyManualOrderStatus(context, order, manager, status) { + if (order.status === status) { + return context.prisma.order.findUnique({ + where: { id: order.id }, + include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, + }); + } + + const { updated, referralBonus } = await context.prisma.$transaction(async (tx) => { + const updatedOrder = await tx.order.update({ + where: { id: order.id }, + data: { + managerId: manager.id, + status, + ...getManualOrderStatusPatch(status), + }, + }); + + const createdReferralBonus = status === 'COMPLETED' + ? await createReferralBonusTransaction(tx, updatedOrder) + : null; + + return { + updated: updatedOrder, + referralBonus: createdReferralBonus, + }; + }); + + await appendOrderEvent(context.prisma, updated.id, status, manager.id, `Manager set status to ${status}`); + await notifyOrderStakeholders(context, updated, status, `Manager set status to ${status}`); + + if (referralBonus?.isNew) { + await dispatchToUserConnections( + context.prisma, + referralBonus.transaction.userId, + `Начислен бонус: ${toFloat(referralBonus.transaction.amount)} за заказ ${updated.code}.`, + ); + } + + return context.prisma.order.findUnique({ + where: { id: updated.id }, + include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, + }); +} + function orderCode() { return `FR-${Date.now()}-${crypto.randomInt(1000, 9999)}`; } @@ -1610,8 +1691,8 @@ export const resolvers = { throw new Error('Order has no items to price.'); } - const deliveryFee = Number(input.deliveryFee); - if (!Number.isFinite(deliveryFee) || deliveryFee < 0) { + const deliveryFee = input.deliveryFee == null ? null : Number(input.deliveryFee); + if (deliveryFee != null && (!Number.isFinite(deliveryFee) || deliveryFee < 0)) { throw new Error('Delivery fee must be zero or greater.'); } @@ -1627,6 +1708,11 @@ export const resolvers = { throw new Error('Pricing can only be set for items from this order.'); } + if (itemPrice.unitPrice == null) { + itemPriceMap.set(itemPrice.itemId, null); + continue; + } + const unitPrice = Number(itemPrice.unitPrice); if (!Number.isFinite(unitPrice) || unitPrice < 0) { throw new Error('Unit price must be zero or greater.'); @@ -1639,11 +1725,15 @@ export const resolvers = { throw new Error('Pricing must be provided for every order item.'); } - const totalProductsPrice = existingOrder.items.reduce( - (sum, item) => sum + (Number(item.quantity) * itemPriceMap.get(item.id)), - 0, - ); - const totalPrice = roundMoney(totalProductsPrice + deliveryFee); + const totalProductsPrice = existingOrder.items.reduce((sum, item) => { + const unitPrice = itemPriceMap.get(item.id); + return unitPrice == null ? sum : sum + (Number(item.quantity) * unitPrice); + }, 0); + const hasCompletePricing = existingOrder.items.every((item) => itemPriceMap.get(item.id) != null); + const normalizedDeliveryFee = deliveryFee == null ? null : roundMoney(deliveryFee); + const totalPrice = hasCompletePricing && normalizedDeliveryFee != null + ? roundMoney(totalProductsPrice + normalizedDeliveryFee) + : null; const order = await context.prisma.$transaction(async (tx) => { for (const item of existingOrder.items) { @@ -1659,26 +1749,27 @@ export const resolvers = { where: { id: input.orderId }, data: { managerId: manager.id, - status: 'WAITING_DOUBLE_CONFIRM', - clientApproved: null, - managerApproved: null, - blockReason: null, deliveryTerms: normalizeOptionalText(input.deliveryTerms), - deliveryFee: roundMoney(deliveryFee), + deliveryFee: normalizedDeliveryFee, totalPrice, }, }); }); - await appendOrderEvent(context.prisma, order.id, 'WAITING_DOUBLE_CONFIRM', manager.id, 'Offer is published by manager'); - await notifyOrderStakeholders(context, order, 'WAITING_DOUBLE_CONFIRM', 'Offer is published by manager'); - return context.prisma.order.findUnique({ where: { id: order.id }, include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, }); }, + managerSetOrderStatus: async (_, { orderId, status }, context) => { + const manager = requireManagerAccess(context); + const order = await context.prisma.order.findUnique({ where: { id: orderId } }); + assertManagerCanAccessOrder(order); + + return applyManualOrderStatus(context, order, manager, status); + }, + clientReviewOrder: async (_, { orderId, decision }, context) => { const customer = requireUser(context); const order = await context.prisma.order.findUnique({ where: { id: orderId } }); @@ -1721,138 +1812,6 @@ export const resolvers = { }); }, - managerFinalizeOrder: async (_, { orderId, decision }, context) => { - const manager = requireManagerAccess(context); - const order = await context.prisma.order.findUnique({ where: { id: orderId } }); - assertManagerCanAccessOrder(order); - - const status = decision === 'REJECT' - ? 'MANAGER_REJECTED' - : order.clientApproved - ? 'CONFIRMED' - : 'WAITING_DOUBLE_CONFIRM'; - - const updated = await context.prisma.order.update({ - where: { id: orderId }, - data: { - managerId: manager.id, - managerApproved: decision === 'APPROVE', - status, - }, - }); - - await appendOrderEvent( - context.prisma, - updated.id, - status, - manager.id, - decision === 'APPROVE' ? 'Manager approved order' : 'Manager rejected order', - ); - await notifyOrderStakeholders( - context, - updated, - status, - decision === 'APPROVE' ? 'Manager approved order' : 'Manager rejected order', - ); - - return context.prisma.order.findUnique({ - where: { id: updated.id }, - include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, - }); - }, - - blockOrder: async (_, { input }, context) => { - const manager = requireManagerAccess(context); - const order = await context.prisma.order.findUnique({ - where: { id: input.orderId }, - }); - assertManagerCanAccessOrder(order); - - const updated = await context.prisma.order.update({ - where: { id: input.orderId }, - data: { - managerId: manager.id, - status: 'MANAGER_BLOCKED', - blockReason: input.reason, - }, - }); - - await appendOrderEvent(context.prisma, updated.id, 'MANAGER_BLOCKED', manager.id, input.reason); - await notifyOrderStakeholders(context, updated, 'MANAGER_BLOCKED', input.reason); - - return context.prisma.order.findUnique({ - where: { id: updated.id }, - include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, - }); - }, - - startOrderWork: async (_, { orderId }, context) => { - const manager = requireManagerAccess(context); - const order = await context.prisma.order.findUnique({ where: { id: orderId } }); - assertManagerCanAccessOrder(order); - if (!['WAITING_DOUBLE_CONFIRM', 'CONFIRMED'].includes(order.status)) { - throw new Error('Only priced order can be started.'); - } - - const updated = await context.prisma.order.update({ - where: { id: orderId }, - data: { - managerId: manager.id, - status: 'IN_PROGRESS', - }, - }); - - await appendOrderEvent(context.prisma, updated.id, 'IN_PROGRESS', manager.id, 'Order moved to in-progress'); - await notifyOrderStakeholders(context, updated, 'IN_PROGRESS', 'Order moved to in-progress'); - - return context.prisma.order.findUnique({ - where: { id: updated.id }, - include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, - }); - }, - - completeOrder: async (_, { orderId }, context) => { - const manager = requireManagerAccess(context); - const order = await context.prisma.order.findUnique({ where: { id: orderId } }); - assertManagerCanAccessOrder(order); - if (order.status !== 'IN_PROGRESS') { - throw new Error('Only in-progress order can be completed.'); - } - - const { updated, referralBonus } = await context.prisma.$transaction(async (tx) => { - const updatedOrder = await tx.order.update({ - where: { id: orderId }, - data: { - managerId: manager.id, - status: 'COMPLETED', - }, - }); - - const createdReferralBonus = await createReferralBonusTransaction(tx, updatedOrder); - - return { - updated: updatedOrder, - referralBonus: createdReferralBonus, - }; - }); - - await appendOrderEvent(context.prisma, updated.id, 'COMPLETED', manager.id, 'Order completed'); - await notifyOrderStakeholders(context, updated, 'COMPLETED', 'Order completed'); - - if (referralBonus?.isNew) { - await dispatchToUserConnections( - context.prisma, - referralBonus.transaction.userId, - `Начислен бонус: ${toFloat(referralBonus.transaction.amount)} за заказ ${updated.code}.`, - ); - } - - return context.prisma.order.findUnique({ - where: { id: updated.id }, - include: { items: true, history: { orderBy: { createdAt: 'desc' } } }, - }); - }, - createReferral: async (_, { input }, context) => { const manager = requireManagerAccess(context); diff --git a/src/schema.graphql b/src/schema.graphql index 5d340bf..6ee668d 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -467,17 +467,12 @@ input SetOrderOfferInput { orderId: ID! itemPrices: [OrderItemPriceInput!]! deliveryTerms: String! - deliveryFee: Float! + deliveryFee: Float } input OrderItemPriceInput { itemId: ID! - unitPrice: Float! -} - -input BlockOrderInput { - orderId: ID! - reason: String! + unitPrice: Float } input CreateReferralInput { @@ -526,11 +521,8 @@ type Mutation { submitReadyOrder(input: SubmitReadyOrderInput!): Order! submitCalculationOrder(input: SubmitCalculationOrderInput!): Order! managerSetOrderOffer(input: SetOrderOfferInput!): Order! + managerSetOrderStatus(orderId: ID!, status: OrderStatus!): Order! clientReviewOrder(orderId: ID!, decision: Decision!): Order! - managerFinalizeOrder(orderId: ID!, decision: Decision!): Order! - blockOrder(input: BlockOrderInput!): Order! - startOrderWork(orderId: ID!): Order! - completeOrder(orderId: ID!): Order! createReferral(input: CreateReferralInput!): ReferralLink! addBonusTransaction(input: AddBonusTransactionInput!): BonusTransaction!