Fix fulfillment consumables pricing architecture

- Add pricePerUnit field to Supply model for seller pricing
- Fix updateSupplyPrice mutation to update pricePerUnit only
- Separate purchase price (price) from selling price (pricePerUnit)
- Fix GraphQL mutations to include organization field (CREATE/UPDATE_LOGISTICS)
- Update GraphQL types to make Supply.price required again
- Add comprehensive pricing rules to rules-complete.md sections 11.7.5 and 18.8
- Fix supplies-tab.tsx to show debug info and handle user loading

Architecture changes:
• Supply.price = purchase price from supplier (immutable)
• Supply.pricePerUnit = selling price to sellers (mutable by fulfillment)
• Warehouse shows purchase price only (readonly)
• Services shows/edits selling price only

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Veronika Smirnova
2025-08-07 14:33:40 +03:00
parent cd7dcd9333
commit 0304f69410
6 changed files with 371 additions and 54 deletions

View File

@ -650,7 +650,6 @@ export const UPDATE_SUPPLY_PRICE = gql`
}
`
// Мутация для заказа поставки расходников
export const CREATE_SUPPLY_ORDER = gql`
mutation CreateSupplyOrder($input: SupplyOrderInput!) {
@ -746,6 +745,11 @@ export const CREATE_LOGISTICS = gql`
description
createdAt
updatedAt
organization {
id
name
fullName
}
}
}
}
@ -765,6 +769,11 @@ export const UPDATE_LOGISTICS = gql`
description
createdAt
updatedAt
organization {
id
name
fullName
}
}
}
}

View File

@ -718,7 +718,7 @@ export const resolvers = {
console.warn('🔥 SUPPLIES RESOLVER - NEW FORMAT:', {
organizationId: currentUser.organization.id,
suppliesCount: transformedSupplies.length,
supplies: transformedSupplies.map(s => ({
supplies: transformedSupplies.map((s) => ({
id: s.id,
name: s.name,
pricePerUnit: s.pricePerUnit,
@ -765,7 +765,7 @@ export const resolvers = {
// Расходники фулфилмента из склада (новая архитектура - синхронизация со склада)
myFulfillmentSupplies: async (_: unknown, __: unknown, context: Context) => {
console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES RESOLVER CALLED (NEW ARCHITECTURE) 🔥🔥🔥')
if (!context.user) {
console.warn('❌ No user in context')
throw new GraphQLError('Требуется авторизация', {
@ -826,7 +826,7 @@ export const resolvers = {
})
// Преобразуем в формат для фронтенда
return supplies.map(supply => ({
return supplies.map((supply) => ({
...supply,
price: supply.price ? parseFloat(supply.price.toString()) : 0,
shippedQuantity: 0, // Добавляем для совместимости
@ -1387,7 +1387,6 @@ export const resolvers = {
// Мои товары и расходники (для поставщиков)
myProducts: async (_: unknown, __: unknown, context: Context) => {
if (!context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
@ -1399,7 +1398,6 @@ export const resolvers = {
include: { organization: true },
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
@ -3607,7 +3605,6 @@ export const resolvers = {
}
},
// Обновить цену расходника (новая архитектура - только цену можно редактировать)
updateSupplyPrice: async (
_: unknown,
@ -3655,7 +3652,7 @@ export const resolvers = {
const updatedSupply = await prisma.supply.update({
where: { id: args.id },
data: {
price: args.input.pricePerUnit, // Обновляем только цену
pricePerUnit: args.input.pricePerUnit, // Обновляем цену продажи, НЕ цену закупки
updatedAt: new Date(),
},
include: { organization: true },
@ -4050,7 +4047,7 @@ export const resolvers = {
return {
name: product.name,
description: product.description || `Заказано у ${partner.name}`,
price: product.price,
price: product.price, // Цена закупки у поставщика
quantity: item.quantity,
unit: 'шт',
category: productWithCategory?.category?.name || 'Расходники',
@ -5830,7 +5827,7 @@ export const resolvers = {
data: {
name: item.product.name,
description: item.product.description || `Поставка от ${existingOrder.partner.name}`,
price: item.price,
price: item.price, // Цена закупки у поставщика
quantity: item.quantity,
unit: 'шт',
category: item.product.category?.name || 'Расходники',
@ -6590,7 +6587,7 @@ export const resolvers = {
description: isSellerSupply
? `Расходники селлера ${updatedOrder.organization?.name || updatedOrder.organization?.fullName}`
: item.product.description || `Расходники от ${updatedOrder.partner.name}`,
price: item.price,
price: item.price, // Цена закупки у поставщика
quantity: item.quantity,
currentStock: item.quantity,
usedStock: 0,

View File

@ -36,7 +36,7 @@ export const typeDefs = gql`
# Расходники селлеров (материалы клиентов)
mySupplies: [Supply!]!
# Доступные расходники для рецептур селлеров (только с ценой и в наличии)
getAvailableSuppliesForRecipe: [SupplyForRecipe!]!
@ -522,25 +522,25 @@ export const typeDefs = gql`
name: String!
description: String
# Новые поля для Services архитектуры
pricePerUnit: Float # Цена за единицу для рецептур (может быть null)
unit: String! # Единица измерения: "шт", "кг", "м"
warehouseStock: Int! # Остаток на складе (readonly)
isAvailable: Boolean! # Есть ли на складе (влияет на цвет)
pricePerUnit: Float # Цена за единицу для рецептур (может быть null)
unit: String! # Единица измерения: "шт", "кг", "м"
warehouseStock: Int! # Остаток на складе (readonly)
isAvailable: Boolean! # Есть ли на складе (влияет на цвет)
warehouseConsumableId: ID! # Связь со складом
# Поля из базы данных для обратной совместимости
price: Float! # Из Prisma schema
quantity: Int! # Из Prisma schema
category: String! # Из Prisma schema
status: String! # Из Prisma schema
date: DateTime! # Из Prisma schema
supplier: String! # Из Prisma schema
minStock: Int! # Из Prisma schema
currentStock: Int! # Из Prisma schema
usedStock: Int! # Из Prisma schema
type: String! # Из Prisma schema (SupplyType enum)
sellerOwnerId: ID # Из Prisma schema
sellerOwner: Organization # Из Prisma schema
shopLocation: String # Из Prisma schema
price: Float! # Цена закупки у поставщика (не меняется)
quantity: Int! # Из Prisma schema
category: String! # Из Prisma schema
status: String! # Из Prisma schema
date: DateTime! # Из Prisma schema
supplier: String! # Из Prisma schema
minStock: Int! # Из Prisma schema
currentStock: Int! # Из Prisma schema
usedStock: Int! # Из Prisma schema
type: String! # Из Prisma schema (SupplyType enum)
sellerOwnerId: ID # Из Prisma schema
sellerOwner: Organization # Из Prisma schema
shopLocation: String # Из Prisma schema
imageUrl: String
createdAt: DateTime!
updatedAt: DateTime!
@ -551,15 +551,15 @@ export const typeDefs = gql`
type SupplyForRecipe {
id: ID!
name: String!
pricePerUnit: Float! # Всегда не null
pricePerUnit: Float! # Всегда не null
unit: String!
imageUrl: String
warehouseStock: Int! # Всегда > 0
warehouseStock: Int! # Всегда > 0
}
# Для обновления цены расходника в разделе Услуги
input UpdateSupplyPriceInput {
pricePerUnit: Float # Может быть null (цена не установлена)
pricePerUnit: Float # Может быть null (цена не установлена)
}
input UseFulfillmentSuppliesInput {
@ -567,7 +567,7 @@ export const typeDefs = gql`
quantityUsed: Int!
description: String # Описание использования (например, "Подготовка 300 продуктов")
}
# Устаревшие типы для обратной совместимости
input SupplyInput {
name: String!