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:
@ -75,7 +75,21 @@ export function FulfillmentSuppliesTab({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="supplies" className="mt-0 flex-1 overflow-hidden">
|
||||
{isWholesale ? <RealSupplyOrdersTab /> : <SellerSupplyOrdersTab />}
|
||||
{/* ВРЕМЕННО: Заменяем старый компонент на информационное сообщение */}
|
||||
{isWholesale ? (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">
|
||||
Используйте новый интерфейс
|
||||
</h3>
|
||||
<p className="text-white/60">
|
||||
Переходите в раздел "Входящие поставки" → "Расходники фулфилмента"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<SellerSupplyOrdersTab />
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { toast } from "sonner";
|
||||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||||
import { UPDATE_SUPPLY_ORDER_STATUS } from "@/graphql/mutations";
|
||||
import { UPDATE_SUPPLY_ORDER_STATUS, SUPPLIER_APPROVE_ORDER, SUPPLIER_REJECT_ORDER, SUPPLIER_SHIP_ORDER } from "@/graphql/mutations";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import {
|
||||
ChevronRight,
|
||||
@ -245,6 +245,53 @@ export function RealSupplyOrdersTab() {
|
||||
}
|
||||
);
|
||||
|
||||
// Мутации для поставщика
|
||||
const [supplierApproveOrder] = useMutation(SUPPLIER_APPROVE_ORDER, {
|
||||
refetchQueries: [{ query: GET_SUPPLY_ORDERS }],
|
||||
awaitRefetchQueries: true,
|
||||
onCompleted: (data) => {
|
||||
if (data.supplierApproveOrder.success) {
|
||||
toast.success(data.supplierApproveOrder.message);
|
||||
} else {
|
||||
toast.error(data.supplierApproveOrder.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error approving order:", error);
|
||||
toast.error("Ошибка при одобрении заказа");
|
||||
},
|
||||
});
|
||||
|
||||
const [supplierRejectOrder] = useMutation(SUPPLIER_REJECT_ORDER, {
|
||||
refetchQueries: [{ query: GET_SUPPLY_ORDERS }],
|
||||
onCompleted: (data) => {
|
||||
if (data.supplierRejectOrder.success) {
|
||||
toast.success(data.supplierRejectOrder.message);
|
||||
} else {
|
||||
toast.error(data.supplierRejectOrder.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error rejecting order:", error);
|
||||
toast.error("Ошибка при отклонении заказа");
|
||||
},
|
||||
});
|
||||
|
||||
const [supplierShipOrder] = useMutation(SUPPLIER_SHIP_ORDER, {
|
||||
refetchQueries: [{ query: GET_SUPPLY_ORDERS }],
|
||||
onCompleted: (data) => {
|
||||
if (data.supplierShipOrder.success) {
|
||||
toast.success(data.supplierShipOrder.message);
|
||||
} else {
|
||||
toast.error(data.supplierShipOrder.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error shipping order:", error);
|
||||
toast.error("Ошибка при отправке заказа");
|
||||
},
|
||||
});
|
||||
|
||||
// Получаем ID текущей организации (поставщика)
|
||||
const currentOrganizationId = user?.organization?.id;
|
||||
|
||||
@ -829,7 +876,66 @@ export function RealSupplyOrdersTab() {
|
||||
|
||||
<div className="px-3 py-2.5">
|
||||
<div className="flex items-center space-x-1">
|
||||
{order.status === "PENDING" && (
|
||||
{/* Кнопки для поставщика */}
|
||||
{console.log(`DEBUG: Заказ ${order.id.slice(-8)} - статус: ${order.status}, partnerId: ${order.partner?.id}, currentOrganizationId: ${currentOrganizationId}, показать кнопки: ${order.status === "PENDING" && order.partner?.id === currentOrganizationId}`)}
|
||||
{order.status === "PENDING" && order.partner?.id === currentOrganizationId && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
supplierApproveOrder({ variables: { id: order.id } });
|
||||
}}
|
||||
disabled={updating}
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30 text-xs px-2 py-1 h-6"
|
||||
>
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Одобрить
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
supplierRejectOrder({ variables: { id: order.id } });
|
||||
}}
|
||||
disabled={updating}
|
||||
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border border-red-500/30 text-xs px-2 py-1 h-6"
|
||||
>
|
||||
<XCircle className="h-3 w-3 mr-1" />
|
||||
Отклонить
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{order.status === "SUPPLIER_APPROVED" && (
|
||||
<div className="text-blue-300 text-xs">
|
||||
Ожидает подтверждения логистики
|
||||
</div>
|
||||
)}
|
||||
{order.status === "LOGISTICS_CONFIRMED" && order.partner?.id === currentOrganizationId && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
supplierShipOrder({ variables: { id: order.id } });
|
||||
}}
|
||||
disabled={updating}
|
||||
className="bg-orange-500/20 hover:bg-orange-500/30 text-orange-300 border border-orange-500/30 text-xs px-2 py-1 h-6"
|
||||
>
|
||||
<Truck className="h-3 w-3 mr-1" />
|
||||
Отправить
|
||||
</Button>
|
||||
)}
|
||||
{order.status === "SHIPPED" && (
|
||||
<div className="text-orange-300 text-xs">
|
||||
В пути - ожидает получения
|
||||
</div>
|
||||
)}
|
||||
{order.status === "DELIVERED" && (
|
||||
<div className="text-green-300 text-xs">
|
||||
Получено
|
||||
</div>
|
||||
)}
|
||||
{false && ( // Временно отключаем старую кнопку
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
|
Reference in New Issue
Block a user