feat: Implement comprehensive three-party supply order workflow system
- Added logistics partner selection as mandatory requirement for fulfillment supply orders - Implemented complete status workflow: PENDING → SUPPLIER_APPROVED → LOGISTICS_CONFIRMED → SHIPPED → DELIVERED - Created dedicated interfaces for all three parties: * Fulfillment: Create orders with mandatory logistics selection and receive shipments * Suppliers: View, approve/reject orders, and ship approved orders via /supplies tab * Logistics: Confirm/reject transport requests via new /logistics-orders dashboard - Updated Prisma schema with logisticsPartnerId (non-nullable) and new SupplyOrderStatus enum - Added comprehensive GraphQL mutations for each party's workflow actions - Fixed GraphQL resolver to include logistics partners in supplyOrders query - Enhanced UI components with proper status badges and action buttons - Added backward compatibility for legacy status handling - Updated sidebar navigation routing for LOGIST organization type 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -14,7 +14,7 @@ import {
|
||||
GET_MY_SUPPLIES,
|
||||
GET_WAREHOUSE_PRODUCTS,
|
||||
} from "@/graphql/queries";
|
||||
import { UPDATE_SUPPLY_ORDER_STATUS } from "@/graphql/mutations";
|
||||
import { FULFILLMENT_RECEIVE_ORDER } from "@/graphql/mutations";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
@ -96,25 +96,38 @@ const formatDate = (dateString: string) => {
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
PENDING: {
|
||||
label: "Ожидает",
|
||||
label: "Ожидает одобрения поставщика",
|
||||
color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||
},
|
||||
CONFIRMED: {
|
||||
label: "Подтверждён",
|
||||
SUPPLIER_APPROVED: {
|
||||
label: "Ожидает подтверждения логистики",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
},
|
||||
IN_TRANSIT: {
|
||||
LOGISTICS_CONFIRMED: {
|
||||
label: "Ожидает отправки поставщиком",
|
||||
color: "bg-cyan-500/20 text-cyan-300 border-cyan-500/30",
|
||||
},
|
||||
SHIPPED: {
|
||||
label: "В пути",
|
||||
color: "bg-orange-500/20 text-orange-300 border-orange-500/30",
|
||||
},
|
||||
DELIVERED: {
|
||||
label: "Доставлен",
|
||||
label: "Доставлено",
|
||||
color: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Отменён",
|
||||
label: "Отменено",
|
||||
color: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||
},
|
||||
// Устаревшие статусы для обратной совместимости
|
||||
CONFIRMED: {
|
||||
label: "Подтверждён (устаревший)",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
},
|
||||
IN_TRANSIT: {
|
||||
label: "В пути (устаревший)",
|
||||
color: "bg-orange-500/20 text-orange-300 border-orange-500/30",
|
||||
},
|
||||
};
|
||||
|
||||
const config =
|
||||
@ -128,16 +141,24 @@ export function FulfillmentDetailedSuppliesTab() {
|
||||
const { user } = useAuth();
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
|
||||
// Мутация для обновления статуса заказа
|
||||
const [updateSupplyOrderStatus] = useMutation(UPDATE_SUPPLY_ORDER_STATUS, {
|
||||
// Убираем устаревшую мутацию updateSupplyOrderStatus
|
||||
|
||||
const [fulfillmentReceiveOrder] = useMutation(FULFILLMENT_RECEIVE_ORDER, {
|
||||
refetchQueries: [
|
||||
{ query: GET_SUPPLY_ORDERS }, // Обновляем заказы поставок
|
||||
{ query: GET_MY_SUPPLIES }, // Обновляем склад фулфилмента (расходники фулфилмента)
|
||||
{ query: GET_WAREHOUSE_PRODUCTS }, // Обновляем товары склада
|
||||
{ query: GET_SUPPLY_ORDERS },
|
||||
{ query: GET_MY_SUPPLIES },
|
||||
{ query: GET_WAREHOUSE_PRODUCTS },
|
||||
],
|
||||
onCompleted: (data) => {
|
||||
if (data.fulfillmentReceiveOrder.success) {
|
||||
toast.success(data.fulfillmentReceiveOrder.message);
|
||||
} else {
|
||||
toast.error(data.fulfillmentReceiveOrder.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error updating supply order status:", error);
|
||||
toast.error("Ошибка при обновлении статуса заказа");
|
||||
console.error("Error receiving supply order:", error);
|
||||
toast.error("Ошибка при приеме заказа поставки");
|
||||
},
|
||||
});
|
||||
|
||||
@ -177,30 +198,25 @@ export function FulfillmentDetailedSuppliesTab() {
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
// Функция для обновления статуса заказа
|
||||
const handleStatusUpdate = async (orderId: string, newStatus: string) => {
|
||||
// Убираем устаревшую функцию handleStatusUpdate
|
||||
|
||||
// Проверяем, можно ли принять заказ (для фулфилмента)
|
||||
const canReceiveOrder = (status: string) => {
|
||||
return status === "SHIPPED";
|
||||
};
|
||||
|
||||
// Функция для приема заказа фулфилментом
|
||||
const handleReceiveOrder = async (orderId: string) => {
|
||||
try {
|
||||
await updateSupplyOrderStatus({
|
||||
variables: {
|
||||
id: orderId,
|
||||
status: newStatus,
|
||||
},
|
||||
await fulfillmentReceiveOrder({
|
||||
variables: { id: orderId },
|
||||
});
|
||||
toast.success("Статус заказа обновлен");
|
||||
} catch (error) {
|
||||
console.error("Error updating status:", error);
|
||||
console.error("Error receiving order:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Проверяем, можно ли отметить как доставленный
|
||||
const canMarkAsDelivered = (status: string) => {
|
||||
return status === "IN_TRANSIT";
|
||||
};
|
||||
|
||||
// Проверяем, можно ли отметить как в пути
|
||||
const canMarkAsInTransit = (status: string) => {
|
||||
return status === "CONFIRMED";
|
||||
};
|
||||
// Убираем устаревшие функции проверки статусов
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@ -406,34 +422,24 @@ export function FulfillmentDetailedSuppliesTab() {
|
||||
<div className="flex items-center gap-2">
|
||||
{getStatusBadge(order.status)}
|
||||
|
||||
{/* Кнопка "В пути" для подтвержденных заказов */}
|
||||
{canMarkAsInTransit(order.status) && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleStatusUpdate(order.id, "IN_TRANSIT");
|
||||
}}
|
||||
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-300 border border-yellow-500/30 text-xs px-3 py-1 h-7"
|
||||
>
|
||||
<Truck className="h-3 w-3 mr-1" />В пути
|
||||
</Button>
|
||||
)}
|
||||
{/* Убираем устаревшую кнопку "В пути" */}
|
||||
|
||||
{/* Кнопка "Получено" для заказов в пути */}
|
||||
{canMarkAsDelivered(order.status) && (
|
||||
{/* Кнопка "Принять" для заказов в статусе SHIPPED */}
|
||||
{canReceiveOrder(order.status) && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleStatusUpdate(order.id, "DELIVERED");
|
||||
handleReceiveOrder(order.id);
|
||||
}}
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30 text-xs px-3 py-1 h-7"
|
||||
>
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Получено
|
||||
Принять
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Убираем устаревшую кнопку "Получено" */}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
Reference in New Issue
Block a user