Добавлены модели для внешней рекламы и кэша склада WB в схему Prisma. Обновлены компоненты AdvertisingTab и WBWarehouseDashboard для работы с новыми данными. Реализованы GraphQL запросы и мутации для управления внешней рекламой и кэшем склада. Оптимизирована логика отображения статистики и добавлены новые функции для работы с рекламой.

This commit is contained in:
Bivekich
2025-07-29 17:44:40 +03:00
parent 8b0d3cde00
commit c174a9f83c
13 changed files with 4576 additions and 780 deletions

View File

@ -1033,6 +1033,70 @@ export const REMOVE_FROM_FAVORITES = gql`
}
`;
// Мутации для внешней рекламы
export const CREATE_EXTERNAL_AD = gql`
mutation CreateExternalAd($input: ExternalAdInput!) {
createExternalAd(input: $input) {
success
message
externalAd {
id
name
url
cost
date
nmId
clicks
organizationId
createdAt
updatedAt
}
}
}
`;
export const UPDATE_EXTERNAL_AD = gql`
mutation UpdateExternalAd($id: ID!, $input: ExternalAdInput!) {
updateExternalAd(id: $id, input: $input) {
success
message
externalAd {
id
name
url
cost
date
nmId
clicks
organizationId
createdAt
updatedAt
}
}
}
`;
export const DELETE_EXTERNAL_AD = gql`
mutation DeleteExternalAd($id: ID!) {
deleteExternalAd(id: $id) {
success
message
externalAd {
id
}
}
}
`;
export const UPDATE_EXTERNAL_AD_CLICKS = gql`
mutation UpdateExternalAdClicks($id: ID!, $clicks: Int!) {
updateExternalAdClicks(id: $id, clicks: $clicks) {
success
message
}
}
`;
// Мутации для категорий
export const CREATE_CATEGORY = gql`
mutation CreateCategory($input: CategoryInput!) {
@ -1248,3 +1312,25 @@ export const UPDATE_SUPPLY_ORDER_STATUS = gql`
}
}
`;
// Мутации для кеша склада WB
export const SAVE_WB_WAREHOUSE_CACHE = gql`
mutation SaveWBWarehouseCache($input: WBWarehouseCacheInput!) {
saveWBWarehouseCache(input: $input) {
success
message
fromCache
cache {
id
organizationId
cacheDate
data
totalProducts
totalStocks
totalReserved
createdAt
updatedAt
}
}
}
`;

View File

@ -829,6 +829,27 @@ export const GET_WILDBERRIES_CAMPAIGNS_LIST = gql`
}
`
export const GET_EXTERNAL_ADS = gql`
query GetExternalAds($dateFrom: String!, $dateTo: String!) {
getExternalAds(dateFrom: $dateFrom, dateTo: $dateTo) {
success
message
externalAds {
id
name
url
cost
date
nmId
clicks
organizationId
createdAt
updatedAt
}
}
}
`
// Админ запросы
export const ADMIN_ME = gql`
query AdminMe {
@ -932,3 +953,25 @@ export const GET_PENDING_SUPPLIES_COUNT = gql`
}
}
`;
// Запросы для кеша склада WB
export const GET_WB_WAREHOUSE_DATA = gql`
query GetWBWarehouseData {
getWBWarehouseData {
success
message
fromCache
cache {
id
organizationId
cacheDate
data
totalProducts
totalStocks
totalReserved
createdAt
updatedAt
}
}
}
`;

View File

@ -5017,6 +5017,59 @@ export const resolvers = {
};
}
},
updateExternalAdClicks: async (
_: unknown,
{ id, clicks }: { id: string; clicks: 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("Организация не найдена");
}
// Проверяем, что реклама принадлежит организации пользователя
const existingAd = await prisma.externalAd.findFirst({
where: {
id,
organizationId: user.organization.id,
},
});
if (!existingAd) {
throw new GraphQLError("Внешняя реклама не найдена");
}
await prisma.externalAd.update({
where: { id },
data: { clicks },
});
return {
success: true,
message: "Клики успешно обновлены",
externalAd: null,
};
} catch (error) {
console.error("Error updating external ad clicks:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка обновления кликов",
externalAd: null,
};
}
},
},
// Резолверы типов
@ -6019,14 +6072,394 @@ const wildberriesQueries = {
},
};
// Резолверы для внешней рекламы
const externalAdQueries = {
getExternalAds: async (
_: unknown,
{ dateFrom, dateTo }: { dateFrom: string; dateTo: string },
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("Организация не найдена");
}
const externalAds = await prisma.externalAd.findMany({
where: {
organizationId: user.organization.id,
date: {
gte: new Date(dateFrom),
lte: new Date(dateTo + 'T23:59:59.999Z'),
},
},
orderBy: {
date: 'desc',
},
});
return {
success: true,
message: null,
externalAds: externalAds.map(ad => ({
...ad,
cost: parseFloat(ad.cost.toString()),
date: ad.date.toISOString().split('T')[0],
createdAt: ad.createdAt.toISOString(),
updatedAt: ad.updatedAt.toISOString(),
})),
};
} catch (error) {
console.error("Error fetching external ads:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка получения внешней рекламы",
externalAds: [],
};
}
},
};
const externalAdMutations = {
createExternalAd: async (
_: unknown,
{ input }: { input: { name: string; url: string; cost: number; date: string; nmId: string } },
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("Организация не найдена");
}
const externalAd = await prisma.externalAd.create({
data: {
name: input.name,
url: input.url,
cost: input.cost,
date: new Date(input.date),
nmId: input.nmId,
organizationId: user.organization.id,
},
});
return {
success: true,
message: "Внешняя реклама успешно создана",
externalAd: {
...externalAd,
cost: parseFloat(externalAd.cost.toString()),
date: externalAd.date.toISOString().split('T')[0],
createdAt: externalAd.createdAt.toISOString(),
updatedAt: externalAd.updatedAt.toISOString(),
},
};
} catch (error) {
console.error("Error creating external ad:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка создания внешней рекламы",
externalAd: null,
};
}
},
updateExternalAd: async (
_: unknown,
{ id, input }: { id: string; input: { name: string; url: string; cost: number; date: string; nmId: string } },
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("Организация не найдена");
}
// Проверяем, что реклама принадлежит организации пользователя
const existingAd = await prisma.externalAd.findFirst({
where: {
id,
organizationId: user.organization.id,
},
});
if (!existingAd) {
throw new GraphQLError("Внешняя реклама не найдена");
}
const externalAd = await prisma.externalAd.update({
where: { id },
data: {
name: input.name,
url: input.url,
cost: input.cost,
date: new Date(input.date),
nmId: input.nmId,
},
});
return {
success: true,
message: "Внешняя реклама успешно обновлена",
externalAd: {
...externalAd,
cost: parseFloat(externalAd.cost.toString()),
date: externalAd.date.toISOString().split('T')[0],
createdAt: externalAd.createdAt.toISOString(),
updatedAt: externalAd.updatedAt.toISOString(),
},
};
} catch (error) {
console.error("Error updating external ad:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка обновления внешней рекламы",
externalAd: null,
};
}
},
deleteExternalAd: async (
_: unknown,
{ id }: { id: string },
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("Организация не найдена");
}
// Проверяем, что реклама принадлежит организации пользователя
const existingAd = await prisma.externalAd.findFirst({
where: {
id,
organizationId: user.organization.id,
},
});
if (!existingAd) {
throw new GraphQLError("Внешняя реклама не найдена");
}
await prisma.externalAd.delete({
where: { id },
});
return {
success: true,
message: "Внешняя реклама успешно удалена",
externalAd: null,
};
} catch (error) {
console.error("Error deleting external ad:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка удаления внешней рекламы",
externalAd: null,
};
}
},
};
// Резолверы для кеша склада WB
const wbWarehouseCacheQueries = {
getWBWarehouseData: async (
_: unknown,
__: unknown,
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("Организация не найдена");
}
// Получаем текущую дату без времени
const today = new Date();
today.setHours(0, 0, 0, 0);
// Ищем кеш за сегодня
const cache = await prisma.wBWarehouseCache.findFirst({
where: {
organizationId: user.organization.id,
cacheDate: today,
},
orderBy: {
createdAt: 'desc',
},
});
if (cache) {
// Возвращаем данные из кеша
return {
success: true,
message: "Данные получены из кеша",
cache: {
...cache,
cacheDate: cache.cacheDate.toISOString().split('T')[0],
createdAt: cache.createdAt.toISOString(),
updatedAt: cache.updatedAt.toISOString(),
},
fromCache: true,
};
} else {
// Кеша нет, нужно загрузить данные из API
return {
success: true,
message: "Кеш не найден, требуется загрузка из API",
cache: null,
fromCache: false,
};
}
} catch (error) {
console.error("Error getting WB warehouse cache:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка получения кеша склада WB",
cache: null,
fromCache: false,
};
}
},
};
const wbWarehouseCacheMutations = {
saveWBWarehouseCache: async (
_: unknown,
{ input }: { input: { data: string; totalProducts: number; totalStocks: number; totalReserved: 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("Организация не найдена");
}
// Получаем текущую дату без времени
const today = new Date();
today.setHours(0, 0, 0, 0);
// Используем upsert для создания или обновления кеша
const cache = await prisma.wBWarehouseCache.upsert({
where: {
organizationId_cacheDate: {
organizationId: user.organization.id,
cacheDate: today,
},
},
update: {
data: input.data,
totalProducts: input.totalProducts,
totalStocks: input.totalStocks,
totalReserved: input.totalReserved,
},
create: {
organizationId: user.organization.id,
cacheDate: today,
data: input.data,
totalProducts: input.totalProducts,
totalStocks: input.totalStocks,
totalReserved: input.totalReserved,
},
});
return {
success: true,
message: "Кеш склада WB успешно сохранен",
cache: {
...cache,
cacheDate: cache.cacheDate.toISOString().split('T')[0],
createdAt: cache.createdAt.toISOString(),
updatedAt: cache.updatedAt.toISOString(),
},
fromCache: false,
};
} catch (error) {
console.error("Error saving WB warehouse cache:", error);
return {
success: false,
message: error instanceof Error ? error.message : "Ошибка сохранения кеша склада WB",
cache: null,
fromCache: false,
};
}
},
};
// Добавляем админ запросы и мутации к основным резолверам
resolvers.Query = {
...resolvers.Query,
...adminQueries,
...wildberriesQueries,
...externalAdQueries,
...wbWarehouseCacheQueries,
};
resolvers.Mutation = {
...resolvers.Mutation,
...adminMutations,
...externalAdMutations,
...wbWarehouseCacheMutations,
};

