feat(pos): add write-off functionality for showcase kits
Add support for writing off showcase kits by creating a write-off document with components, converting reservations, and updating statuses. - Add `write_off_from_showcase` static method to ShowcaseManager - Add API endpoint `/pos/api/product-kits/<int:kit_id>/write-off/` - Add write-off button to POS terminal UI - Implement confirmation dialog with detailed information - Add redirect to write-off document detail page after success The write-off process includes: 1. Creating a write-off document in draft state 2. Converting existing reservations to write-off document items 3. Marking the showcase item as dismantled 4. Setting the product kit status to discontinued (if not already) Breaking Changes: No
This commit is contained in:
@@ -666,6 +666,113 @@ class ShowcaseManager:
|
||||
'message': f'Ошибка разбора: {str(e)}'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def write_off_from_showcase(showcase_item, reason='spoilage', notes=None, created_by=None):
|
||||
"""
|
||||
Списывает экземпляр витринного комплекта:
|
||||
1. Создаёт документ списания с компонентами комплекта
|
||||
2. Преобразует резервы комплекта в позиции документа списания
|
||||
3. Помечает экземпляр как разобранный
|
||||
|
||||
Args:
|
||||
showcase_item: ShowcaseItem - экземпляр для списания
|
||||
reason: str - причина списания (spoilage по умолчанию)
|
||||
notes: str - примечания
|
||||
created_by: User - пользователь
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
'success': bool,
|
||||
'document_id': int,
|
||||
'document_number': str,
|
||||
'items_count': int,
|
||||
'message': str,
|
||||
'error': str (при ошибке)
|
||||
}
|
||||
"""
|
||||
from inventory.services.writeoff_document_service import WriteOffDocumentService
|
||||
|
||||
# Проверка статуса
|
||||
if showcase_item.status == 'sold':
|
||||
return {
|
||||
'success': False,
|
||||
'document_id': None,
|
||||
'message': 'Нельзя списать проданный экземпляр'
|
||||
}
|
||||
|
||||
if showcase_item.status == 'dismantled':
|
||||
return {
|
||||
'success': False,
|
||||
'document_id': None,
|
||||
'message': 'Экземпляр уже разобран'
|
||||
}
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
warehouse = showcase_item.showcase.warehouse
|
||||
product_kit = showcase_item.product_kit
|
||||
|
||||
# Создаём документ списания (черновик)
|
||||
document = WriteOffDocumentService.create_document(
|
||||
warehouse=warehouse,
|
||||
date=timezone.now().date(),
|
||||
notes=f'Списание витринного комплекта: {product_kit.name}',
|
||||
created_by=created_by
|
||||
)
|
||||
|
||||
# Получаем резервы этого экземпляра
|
||||
reservations = Reservation.objects.filter(
|
||||
showcase_item=showcase_item,
|
||||
status='reserved'
|
||||
).select_related('product')
|
||||
|
||||
items_count = 0
|
||||
|
||||
for reservation in reservations:
|
||||
# Добавляем позицию в документ списания
|
||||
# Используем add_item без создания резерва (меняем статус существующего)
|
||||
from inventory.models import WriteOffDocumentItem
|
||||
|
||||
item = WriteOffDocumentItem.objects.create(
|
||||
document=document,
|
||||
product=reservation.product,
|
||||
quantity=reservation.quantity,
|
||||
reason=reason,
|
||||
notes=notes
|
||||
)
|
||||
|
||||
# Привязываем существующий резерв к позиции документа
|
||||
reservation.writeoff_document_item = item
|
||||
reservation.status = 'converted_to_writeoff'
|
||||
reservation.converted_at = timezone.now()
|
||||
reservation.save(update_fields=['writeoff_document_item', 'status', 'converted_at'])
|
||||
|
||||
items_count += 1
|
||||
|
||||
# Помечаем экземпляр как разобранный
|
||||
showcase_item.status = 'dismantled'
|
||||
showcase_item.save(update_fields=['status'])
|
||||
|
||||
# Помечаем шаблон комплекта как снятый
|
||||
if product_kit.status != 'discontinued':
|
||||
product_kit.status = 'discontinued'
|
||||
product_kit.save(update_fields=['status'])
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'document_id': document.id,
|
||||
'document_number': document.document_number,
|
||||
'items_count': items_count,
|
||||
'message': f'Создан документ {document.document_number} с {items_count} позициями'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'document_id': None,
|
||||
'message': f'Ошибка списания: {str(e)}'
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_showcase_items_for_pos(showcase=None):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user