Реализован функционал просмотра заявок покупателей на возврат от Wildberries API в фулфилмент-складе. Добавлена интеграция с WB API /api/v1/claims для получения заявок от всех партнеров-селлеров. Создан полнофункциональный интерфейс с поиском, фильтрацией по статусам, детальным просмотром заявок и отображением медиафайлов от покупателей.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Bivekich
2025-08-04 13:31:07 +03:00
parent 1d5d4906be
commit 17ffd6c9ed
6 changed files with 767 additions and 97 deletions

View File

@ -8171,6 +8171,147 @@ const wildberriesQueries = {
};
}
},
// Получение заявок покупателей на возврат от Wildberries от всех партнеров-селлеров
wbReturnClaims: async (
_: unknown,
{ isArchive, limit, offset }: { isArchive: boolean; limit?: number; offset?: number },
context: Context
) => {
if (!context.user) {
throw new GraphQLError("Требуется авторизация", {
extensions: { code: "UNAUTHENTICATED" },
});
}
try {
// Получаем текущую организацию пользователя (фулфилмент)
const user = await prisma.user.findUnique({
where: { id: context.user.id },
include: {
organization: true,
},
});
if (!user?.organization) {
throw new GraphQLError("У пользователя нет организации");
}
// Проверяем, что это фулфилмент организация
if (user.organization.type !== "FULFILLMENT") {
throw new GraphQLError("Доступ только для фулфилмент организаций");
}
// Получаем всех партнеров-селлеров с активными WB API ключами
const partnerSellerOrgs = await prisma.counterparty.findMany({
where: {
organizationId: user.organization.id,
},
include: {
counterparty: {
include: {
apiKeys: {
where: {
marketplace: "WILDBERRIES",
isActive: true,
},
},
},
},
},
});
// Фильтруем только селлеров с WB API ключами
const sellersWithWbKeys = partnerSellerOrgs.filter(
(partner) =>
partner.counterparty.type === "SELLER" &&
partner.counterparty.apiKeys.length > 0
);
if (sellersWithWbKeys.length === 0) {
return {
claims: [],
total: 0,
};
}
console.log(`Found ${sellersWithWbKeys.length} seller partners with WB keys`);
// Получаем заявки от всех селлеров параллельно
const claimsPromises = sellersWithWbKeys.map(async (partner) => {
const wbApiKey = partner.counterparty.apiKeys[0].apiKey;
const wbService = new WildberriesService(wbApiKey);
try {
const claimsResponse = await wbService.getClaims({
isArchive,
limit: Math.ceil((limit || 50) / sellersWithWbKeys.length), // Распределяем лимит между селлерами
offset: 0,
});
// Добавляем информацию о селлере к каждой заявке
const claimsWithSeller = claimsResponse.claims.map((claim) => ({
...claim,
sellerOrganization: {
id: partner.counterparty.id,
name: partner.counterparty.name || "Неизвестная организация",
inn: partner.counterparty.inn || "",
},
}));
console.log(`Got ${claimsWithSeller.length} claims from seller ${partner.counterparty.name}`);
return claimsWithSeller;
} catch (error) {
console.error(`Error fetching claims for seller ${partner.counterparty.name}:`, error);
return [];
}
});
const allClaims = (await Promise.all(claimsPromises)).flat();
console.log(`Total claims aggregated: ${allClaims.length}`);
// Сортируем по дате создания (новые первыми)
allClaims.sort((a, b) => new Date(b.dt).getTime() - new Date(a.dt).getTime());
// Применяем пагинацию
const paginatedClaims = allClaims.slice(offset || 0, (offset || 0) + (limit || 50));
console.log(`Paginated claims: ${paginatedClaims.length}`);
// Преобразуем в формат фронтенда
const transformedClaims = paginatedClaims.map((claim) => ({
id: claim.id,
claimType: claim.claim_type,
status: claim.status,
statusEx: claim.status_ex,
nmId: claim.nm_id,
userComment: claim.user_comment || "",
wbComment: claim.wb_comment || null,
dt: claim.dt,
imtName: claim.imt_name,
orderDt: claim.order_dt,
dtUpdate: claim.dt_update,
photos: claim.photos || [],
videoPaths: claim.video_paths || [],
actions: claim.actions || [],
price: claim.price,
currencyCode: claim.currency_code,
srid: claim.srid,
sellerOrganization: claim.sellerOrganization,
}));
console.log(`Returning ${transformedClaims.length} transformed claims to frontend`);
return {
claims: transformedClaims,
total: allClaims.length,
};
} catch (error) {
console.error("Error fetching WB return claims:", error);
throw new GraphQLError(
error instanceof Error ? error.message : "Ошибка получения заявок на возврат"
);
}
},
};
// Резолверы для внешней рекламы