View File

@ -108,6 +108,12 @@ export const typeDefs = gql`
# Список кампаний Wildberries
getWildberriesCampaignsList: WildberriesCampaignsListResponse!
# Типы для внешней рекламы
getExternalAds(dateFrom: String!, dateTo: String!): ExternalAdsResponse!
# Типы для кеша склада WB
getWBWarehouseData: WBWarehouseCacheResponse!
}
type Mutation {
@ -244,6 +250,12 @@ export const typeDefs = gql`
# Админ мутации
adminLogin(username: String!, password: String!): AdminAuthResponse!
adminLogout: Boolean!
# Типы для внешней рекламы
createExternalAd(input: ExternalAdInput!): ExternalAdResponse!
updateExternalAd(id: ID!, input: ExternalAdInput!): ExternalAdResponse!
deleteExternalAd(id: ID!): ExternalAdResponse!
updateExternalAdClicks(id: ID!, clicks: Int!): ExternalAdResponse!
}
# Типы данных
@ -1147,4 +1159,84 @@ export const typeDefs = gql`
advertId: Int!
changeTime: String!
}
# Типы для внешней рекламы
type ExternalAd {
id: ID!
name: String!
url: String!
cost: Float!
date: String!
nmId: String!
clicks: Int!
organizationId: String!
createdAt: String!
updatedAt: String!
}
input ExternalAdInput {
name: String!
url: String!
cost: Float!
date: String!
nmId: String!
}
type ExternalAdResponse {
success: Boolean!
message: String
externalAd: ExternalAd
}
type ExternalAdsResponse {
success: Boolean!
message: String
externalAds: [ExternalAd!]!
}
extend type Query {
getExternalAds(dateFrom: String!, dateTo: String!): ExternalAdsResponse!
}
extend type Mutation {
createExternalAd(input: ExternalAdInput!): ExternalAdResponse!
updateExternalAd(id: ID!, input: ExternalAdInput!): ExternalAdResponse!
deleteExternalAd(id: ID!): ExternalAdResponse!
updateExternalAdClicks(id: ID!, clicks: Int!): ExternalAdResponse!
}
# Типы для кеша склада WB
type WBWarehouseCache {
id: ID!
organizationId: String!
cacheDate: String!
data: String! # JSON строка с данными
totalProducts: Int!
totalStocks: Int!
totalReserved: Int!
createdAt: String!
updatedAt: String!
}
type WBWarehouseCacheResponse {
success: Boolean!
message: String
cache: WBWarehouseCache
fromCache: Boolean! # Указывает, получены ли данные из кеша
}
input WBWarehouseCacheInput {
data: String! # JSON строка с данными склада
totalProducts: Int!
totalStocks: Int!
totalReserved: Int!
}
extend type Query {
getWBWarehouseData: WBWarehouseCacheResponse!
}
extend type Mutation {
saveWBWarehouseCache(input: WBWarehouseCacheInput!): WBWarehouseCacheResponse!
}
`;