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

@ -54,12 +54,23 @@ export function SuppliesTab() {
const [isSaving, setIsSaving] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
// Debug информация
console.log('SuppliesTab - User:', user?.phone, 'Type:', user?.organization?.type)
// GraphQL запросы и мутации
const { data, loading, error, refetch } = useQuery(GET_MY_SUPPLIES, {
skip: user?.organization?.type !== 'FULFILLMENT',
skip: !user || user?.organization?.type !== 'FULFILLMENT',
})
const [updateSupplyPrice] = useMutation(UPDATE_SUPPLY_PRICE)
// Debug GraphQL запроса
console.log('SuppliesTab - Query:', {
skip: !user || user?.organization?.type !== 'FULFILLMENT',
loading,
error: error?.message,
dataLength: data?.mySupplies?.length,
})
const supplies = data?.mySupplies || []
// Преобразуем загруженные расходники в редактируемый формат
@ -130,7 +141,7 @@ export function SuppliesTab() {
if (field !== 'pricePerUnit') {
return // Только цену можно редактировать
}
setEditableSupplies((prev) =>
prev.map((supply) => {
if (supply.id !== supplyId) return supply
@ -155,7 +166,7 @@ export function SuppliesTab() {
for (const supply of suppliesToSave) {
// Проверяем валидность цены (может быть пустой)
const pricePerUnit = supply.pricePerUnit.trim() ? parseFloat(supply.pricePerUnit) : null
if (supply.pricePerUnit.trim() && (isNaN(pricePerUnit!) || pricePerUnit! <= 0)) {
toast.error('Введите корректную цену')
setIsSaving(false)
@ -187,9 +198,7 @@ export function SuppliesTab() {
}
// Сбрасываем флаги изменений
setEditableSupplies((prev) =>
prev.map((s) => ({ ...s, hasChanges: false, isEditing: false })),
)
setEditableSupplies((prev) => prev.map((s) => ({ ...s, hasChanges: false, isEditing: false })))
toast.success('Цены успешно обновлены')
} catch (error) {
@ -212,7 +221,9 @@ export function SuppliesTab() {
<div className="flex items-center justify-between mb-6">
<div>
<h2 className="text-lg font-semibold text-white mb-1">Расходники со склада</h2>
<p className="text-white/70 text-sm">Расходники появляются автоматически из поставок. Можно только установить цену.</p>
<p className="text-white/70 text-sm">
Расходники появляются автоматически из поставок. Можно только установить цену.
</p>
</div>
<div className="flex gap-3">
@ -261,7 +272,19 @@ export function SuppliesTab() {
</svg>
</div>
<h3 className="text-lg font-semibold text-white mb-2">Ошибка загрузки</h3>
<p className="text-white/70 text-sm mb-4">Не удалось загрузить расходники</p>
<p className="text-white/70 text-sm mb-4">
Не удалось загрузить расходники
{process.env.NODE_ENV === 'development' && (
<>
<br />
<span className="text-xs text-red-300">
Debug: {error.message}
<br />
User type: {user?.organization?.type}
</span>
</>
)}
</p>
<Button
onClick={() => refetch()}
className="bg-gradient-to-r from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white"
@ -301,9 +324,7 @@ export function SuppliesTab() {
key={supply.id || index}
className={`border-t border-white/10 hover:bg-white/5 ${
supply.hasChanges ? 'bg-blue-500/10' : ''
} ${
supply.isAvailable ? '' : 'opacity-60'
}`}
} ${supply.isAvailable ? '' : 'opacity-60'}`}
>
<td className="p-4 text-white/80">{index + 1}</td>
@ -346,13 +367,13 @@ export function SuppliesTab() {
<td className="p-4">
<span className="text-white font-medium">{supply.name}</span>
</td>
{/* Остаток на складе */}
<td className="p-4">
<div className="flex items-center gap-2">
<span className={`text-sm font-medium ${
supply.isAvailable ? 'text-green-400' : 'text-red-400'
}`}>
<span
className={`text-sm font-medium ${supply.isAvailable ? 'text-green-400' : 'text-red-400'}`}
>
{supply.warehouseStock}
</span>
{!supply.isAvailable && (
@ -362,7 +383,7 @@ export function SuppliesTab() {
)}
</div>
</td>
{/* Единица измерения */}
<td className="p-4">
<span className="text-white/80">{supply.unit}</span>
@ -382,7 +403,9 @@ export function SuppliesTab() {
/>
) : (
<span className="text-white/80">
{supply.pricePerUnit ? `${parseFloat(supply.pricePerUnit).toLocaleString()}` : 'Не установлена'}
{supply.pricePerUnit
? `${parseFloat(supply.pricePerUnit).toLocaleString()}`
: 'Не установлена'}
</span>
)}
</td>