a lot of
This commit is contained in:
@ -106,6 +106,7 @@ model Organization {
|
||||
supplyOrders SupplyOrder[]
|
||||
partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner")
|
||||
fulfillmentSupplyOrders SupplyOrder[] @relation("SupplyOrderFulfillmentCenter")
|
||||
logisticsSupplyOrders SupplyOrder[] @relation("SupplyOrderLogistics")
|
||||
wildberriesSupplies WildberriesSupply[]
|
||||
supplySuppliers SupplySupplier[] @relation("SupplySuppliers")
|
||||
externalAds ExternalAd[] @relation("ExternalAds")
|
||||
@ -472,6 +473,7 @@ model SupplyOrder {
|
||||
totalAmount Decimal @db.Decimal(12, 2)
|
||||
totalItems Int
|
||||
fulfillmentCenterId String?
|
||||
logisticsPartnerId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
organizationId String
|
||||
@ -479,6 +481,7 @@ model SupplyOrder {
|
||||
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
|
||||
partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id])
|
||||
fulfillmentCenter Organization? @relation("SupplyOrderFulfillmentCenter", fields: [fulfillmentCenterId], references: [id])
|
||||
logisticsPartner Organization? @relation("SupplyOrderLogistics", fields: [logisticsPartnerId], references: [id])
|
||||
|
||||
@@map("supply_orders")
|
||||
}
|
||||
|
@ -83,6 +83,8 @@ export function CreateFulfillmentConsumablesSupplyPage() {
|
||||
const { user } = useAuth();
|
||||
const [selectedSupplier, setSelectedSupplier] =
|
||||
useState<FulfillmentConsumableSupplier | null>(null);
|
||||
const [selectedLogistics, setSelectedLogistics] =
|
||||
useState<FulfillmentConsumableSupplier | null>(null);
|
||||
const [selectedConsumables, setSelectedConsumables] = useState<
|
||||
SelectedFulfillmentConsumable[]
|
||||
>([]);
|
||||
@ -113,6 +115,11 @@ export function CreateFulfillmentConsumablesSupplyPage() {
|
||||
counterpartiesData?.myCounterparties || []
|
||||
).filter((org: FulfillmentConsumableSupplier) => org.type === "WHOLESALE");
|
||||
|
||||
// Фильтруем только логистические компании
|
||||
const logisticsPartners = (
|
||||
counterpartiesData?.myCounterparties || []
|
||||
).filter((org: FulfillmentConsumableSupplier) => org.type === "LOGIST");
|
||||
|
||||
// Фильтруем поставщиков по поисковому запросу
|
||||
const filteredSuppliers = consumableSuppliers.filter(
|
||||
(supplier: FulfillmentConsumableSupplier) =>
|
||||
@ -258,6 +265,7 @@ export function CreateFulfillmentConsumablesSupplyPage() {
|
||||
deliveryDate: deliveryDate,
|
||||
// Для фулфилмента указываем себя как получателя (поставка на свой склад)
|
||||
fulfillmentCenterId: user?.organization?.id,
|
||||
logisticsPartnerId: selectedLogistics?.id,
|
||||
items: selectedConsumables.map((consumable) => ({
|
||||
productId: consumable.id,
|
||||
quantity: consumable.selectedQuantity,
|
||||
@ -784,6 +792,42 @@ export function CreateFulfillmentConsumablesSupplyPage() {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Выбор логистики */}
|
||||
<div className="mb-3">
|
||||
<label className="text-white/60 text-xs mb-1 block">
|
||||
Логистика (опционально):
|
||||
</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedLogistics?.id || ""}
|
||||
onChange={(e) => {
|
||||
const logisticsId = e.target.value;
|
||||
const logistics = logisticsPartners.find(p => p.id === logisticsId);
|
||||
setSelectedLogistics(logistics || null);
|
||||
}}
|
||||
className="w-full bg-white/10 border border-white/20 rounded-md px-3 py-2 text-white text-sm focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent appearance-none"
|
||||
>
|
||||
<option value="" className="bg-gray-800 text-white">
|
||||
Без логистики
|
||||
</option>
|
||||
{logisticsPartners.map((partner) => (
|
||||
<option
|
||||
key={partner.id}
|
||||
value={partner.id}
|
||||
className="bg-gray-800 text-white"
|
||||
>
|
||||
{partner.name || partner.fullName || partner.inn}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg className="w-4 h-4 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-white font-semibold text-sm">
|
||||
Итого:
|
||||
|
@ -16,8 +16,8 @@ import {
|
||||
import { DeliveryDetailsProps } from "./types";
|
||||
|
||||
const DELIVERY_STATUS_CONFIG = {
|
||||
delivered: {
|
||||
label: "Доставлено",
|
||||
"in-stock": {
|
||||
label: "На складе",
|
||||
color: "bg-green-500/20 text-green-300",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
@ -26,6 +26,22 @@ const DELIVERY_STATUS_CONFIG = {
|
||||
color: "bg-blue-500/20 text-blue-300",
|
||||
icon: Truck,
|
||||
},
|
||||
confirmed: {
|
||||
label: "Подтверждено",
|
||||
color: "bg-cyan-500/20 text-cyan-300",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
planned: {
|
||||
label: "Запланировано",
|
||||
color: "bg-yellow-500/20 text-yellow-300",
|
||||
icon: Clock,
|
||||
},
|
||||
// Обратная совместимость
|
||||
delivered: {
|
||||
label: "Доставлено",
|
||||
color: "bg-green-500/20 text-green-300",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
pending: {
|
||||
label: "Ожидание",
|
||||
color: "bg-yellow-500/20 text-yellow-300",
|
||||
|
@ -32,6 +32,27 @@ import {
|
||||
|
||||
// Статусы расходников с цветами
|
||||
const STATUS_CONFIG = {
|
||||
"in-stock": {
|
||||
label: "Доступен",
|
||||
color: "bg-green-500/20 text-green-300",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
"in-transit": {
|
||||
label: "В пути",
|
||||
color: "bg-blue-500/20 text-blue-300",
|
||||
icon: Clock,
|
||||
},
|
||||
confirmed: {
|
||||
label: "Подтверждено",
|
||||
color: "bg-cyan-500/20 text-cyan-300",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
planned: {
|
||||
label: "Запланировано",
|
||||
color: "bg-yellow-500/20 text-yellow-300",
|
||||
icon: Clock,
|
||||
},
|
||||
// Обратная совместимость и специальные статусы
|
||||
available: {
|
||||
label: "Доступен",
|
||||
color: "bg-green-500/20 text-green-300",
|
||||
@ -47,11 +68,6 @@ const STATUS_CONFIG = {
|
||||
color: "bg-red-500/20 text-red-300",
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
"in-transit": {
|
||||
label: "В пути",
|
||||
color: "bg-blue-500/20 text-blue-300",
|
||||
icon: Clock,
|
||||
},
|
||||
reserved: {
|
||||
label: "Зарезервирован",
|
||||
color: "bg-purple-500/20 text-purple-300",
|
||||
|
@ -477,6 +477,7 @@ export function FulfillmentWarehouseDashboard() {
|
||||
supplyOrders,
|
||||
allProducts,
|
||||
mySupplies,
|
||||
myFulfillmentSupplies,
|
||||
suppliesReceivedToday,
|
||||
suppliesUsedToday,
|
||||
productsReceivedToday,
|
||||
|
@ -15,7 +15,6 @@ export function SuppliesGrid({
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||||
{supplies.map((supply) => {
|
||||
const statusConfig = getStatusConfig(supply.status);
|
||||
const isExpanded = expandedSupplies.has(supply.id);
|
||||
const deliveries = getSupplyDeliveries(supply);
|
||||
|
||||
@ -26,7 +25,6 @@ export function SuppliesGrid({
|
||||
supply={supply}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpansion={onToggleExpansion}
|
||||
statusConfig={statusConfig}
|
||||
getSupplyDeliveries={getSupplyDeliveries}
|
||||
/>
|
||||
|
||||
|
@ -18,7 +18,6 @@ export function SupplyCard({
|
||||
supply,
|
||||
isExpanded,
|
||||
onToggleExpansion,
|
||||
statusConfig,
|
||||
getSupplyDeliveries,
|
||||
}: SupplyCardProps) {
|
||||
const formatCurrency = (amount: number) => {
|
||||
@ -33,7 +32,6 @@ export function SupplyCard({
|
||||
return new Intl.NumberFormat("ru-RU").format(num);
|
||||
};
|
||||
|
||||
const StatusIcon = statusConfig.icon;
|
||||
const isLowStock =
|
||||
supply.currentStock <= supply.minStock && supply.currentStock > 0;
|
||||
const stockPercentage =
|
||||
@ -58,12 +56,6 @@ export function SupplyCard({
|
||||
{supply.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2 ml-2">
|
||||
<Badge className={`${statusConfig.color} text-xs`}>
|
||||
<StatusIcon className="h-3 w-3 mr-1" />
|
||||
{statusConfig.label}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Основная информация */}
|
||||
|
@ -54,7 +54,6 @@ export interface SupplyCardProps {
|
||||
supply: Supply;
|
||||
isExpanded: boolean;
|
||||
onToggleExpansion: (id: string) => void;
|
||||
statusConfig: StatusConfig;
|
||||
getSupplyDeliveries: (supply: Supply) => Supply[];
|
||||
}
|
||||
|
||||
|
@ -728,33 +728,57 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
|
||||
// Получаем расходники селлеров из таблицы supply
|
||||
// Это расходники, созданные при доставке заказов от селлеров
|
||||
const existingSupplies = await prisma.supply.findMany({
|
||||
// Получаем ВСЕ расходники из таблицы supply для фулфилмента
|
||||
const allSupplies = await prisma.supply.findMany({
|
||||
where: { organizationId: currentUser.organization.id },
|
||||
include: { organization: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
// Получаем все заказы фулфилмента для себя (чтобы исключить их расходники)
|
||||
const fulfillmentOwnOrders = await prisma.supplyOrder.findMany({
|
||||
where: {
|
||||
organizationId: currentUser.organization.id, // Созданы фулфилментом
|
||||
fulfillmentCenterId: currentUser.organization.id, // Для себя
|
||||
status: "DELIVERED",
|
||||
},
|
||||
include: {
|
||||
items: {
|
||||
include: {
|
||||
product: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Создаем набор названий товаров из заказов фулфилмента для себя
|
||||
const fulfillmentProductNames = new Set(
|
||||
fulfillmentOwnOrders.flatMap((order) =>
|
||||
order.items.map((item) => item.product.name)
|
||||
)
|
||||
);
|
||||
|
||||
// Фильтруем расходники: исключаем те, что созданы заказами фулфилмента для себя
|
||||
const sellerSupplies = allSupplies.filter((supply) => {
|
||||
// Если расходник соответствует товару из заказа фулфилмента для себя,
|
||||
// то это расходник фулфилмента, а не селлера
|
||||
return !fulfillmentProductNames.has(supply.name);
|
||||
});
|
||||
|
||||
// Логирование для отладки
|
||||
console.log("🔥🔥🔥 SELLER SUPPLIES RESOLVER CALLED 🔥🔥🔥");
|
||||
console.log("📊 Расходники селлеров:", {
|
||||
organizationId: currentUser.organization.id,
|
||||
organizationType: currentUser.organization.type,
|
||||
existingSuppliesCount: existingSupplies.length,
|
||||
allSuppliesCount: allSupplies.length,
|
||||
fulfillmentOwnOrdersCount: fulfillmentOwnOrders.length,
|
||||
fulfillmentProductNames: Array.from(fulfillmentProductNames),
|
||||
filteredSellerSuppliesCount: sellerSupplies.length,
|
||||
sellerOrdersCount: sellerSupplyOrders.length,
|
||||
sellerOrders: sellerSupplyOrders.map((o) => ({
|
||||
id: o.id,
|
||||
sellerName: o.organization.name,
|
||||
supplierName: o.partner.name,
|
||||
status: o.status,
|
||||
itemsCount: o.items.length,
|
||||
})),
|
||||
});
|
||||
|
||||
// Возвращаем только расходники селлеров из таблицы supply
|
||||
// TODO: В будущем можно добавить фильтрацию по источнику заказа
|
||||
return existingSupplies;
|
||||
// Возвращаем только расходники селлеров (исключая расходники фулфилмента)
|
||||
return sellerSupplies;
|
||||
},
|
||||
|
||||
// Расходники фулфилмента (материалы для работы фулфилмента)
|
||||
@ -818,12 +842,14 @@ export const resolvers = {
|
||||
category: item.product.category?.name || "Расходники фулфилмента",
|
||||
status:
|
||||
order.status === "PENDING"
|
||||
? "in-transit"
|
||||
? "planned"
|
||||
: order.status === "CONFIRMED"
|
||||
? "in-transit"
|
||||
? "confirmed"
|
||||
: order.status === "IN_TRANSIT"
|
||||
? "in-transit"
|
||||
: "available",
|
||||
: order.status === "DELIVERED"
|
||||
? "in-stock"
|
||||
: "planned",
|
||||
date: order.createdAt,
|
||||
supplier: order.partner.name || order.partner.fullName || "Не указан",
|
||||
minStock: Math.round(item.quantity * 0.1),
|
||||
@ -3751,6 +3777,7 @@ export const resolvers = {
|
||||
totalItems: totalItems,
|
||||
organizationId: currentUser.organization.id,
|
||||
fulfillmentCenterId: fulfillmentCenterId,
|
||||
logisticsPartnerId: args.input.logisticsPartnerId,
|
||||
status: initialStatus,
|
||||
items: {
|
||||
create: orderItems,
|
||||
@ -3772,6 +3799,11 @@ export const resolvers = {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
logisticsPartner: {
|
||||
include: {
|
||||
users: true,
|
||||
},
|
||||
},
|
||||
items: {
|
||||
include: {
|
||||
product: {
|
||||
@ -3803,7 +3835,7 @@ export const resolvers = {
|
||||
quantity: item.quantity,
|
||||
unit: "шт",
|
||||
category: productWithCategory?.category?.name || "Расходники",
|
||||
status: "in-transit", // Статус "в пути" так как заказ только создан
|
||||
status: "planned", // Статус "запланировано" (ожидает одобрения поставщиком)
|
||||
date: new Date(args.input.deliveryDate),
|
||||
supplier: partner.name || partner.fullName || "Не указан",
|
||||
minStock: Math.round(item.quantity * 0.1), // 10% от заказанного как минимальный остаток
|
||||
@ -5288,10 +5320,53 @@ export const resolvers = {
|
||||
},
|
||||
});
|
||||
|
||||
// Если статус изменился на DELIVERED, обновляем склад фулфилмента
|
||||
if (args.status === "DELIVERED" && existingOrder.fulfillmentCenterId) {
|
||||
console.log("🚚 Обновляем склад фулфилмента:", {
|
||||
// Обновляем статусы расходников в зависимости от статуса заказа
|
||||
const targetOrganizationId = existingOrder.fulfillmentCenterId || existingOrder.organizationId;
|
||||
|
||||
if (args.status === "CONFIRMED") {
|
||||
// При подтверждении поставщиком - переводим расходники в статус "confirmed"
|
||||
await prisma.supply.updateMany({
|
||||
where: {
|
||||
organizationId: targetOrganizationId,
|
||||
status: "planned",
|
||||
// Находим расходники по названиям товаров из заказа
|
||||
name: {
|
||||
in: existingOrder.items.map(item => item.product.name)
|
||||
}
|
||||
},
|
||||
data: {
|
||||
status: "confirmed"
|
||||
}
|
||||
});
|
||||
|
||||
console.log("✅ Статусы расходников обновлены на 'confirmed'");
|
||||
}
|
||||
|
||||
if (args.status === "IN_TRANSIT") {
|
||||
// При отгрузке - переводим расходники в статус "in-transit"
|
||||
await prisma.supply.updateMany({
|
||||
where: {
|
||||
organizationId: targetOrganizationId,
|
||||
status: "confirmed",
|
||||
name: {
|
||||
in: existingOrder.items.map(item => item.product.name)
|
||||
}
|
||||
},
|
||||
data: {
|
||||
status: "in-transit"
|
||||
}
|
||||
});
|
||||
|
||||
console.log("✅ Статусы расходников обновлены на 'in-transit'");
|
||||
}
|
||||
|
||||
// Если статус изменился на DELIVERED, обновляем склад
|
||||
if (args.status === "DELIVERED") {
|
||||
|
||||
console.log("🚚 Обновляем склад организации:", {
|
||||
targetOrganizationId,
|
||||
fulfillmentCenterId: existingOrder.fulfillmentCenterId,
|
||||
organizationId: existingOrder.organizationId,
|
||||
itemsCount: existingOrder.items.length,
|
||||
items: existingOrder.items.map((item) => ({
|
||||
productName: item.product.name,
|
||||
@ -5299,19 +5374,19 @@ export const resolvers = {
|
||||
})),
|
||||
});
|
||||
|
||||
// Обновляем расходники фулфилмента
|
||||
// Обновляем расходники
|
||||
for (const item of existingOrder.items) {
|
||||
console.log("📦 Обрабатываем товар:", {
|
||||
productName: item.product.name,
|
||||
quantity: item.quantity,
|
||||
fulfillmentCenterId: existingOrder.fulfillmentCenterId,
|
||||
targetOrganizationId,
|
||||
});
|
||||
|
||||
// Ищем существующий расходник
|
||||
// Ищем существующий расходник в правильной организации
|
||||
const existingSupply = await prisma.supply.findFirst({
|
||||
where: {
|
||||
name: item.product.name,
|
||||
organizationId: existingOrder.fulfillmentCenterId,
|
||||
organizationId: targetOrganizationId,
|
||||
},
|
||||
});
|
||||
|
||||
@ -5329,14 +5404,14 @@ export const resolvers = {
|
||||
where: { id: existingSupply.id },
|
||||
data: {
|
||||
currentStock: existingSupply.currentStock + item.quantity,
|
||||
status: "available", // Меняем статус на "доступен"
|
||||
status: "in-stock", // Меняем статус на "на складе"
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log("➕ Создаем новый расходник:", {
|
||||
name: item.product.name,
|
||||
quantity: item.quantity,
|
||||
organizationId: existingOrder.fulfillmentCenterId,
|
||||
organizationId: targetOrganizationId,
|
||||
});
|
||||
|
||||
// Создаем новый расходник
|
||||
@ -5350,7 +5425,7 @@ export const resolvers = {
|
||||
quantity: item.quantity,
|
||||
unit: "шт",
|
||||
category: item.product.category?.name || "Расходники",
|
||||
status: "available",
|
||||
status: "in-stock",
|
||||
date: new Date(),
|
||||
supplier:
|
||||
existingOrder.partner.name ||
|
||||
@ -5358,7 +5433,7 @@ export const resolvers = {
|
||||
"Не указан",
|
||||
minStock: Math.round(item.quantity * 0.1),
|
||||
currentStock: item.quantity,
|
||||
organizationId: existingOrder.fulfillmentCenterId,
|
||||
organizationId: targetOrganizationId,
|
||||
},
|
||||
});
|
||||
|
||||
@ -5370,7 +5445,7 @@ export const resolvers = {
|
||||
}
|
||||
}
|
||||
|
||||
console.log("🎉 Склад фулфилмента успешно обновлен!");
|
||||
console.log("🎉 Склад организации успешно обновлен!");
|
||||
}
|
||||
|
||||
return {
|
||||
|
Reference in New Issue
Block a user