- Добавлена реализация метода test_connection() с обработкой различных HTTP статусов
- Реализованы вспомогательные методы _get_api_url() и _get_auth() для работы с API
- Добавлена интеграция WooCommerceService в get_integration_service()
- Настроены поля формы для WooCommerceIntegration в get_form_fields_meta()
fix(inventory): исправить расчет цены продажи в базовых единицах
- Исправлен расчет sale_price в SaleProcessor с учетом conversion_factor_snapshot
- Обновлен расчет цены в сигнале create_sale_on_order_completion для корректной работы с sales_unit
- Добавлен параметр lock в get_batches_for_fifo() для блокировки строк
- Используется select_for_update() в write_off_by_fifo() для предотвращения
параллельной перезаписи quantity при одновременном списании из одной партии
- Защита от потери данных при параллельном завершении заказов
- inventory/signals.py: обработчик изменения статуса Order
* При смене статуса на 'завершён' (is_positive_end=True): reserved → sold
* При смене на 'отменён' (is_negative_end=True): reserved → available
- inventory/services/showcase_manager.py: метод reserve_for_order()
* Переводит ShowcaseItem: in_cart → reserved
* Создаёт жёсткую связь с OrderItem
* Автоматическое управление статусами через сигналы
- Транзакционная безопасность через @transaction.atomic
Проблемы:
1. При продаже витринного комплекта через POS создавались дубликаты резервов
- reserve_stock_on_item_create создавал новые резервы для витринного комплекта
- Хотя резервы уже существовали от ShowcaseManager.reserve_kit_to_showcase
2. При переходе заказа в Completed создавались дубликаты Sale
- ShowcaseManager.sell_showcase_items создавал Sale
- Затем сигнал create_sale_on_order_completion создавал Sale повторно
3. При отмене заказа (Completed → Cancelled) терялась связь ShowcaseItem с резервами
- ShowcaseItem возвращался на витрину, но резервы теряли поле showcase_item
- При повторном переходе в Completed резервы дублировались
Исправления:
1. inventory/signals.py - reserve_stock_on_item_create (строки 165-180):
- Добавлена проверка витринного комплекта (is_temporary && showcase)
- Для витринных комплектов сигнал пропускает создание новых резервов
- Привязка существующих резервов происходит в update_reservation_on_item_change
2. inventory/signals.py - create_sale_on_order_completion (строки 346-365):
- Добавлена проверка уже обработанных резервов (status='converted_to_sale')
- Сигнал пропускает витринные резервы, уже обработанные ShowcaseManager
- Логирует информацию о пропущенных резервах
3. inventory/signals.py - rollback_sale_on_status_change (строки 746-774):
- При возврате ShowcaseItem на витрину восстанавливается связь с резервами
- Обновляется поле showcase_item в резервах через Reservation.objects.update()
- Логируется количество восстановленных связей
4. inventory/services/showcase_manager.py - sell_showcase_items (строки 201-206):
- Добавлена проверка статуса резерва перед созданием Sale
- Если резерв уже в 'converted_to_sale', он пропускается
- Защита от двойного списания одного резерва
Результат:
✅ Резервы создаются только один раз при размещении на витрине
✅ Sale создаются только один раз при продаже
✅ ShowcaseItem корректно возвращается на витрину со связью с резервами
✅ Остатки на складе корректные (60 → 55 после продажи, 60 после отмены)
✅ Нет дублирования при многократных переходах Completed ↔ Cancelled
feat(inventory): introduce stock deficit notifications and base quantity tracking
- Added `quantity_base` field to reservation model for precise inventory calculations
- Implemented non-blocking stock deficit warnings during kit creation process
- Enhanced API responses with warning details for frontend display
- Updated terminal interface to show formatted stock shortage alerts
BREAKING CHANGE: API response structure now includes `warnings` array instead of previous stock warning format
Implement functionality to allow sales even when stock is insufficient, tracking pending quantities and resolving them when new stock arrives via incoming documents. This includes new fields in Sale model (is_pending_cost, pending_quantity), updates to batch manager for negative write-offs, and signal handlers for automatic processing.
- Add is_pending_cost and pending_quantity fields to Sale model
- Modify write_off_by_fifo to support allow_negative flag and return pending quantity
- Update incoming document service to allocate pending sales to new batches
- Enhance sale processor and signals to handle pending sales
- Remove outdated tests.py file
- Add migration for new Sale fields
- Обновлён pos/views.py: метод pos_checkout теперь создаёт Order и связанную модель Delivery
- Обновлён showcase_manager.py: метод sell_showcase_item_to_customer использует новую архитектуру
- Удалён устаревший скрипт create_demo_orders.py
- Исправлена ошибка 'property is_delivery of Order object has no setter'
КРИТИЧНО: Все агрегации Reservation.quantity заменены на quantity_base
Проблемы и решения:
🔴 КРИТИЧНО - BatchManager.write_off_by_fifo():
- Проблема: суммировал quantity вместо quantity_base
- Влияние: FIFO расчет свободного товара был некорректен
- Решение: aggregate(Sum('quantity_base')) в строках 118, 125
🟡 СРЕДНЯЯ ВАЖНОСТЬ - ShowcaseManager:
- reserve_showcase_item(): обновление quantity и quantity_base (строка 403)
- release_showcase_reservation(): обновление обоих полей (строка 481)
- Теперь витринные резервы полностью консистентны
🟡 СРЕДНЯЯ ВАЖНОСТЬ - TransformationService:
- confirm(): проверка доступности через quantity_base (строка 254)
- Корректная валидация при трансформации товаров
🟢 НИЗКАЯ ВАЖНОСТЬ - WriteOffDocumentService:
- update_item(): синхронизация quantity и quantity_base (строка 175)
- Полнота данных в резервах документов списания
🟢 НИЗКАЯ ВАЖНОСТЬ - Сигналы (signals.py):
- update_order_item_reservation(): обновление обоих полей для товаров
- Для обычных товаров: quantity_base = quantity_in_base_units (строка 1081)
- Для комплектов: quantity_base = quantity (компоненты в базовых) (строка 1107)
- Добавлено обновление sales_unit при изменении OrderItem
Архитектура:
- Принцип: quantity_base ВСЕГДА содержит количество в базовых единицах
- Все агрегации резервов используют quantity_base для корректных расчетов
- quantity сохраняется для совместимости и отображения
- sales_unit хранит ссылку на единицу продажи для аудита
Add new models UnitOfMeasure and ProductSalesUnit to enable selling products in different units (e.g., bunches, kg). Update Product model with base_unit field and methods for unit conversions and availability. Extend Sale, Reservation, and OrderItem models with sales_unit fields and snapshots. Modify SaleProcessor to handle quantity conversions. Include admin interfaces for managing units. Add corresponding database migrations.
Создан TenantOnboardingService как единый источник истины для:
- Активации заявки на регистрацию тенанта
- Создания Client, Domain, Subscription
- Инициализации системных данных (Customer, статусы, способы оплаты, склад, витрина)
Новые сервисы:
- TenantOnboardingService (tenants/services/onboarding.py)
- WarehouseService (inventory/services/warehouse_service.py)
- ShowcaseService (inventory/services/showcase_service.py)
- PaymentMethodService (orders/services/payment_method_service.py)
Рефакторинг:
- admin.py: 220 строк → 5 строк (делегирование сервису)
- init_tenant_data.py: 259 строк → 68 строк
- activate_registration.py: использует сервис
- Тесты обновлены для вызова сервиса напрямую
При создании тенанта автоматически создаются склад и витрина по умолчанию.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Текущее состояние перед рефакторингом Transfer → TransferDocument.
Все изменения с последнего коммита по улучшению системы поступлений.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Исправлена проблема, когда при отмене проведенной трансформации оба сигнала выполнялись последовательно:
- rollback_transformation_on_cancel возвращал резервы в 'reserved'
- release_reservations_on_draft_cancel ошибочно освобождал их в 'released'
Изменена проверка в release_reservations_on_draft_cancel: вместо проверки наличия партий Output (которые уже удалены) теперь проверяется статус резервов ('converted_to_transformation') или наличие поля converted_at, что работает независимо от порядка выполнения сигналов.
Реализована полная система трансформации товаров (превращение одного товара в другой).
Пример: белая гипсофила → крашеная гипсофила.
Особенности реализации:
- Резервирование входных товаров в статусе draft
- FIFO списание входных товаров при проведении
- Автоматический расчёт себестоимости выходных товаров
- Возможность отмены как черновиков, так и проведённых трансформаций
Модели (inventory/models.py):
- Transformation: документ трансформации (draft/completed/cancelled)
- TransformationInput: входные товары (списание)
- TransformationOutput: выходные товары (оприходование)
- Добавлен статус 'converted_to_transformation' в Reservation
- Добавлен тип 'transformation' в DocumentCounter
Бизнес-логика (inventory/services/transformation_service.py):
- TransformationService с методами CRUD
- Валидация наличия товаров
- Автоматическая генерация номеров документов
Сигналы (inventory/signals.py):
- Автоматическое резервирование входных товаров
- FIFO списание при проведении
- Создание партий выходных товаров
- Откат операций при отмене
Интерфейс без Django Admin:
- Список трансформаций (list.html)
- Форма создания (form.html)
- Детальный просмотр с добавлением товаров (detail.html)
- Интеграция с компонентом поиска товаров
- 8 views для полного CRUD + проведение/отмена
Миграция:
- 0003_alter_documentcounter_counter_type_and_more.py
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Добавлены поля snapshot_* в модель InventoryLine для фиксации значений на момент завершения
- Обновлен InventoryProcessor для сохранения snapshot перед обработкой
- Обновлен InventoryDetailView для отображения snapshot-значений в проведенных инвентаризациях
- Добавлена миграция 0018 для новых полей
- Теперь в проведенных инвентаризациях отображаются оригинальные значения и правильная разница, а не текущие скорректированные остатки
- Автоматическое проведение документов списания и оприходования после завершения инвентаризации
- Оптимизация SQL-запросов: устранение N+1, bulk-операции для Stock, агрегация для StockBatch и Reservation
- Изменение формулы расчета разницы: (quantity_fact + quantity_reserved) - quantity_available
- Переименование поля 'По факту' в 'Подсчитано (факт, свободные)'
- Добавлены столбцы 'В резервах' и 'Всего на складе' в таблицу инвентаризации
- Перемещение столбца 'В системе (свободно)' после 'В резервах' с визуальным выделением
- Центральное выравнивание значений в столбцах таблицы
- Автоматическое выделение текста при фокусе на поле ввода количества
- Исправление форматирования разницы (убраны лишние нули)
- Изменение статуса 'Не обработана' на 'Не проведено'
- Добавление номера документа для инвентаризаций (INV-XXXXXX)
- Отображение всех типов списаний в debug-странице (WriteOff, WriteOffDocument, WriteOffDocumentItem)
- Улучшение отображения документов в детальном просмотре инвентаризации с возможностью перехода к ним
- Унифицирован формат номеров документов: IN-XXXXXX (6 цифр), как WO-XXXXXX и MOVE-XXXXXX
- Убрано дублирование функции _extract_number_from_document_number
- Оптимизирована инициализация счетчика incoming: быстрая проверка перед полной инициализацией
- Удален неиспользуемый файл utils.py (функциональность перенесена в document_generator.py)
- Все функции генерации номеров используют единый подход через DocumentCounter.get_next_value()
- Добавлено поле receipt_type в модель IncomingBatch с типами: supplier, inventory, adjustment
- Исправлен баг в InventoryProcessor: теперь корректно создается IncomingBatch при инвентаризации
- Создан IncomingAdjustmentCreateView для оприходования без инвентаризации
- Обновлены формы, шаблоны и админка для поддержки разных типов поступлений
- Добавлена навигация и URL для оприходования
- Тип поступления отображается в списках приходов и партий
- Изменён TIME_ZONE с Europe/Moscow на Europe/Minsk в settings.py
- Исправлено создание заказов в POS: теперь используется timezone.localtime() для корректной конвертации UTC → Minsk
- delivery_date и delivery_time_start/end теперь сохраняются в минском времени, а не UTC
- Исправлена разница в 3 часа между временем создания заказа и фактическим временем
- Удалено устаревшее поле payment_method из showcase_manager.py (поле было удалено из модели Order)
Теперь все временные метки заказов соответствуют реальному минскому времени
- Добавлены методы reserve_product_to_showcase и release_showcase_reservation в ShowcaseManager
- Методы работают с резервами для всех активных экземпляров витринного комплекта
- НЕ блокируют создание резерва при нехватке товара, возвращают информацию о дефиците (overdraft)
- Обновлён API endpoint update_product_kit для корректировки резервов при изменении состава
- Добавлено визуальное предупреждение на фронте о нехватке товара на складе
- В модалке редактирования комплекта добавлены контролы для изменения количества товаров (+/-, поле ввода, удаление)
- Автоматический пересчёт цен при изменении состава
- Очистка корзины POS после успешного создания витринного комплекта
Проблема:
При продаже витринного комплекта резервы оставались в статусе 'reserved'
вместо 'converted_to_sale'. Товары из состава комплекта не освобождались.
Причина:
В методе sell_showcase_items порядок операций был неправильный:
1. create_sale_from_reservation вызывался ПЕРВЫМ
2. reservation.order_item устанавливался ПОСЛЕ
В SaleProcessor.create_sale_from_reservation есть логика:
if order and reservation.order_item:
sale_price = reservation.order_item.price
else:
sale_price = reservation.product.actual_price
Так как order_item был None, цена бралась из product.actual_price,
а не из OrderItem, и резерв не конвертировался корректно.
Решение:
Правильный порядок операций:
1. Устанавливаем reservation.order_item = order_item
2. Сохраняем reservation
3. Вызываем create_sale_from_reservation (теперь order_item доступен)
4. Обновляем статус на 'converted_to_sale'
5. Сохраняем финальное состояние
Теперь резервы корректно преобразуются в продажи с правильной ценой
из позиции заказа, и товары освобождаются после продажи.
Проблема:
При попытке продажи 2+ экземпляров одного витринного букета возникала ошибка
IntegrityError, так как поле sold_order_item было OneToOneField.
Это означало что к одному OrderItem мог быть привязан только один ShowcaseItem,
что делало невозможной продажу нескольких экземпляров в одной позиции заказа.
Решение:
1. Изменен тип поля sold_order_item с OneToOneField на ForeignKey
- Теперь несколько ShowcaseItem могут относиться к одному OrderItem
- related_name изменен с 'sold_showcase_item' на 'sold_showcase_items'
2. Обновлен метод mark_sold в модели ShowcaseItem
- Добавлена явная проверка статуса 'sold' перед продажей
- Генерируется ValidationError если экземпляр уже продан
- Удален комментарий про OneToOneField защиту
3. Обновлена обработка ошибок в ShowcaseManager.sell_showcase_items
- Убрана обработка IntegrityError
- Добавлена обработка ValidationError от mark_sold
4. Создана миграция 0012_change_sold_order_item_to_fk
Теперь можно успешно продавать 2 и более экземпляров одного витринного букета
в рамках одной позиции заказа.
Проблема: При продаже 2+ экземпляров одного витринного комплекта возникала
ошибка 'Один из экземпляров уже был продан'. Это происходило потому что
объекты ShowcaseItem проверялись по старому состоянию из памяти.
Причина:
- При вызове sell_showcase_items() передавался список объектов из запроса
- Первый ShowcaseItem менял статус на 'sold' через mark_sold()
- Второй объект в списке все еще имел старый статус из памяти
- Проверка в цикле срабатывала некорректно
Решение:
- Перезагружаем ВСЕ ShowcaseItem из БД с блокировкой перед обработкой
- Используем select_for_update() для получения актуального статуса
- Теперь каждый экземпляр проверяется по свежим данным из БД
- Защита от race conditions через database-level locking
Результат:
Теперь можно продавать 2+ экземпляра одного букета без ошибок.
Проблема:
- Резервы документов списания помечались как 'converted_to_sale'
- Это вводило в заблуждение - списание это не продажа
- В админке резервы списания отображались как 'В продажу'
Решение:
- Добавлен новый статус 'converted_to_writeoff' в Reservation.STATUS_CHOICES
- Увеличен max_length поля status с 20 до 25 символов
- Обновлен WriteOffDocumentService.confirm_document() - теперь использует новый статус
- Обновлено описание поля converted_at (теперь для продажи ИЛИ списания)
- Создана миграция 0011_add_writeoff_status_to_reservation
Изменения:
- inventory/models.py: добавлен статус, увеличен max_length, обновлен help_text
- inventory/services/writeoff_document_service.py: используется converted_to_writeoff
- inventory/migrations/0011_*.py: миграция для изменений модели
Влияние:
- Чистая аналитика: можно отличить продажи от списаний
- Корректный учёт Stock: статус влияет на quantity_reserved
- Защита от ошибок при будущих доработках (откат списания)
- Создан WriteOffDocumentService с методами работы с документами списания
- create_document() - создание документа с автогенерацией номера (WO-XXXXXX)
- add_item() - добавление позиции с автоматическим созданием резерва
- update_item() - обновление позиции с пересчетом резерва
- remove_item() - удаление позиции с освобождением резерва
- confirm_document() - проведение документа (создание WriteOff записей по FIFO)
- cancel_document() - отмена с освобождением всех резервов
- Добавлена валидация доступного количества товара при создании/обновлении позиций
- Добавлена функция generate_writeoff_document_number() для генерации номеров документов
Новая архитектура:
- ShowcaseItem модель - физический экземпляр букета на витрине
- OneToOneField(sold_order_item) - БД-уровневая защита от двойной продажи
- Поддержка создания нескольких экземпляров одного букета
- Возможность продавать N из M доступных (например 2 из 5)
Изменения:
- inventory/models.py: добавлена модель ShowcaseItem с методами lock/unlock/mark_sold
- inventory/services/showcase_manager.py: переработан для работы с ShowcaseItem
- pos/views.py: API поддерживает quantity и showcase_item_ids
- pos/templates/pos/terminal.html: поле "Сколько букетов создать"
- pos/static/pos/js/terminal.js: выбор количества, передача showcase_item_ids
Миграции:
- 0007: создание модели ShowcaseItem
- 0008: data migration существующих букетов
- 0009: очистка ShowcaseItem для уже проданных букетов
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Проблема: При переводе заказа в статус 'completed' возникала ошибка
"Не удалось создать Sale для заказа", т.к. резервы этого же заказа
блокировали списание товара.
Причина: write_off_by_fifo() считал все резервы со статусом 'reserved'
как занятые, включая резервы текущего заказа, которые ещё не были
переведены в 'converted_to_sale'.
Решение:
- Добавлен параметр exclude_order в write_off_by_fifo() для исключения
резервов конкретного заказа из расчёта занятого товара
- SaleProcessor.create_sale() теперь передаёт order в write_off_by_fifo()
- Добавлены транзакции в views для атомарности операций с заказами:
при ошибке в сигналах статус заказа откатывается вместе со всеми
связанными изменениями
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Исправлен метод write_off_by_fifo() для учета зарезервированных партий
- Добавлено автоматическое проставление даты и времени при создании заказов в POS
- Исправлена ошибка фильтрации Product (is_active -> status='active') в transfers
- Предотвращает списание из зарезервированных партий, устраняя отрицательные остатки
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 10 шт создавал Sale на 10 шт, но со склада уходило 20 шт
Найдено ДВЕ причины:
1. Повторное обновление резервов через .save() (inventory/signals.py)
- Резервы обновлялись через res.save() каждый раз при сохранении заказа
- Это вызывало сигнал update_stock_on_reservation_change
- При повторном сохранении заказа происходило двойное срабатывание
Решение:
- Проверка дубликатов ПЕРЕД обновлением резервов
- Замена .save() на .update() для массового обновления без вызова сигналов
- Ручное обновление Stock после .update()
2. Двойное FIFO-списание (inventory/services/sale_processor.py)
- Sale создавалась с processed=False
- Сигнал process_sale_fifo срабатывал и списывал товар (1-й раз)
- Затем SaleProcessor.create_sale() тоже списывал товар (2-й раз)
Решение:
- Sale создаётся сразу с processed=True
- Сигнал не срабатывает, списание только в сервисе
Дополнительно:
- Ограничен выбор статусов при создании заказа только промежуточными
- Статус 'Черновик' установлен по умолчанию
- Убран пустой выбор '-------' из поля статуса
Изменённые файлы:
- myproject/orders/forms.py - настройки статусов для формы заказа
- myproject/inventory/signals.py - исправление сигнала create_sale_on_order_completion
- myproject/inventory/services/sale_processor.py - исправление create_sale
- myproject/test_order_status_default.py - обновлён тест
- DOUBLE_SALE_FIX.md - документация по исправлению
## 1. Add cart lock validation to sell_from_showcase()
- Prevent selling showcase kits locked in another cashier's cart
- Check cart_lock_expires_at before allowing direct sales
- Return clear error message with lock holder's name and time remaining
- File: inventory/services/showcase_manager.py
## 2. Improve error handling in POS create_temp_kit_to_showcase()
- Add detailed logging for all error types (JSON, validation, generic)
- Provide user-friendly error messages instead of generic 500
- Log full context (kit name, showcase ID, items, user) for debugging
- Categorize errors: stock issues, integrity, locks, not found
- File: pos/views.py
## 3. Fix critical bug in create_temporary_kit()
- Replace non-existent is_active field with status='active'
- Affects 3 locations: kit creation, product lookup, kit duplication
- This was causing 500 errors when creating temporary kits from order edit
- File: products/services/kit_service.py
## 4. Improve error handling in create_temporary_kit_api()
- Add comprehensive logging for order creation endpoint
- Provide specific error messages for common failure scenarios
- Help diagnose issues when creating kits from order editing UI
- File: products/views/api_views.py
These changes complete the Soft Lock system and fix the 500 error issue.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed:
- Replace is_active with status='active' for Product filtering in IncomingModelForm
- Product model uses status field instead of is_active
Added:
- Showcase field to ProductKit for tracking showcase placement
- product_kit field to Reservation for tracking kit-specific reservations
- Disassemble button in POS terminal for showcase kits
- API endpoint for kit disassembly (release reservations, mark discontinued)
- Improved reservation filtering when dismantling specific kits
Changes:
- ShowcaseManager now links reservations to specific kit instances
- POS terminal modal shows disassemble button in edit mode
- Kit disassembly properly updates stock aggregates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Создана модель Showcase (витрина) привязанная к складу
- Расширена Reservation для поддержки витринных резервов
- Добавлены поля в OrderItem для маркировки витринных продаж
- Реализован ShowcaseManager с методами резервирования, продажи и разбора
- Обновлён админ-интерфейс для управления витринами
- Добавлена кнопка Витрина в POS (категории) и API для просмотра
- Добавлена кнопка На витрину в панели действий POS
- Миграции готовы к применению
Основные изменения:
- Создана модель IncomingBatch для группировки товаров по документам
- Каждое поступление (Incoming) связано с одной батчем поступления
- Автоматическое создание StockBatch для каждого товара в приходе
- Реализована система нумерации партий (IN-XXXX-XXXX) с поиском максимума в БД
- Обновлены все представления (views) для работы с новой архитектурой
- Добавлены детальные страницы просмотра партий поступлений
- Обновлены шаблоны для отображения информации о партиях и их товарах
- Исправлена логика сигналов для создания StockBatch при приходе товара
- Обновлены формы для работы с новой структурой IncomingBatch
Архитектура FIFO:
- IncomingBatch: одна партия поступления (номер IN-XXXX-XXXX)
- Incoming: товар в партии поступления
- StockBatch: одна партия товара на складе (создается для каждого товара)
Это позволяет системе правильно применять FIFO при продаже товаров.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>