feat: Add signal handler for synchronizing Incoming edits with StockBatch

## Changes

### 1. Fixed missing signal handler for Incoming edit (inventory/signals.py)
- Added new signal handler `update_stock_batch_on_incoming_edit()` that:
  - Triggers when Incoming is edited (created=False)
  - Synchronizes StockBatch with new quantity and cost_price values
  - Automatically triggers cost price recalculation for the product
  - Updates Stock (inventory balance) for the warehouse
  - Includes proper logging and error handling

### 2. Created IncomingModelForm for editing individual incoming items (inventory/forms.py)
- New ModelForm: `IncomingModelForm` that:
  - Inherits from forms.ModelForm (accepts 'instance' parameter required by UpdateView)
  - Allows editing: product, quantity, cost_price, notes
  - Includes validation for positive quantity and non-negative cost_price
  - Filters only active products

### 3. Updated IncomingUpdateView (inventory/views/incoming.py)
- Changed form_class from IncomingForm to IncomingModelForm
- Updated imports to include IncomingModelForm
- Removed obsolete comments from form_valid method

## Architecture

When editing an Incoming item:
1. User submits form with new quantity/cost_price
2. form.save() triggers post_save signal (created=False)
3. update_stock_batch_on_incoming_edit() synchronizes StockBatch
4. StockBatch.save() triggers update_product_cost_on_batch_change()
5. Product.cost_price is recalculated with weighted average

## Problem Solved

Previously, editing an Incoming item would NOT:
- Update the related StockBatch
- Recalculate product cost_price
- Update warehouse inventory balance
- Maintain data consistency between Incoming and StockBatch

Now all these operations happen automatically through the signal chain.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-15 00:22:58 +03:00
parent 4a4bd437b9
commit 65a316649b
4 changed files with 216 additions and 9 deletions

View File

@@ -47,7 +47,7 @@
</label>
<textarea class="form-control {% if form.description.errors %}is-invalid{% endif %}"
id="{{ form.description.id_for_label }}" name="{{ form.description.html_name }}"
rows="4">{{ form.description.value|default:'' }}</textarea>
rows="3">{{ form.description.value|default:'' }}</textarea>
{% if form.description.errors %}
<div class="invalid-feedback">
{% for error in form.description.errors %}
@@ -57,6 +57,72 @@
{% endif %}
</div>
<h5 class="mt-4 mb-3">Адрес</h5>
<div class="row">
<div class="col-md-8 mb-3">
<label for="{{ form.street.id_for_label }}" class="form-label">
{{ form.street.label }}
</label>
{{ form.street }}
{% if form.street.errors %}
<div class="invalid-feedback d-block">
{% for error in form.street.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-4 mb-3">
<label for="{{ form.building_number.id_for_label }}" class="form-label">
{{ form.building_number.label }}
</label>
{{ form.building_number }}
{% if form.building_number.errors %}
<div class="invalid-feedback d-block">
{% for error in form.building_number.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<h5 class="mt-4 mb-3">Контакты</h5>
<div class="row">
<div class="col-md-6 mb-3">
<label for="{{ form.phone.id_for_label }}" class="form-label">
{{ form.phone.label }}
</label>
{{ form.phone }}
{% if form.phone.errors %}
<div class="invalid-feedback d-block">
{% for error in form.phone.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
<label for="{{ form.email.id_for_label }}" class="form-label">
{{ form.email.label }}
</label>
{{ form.email }}
{% if form.email.errors %}
<div class="invalid-feedback d-block">
{% for error in form.email.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
<h5 class="mt-4 mb-3">Настройки</h5>
<div class="mb-3">
<div class="form-check">
<input type="checkbox" class="form-check-input"
@@ -76,7 +142,21 @@
<label class="form-check-label" for="{{ form.is_default.id_for_label }}">
{{ form.is_default.label }}
<small class="text-muted d-block">
Отмечьте, чтобы использовать этот склад по умолчанию при создании новых документов
{{ form.is_default.help_text }}
</small>
</label>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input type="checkbox" class="form-check-input"
id="{{ form.is_pickup_point.id_for_label }}" name="{{ form.is_pickup_point.html_name }}"
{% if form.is_pickup_point.value %}checked{% endif %}>
<label class="form-check-label" for="{{ form.is_pickup_point.id_for_label }}">
{{ form.is_pickup_point.label }}
<small class="text-muted d-block">
{{ form.is_pickup_point.help_text }}
</small>
</label>
</div>