Simplify manager order status editing
This commit is contained in:
253
src/resolvers.js
253
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() {
|
function orderCode() {
|
||||||
return `FR-${Date.now()}-${crypto.randomInt(1000, 9999)}`;
|
return `FR-${Date.now()}-${crypto.randomInt(1000, 9999)}`;
|
||||||
}
|
}
|
||||||
@@ -1610,8 +1691,8 @@ export const resolvers = {
|
|||||||
throw new Error('Order has no items to price.');
|
throw new Error('Order has no items to price.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const deliveryFee = Number(input.deliveryFee);
|
const deliveryFee = input.deliveryFee == null ? null : Number(input.deliveryFee);
|
||||||
if (!Number.isFinite(deliveryFee) || deliveryFee < 0) {
|
if (deliveryFee != null && (!Number.isFinite(deliveryFee) || deliveryFee < 0)) {
|
||||||
throw new Error('Delivery fee must be zero or greater.');
|
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.');
|
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);
|
const unitPrice = Number(itemPrice.unitPrice);
|
||||||
if (!Number.isFinite(unitPrice) || unitPrice < 0) {
|
if (!Number.isFinite(unitPrice) || unitPrice < 0) {
|
||||||
throw new Error('Unit price must be zero or greater.');
|
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.');
|
throw new Error('Pricing must be provided for every order item.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalProductsPrice = existingOrder.items.reduce(
|
const totalProductsPrice = existingOrder.items.reduce((sum, item) => {
|
||||||
(sum, item) => sum + (Number(item.quantity) * itemPriceMap.get(item.id)),
|
const unitPrice = itemPriceMap.get(item.id);
|
||||||
0,
|
return unitPrice == null ? sum : sum + (Number(item.quantity) * unitPrice);
|
||||||
);
|
}, 0);
|
||||||
const totalPrice = roundMoney(totalProductsPrice + deliveryFee);
|
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) => {
|
const order = await context.prisma.$transaction(async (tx) => {
|
||||||
for (const item of existingOrder.items) {
|
for (const item of existingOrder.items) {
|
||||||
@@ -1659,26 +1749,27 @@ export const resolvers = {
|
|||||||
where: { id: input.orderId },
|
where: { id: input.orderId },
|
||||||
data: {
|
data: {
|
||||||
managerId: manager.id,
|
managerId: manager.id,
|
||||||
status: 'WAITING_DOUBLE_CONFIRM',
|
|
||||||
clientApproved: null,
|
|
||||||
managerApproved: null,
|
|
||||||
blockReason: null,
|
|
||||||
deliveryTerms: normalizeOptionalText(input.deliveryTerms),
|
deliveryTerms: normalizeOptionalText(input.deliveryTerms),
|
||||||
deliveryFee: roundMoney(deliveryFee),
|
deliveryFee: normalizedDeliveryFee,
|
||||||
totalPrice,
|
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({
|
return context.prisma.order.findUnique({
|
||||||
where: { id: order.id },
|
where: { id: order.id },
|
||||||
include: { items: true, history: { orderBy: { createdAt: 'desc' } } },
|
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) => {
|
clientReviewOrder: async (_, { orderId, decision }, context) => {
|
||||||
const customer = requireUser(context);
|
const customer = requireUser(context);
|
||||||
const order = await context.prisma.order.findUnique({ where: { id: orderId } });
|
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) => {
|
createReferral: async (_, { input }, context) => {
|
||||||
const manager = requireManagerAccess(context);
|
const manager = requireManagerAccess(context);
|
||||||
|
|
||||||
|
|||||||
@@ -467,17 +467,12 @@ input SetOrderOfferInput {
|
|||||||
orderId: ID!
|
orderId: ID!
|
||||||
itemPrices: [OrderItemPriceInput!]!
|
itemPrices: [OrderItemPriceInput!]!
|
||||||
deliveryTerms: String!
|
deliveryTerms: String!
|
||||||
deliveryFee: Float!
|
deliveryFee: Float
|
||||||
}
|
}
|
||||||
|
|
||||||
input OrderItemPriceInput {
|
input OrderItemPriceInput {
|
||||||
itemId: ID!
|
itemId: ID!
|
||||||
unitPrice: Float!
|
unitPrice: Float
|
||||||
}
|
|
||||||
|
|
||||||
input BlockOrderInput {
|
|
||||||
orderId: ID!
|
|
||||||
reason: String!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input CreateReferralInput {
|
input CreateReferralInput {
|
||||||
@@ -526,11 +521,8 @@ type Mutation {
|
|||||||
submitReadyOrder(input: SubmitReadyOrderInput!): Order!
|
submitReadyOrder(input: SubmitReadyOrderInput!): Order!
|
||||||
submitCalculationOrder(input: SubmitCalculationOrderInput!): Order!
|
submitCalculationOrder(input: SubmitCalculationOrderInput!): Order!
|
||||||
managerSetOrderOffer(input: SetOrderOfferInput!): Order!
|
managerSetOrderOffer(input: SetOrderOfferInput!): Order!
|
||||||
|
managerSetOrderStatus(orderId: ID!, status: OrderStatus!): Order!
|
||||||
clientReviewOrder(orderId: ID!, decision: Decision!): 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!
|
createReferral(input: CreateReferralInput!): ReferralLink!
|
||||||
addBonusTransaction(input: AddBonusTransactionInput!): BonusTransaction!
|
addBonusTransaction(input: AddBonusTransactionInput!): BonusTransaction!
|
||||||
|
|||||||
Reference in New Issue
Block a user