mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-19 02:40:07 -06:00
feat: enhance inventory management features
- Update stock movement model with improved functionality - Enhance inventory routes and API endpoints - Improve inventory templates for movements, reports, and stock items - Add better history tracking and valuation reporting
This commit is contained in:
@@ -13,7 +13,7 @@ class StockMovement(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
movement_type = db.Column(
|
||||
db.String(20), nullable=False, index=True
|
||||
) # 'adjustment', 'transfer', 'sale', 'purchase', 'return', 'waste'
|
||||
) # 'adjustment', 'transfer', 'sale', 'rent', 'purchase', 'return', 'waste'
|
||||
stock_item_id = db.Column(db.Integer, db.ForeignKey("stock_items.id"), nullable=False, index=True)
|
||||
warehouse_id = db.Column(db.Integer, db.ForeignKey("warehouses.id"), nullable=False, index=True)
|
||||
quantity = db.Column(db.Numeric(10, 2), nullable=False) # Positive for additions, negative for removals
|
||||
@@ -307,6 +307,12 @@ class StockMovement(db.Model):
|
||||
return
|
||||
|
||||
# Outbound: consume FIFO (oldest first). Prefer a specific lot if provided (used after devaluation).
|
||||
# Special case: "rent" movements keep value in stock (don't consume from lots) for accounting purposes
|
||||
if qty < 0 and movement.movement_type == "rent":
|
||||
# For rent, we don't consume from lots - this keeps the value in inventory
|
||||
# while removing physical quantity from warehouse
|
||||
return
|
||||
|
||||
qty_to_consume = abs(qty)
|
||||
allow_negative = movement.movement_type == "adjustment"
|
||||
|
||||
|
||||
@@ -1906,7 +1906,12 @@ def reports_valuation():
|
||||
stock_item_id=item_detail["item_id"], warehouse_id=item_detail["warehouse_id"]
|
||||
).first()
|
||||
if stock:
|
||||
items_with_value.append({"stock": stock, "value": item_detail["value"]})
|
||||
items_with_value.append({
|
||||
"stock": stock,
|
||||
"value": item_detail["value"],
|
||||
"quantity": item_detail.get("quantity"),
|
||||
"cost": item_detail.get("cost")
|
||||
})
|
||||
|
||||
return render_template(
|
||||
"inventory/reports/valuation.html",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<option value="adjustment">{{ _('Adjustment') }}</option>
|
||||
<option value="transfer">{{ _('Transfer') }}</option>
|
||||
<option value="sale">{{ _('Sale') }}</option>
|
||||
<option value="rent">{{ _('Rent') }}</option>
|
||||
<option value="purchase">{{ _('Purchase') }}</option>
|
||||
<option value="return">{{ _('Return') }}</option>
|
||||
<option value="waste">{{ _('Waste') }}</option>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<option value="adjustment" {% if movement_type == 'adjustment' %}selected{% endif %}>{{ _('Adjustment') }}</option>
|
||||
<option value="transfer" {% if movement_type == 'transfer' %}selected{% endif %}>{{ _('Transfer') }}</option>
|
||||
<option value="sale" {% if movement_type == 'sale' %}selected{% endif %}>{{ _('Sale') }}</option>
|
||||
<option value="rent" {% if movement_type == 'rent' %}selected{% endif %}>{{ _('Rent') }}</option>
|
||||
<option value="purchase" {% if movement_type == 'purchase' %}selected{% endif %}>{{ _('Purchase') }}</option>
|
||||
<option value="return" {% if movement_type == 'return' %}selected{% endif %}>{{ _('Return') }}</option>
|
||||
<option value="waste" {% if movement_type == 'waste' %}selected{% endif %}>{{ _('Waste') }}</option>
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
<option value="adjustment" {% if selected_movement_type == 'adjustment' %}selected{% endif %}>{{ _('Adjustment') }}</option>
|
||||
<option value="transfer" {% if selected_movement_type == 'transfer' %}selected{% endif %}>{{ _('Transfer') }}</option>
|
||||
<option value="sale" {% if selected_movement_type == 'sale' %}selected{% endif %}>{{ _('Sale') }}</option>
|
||||
<option value="rent" {% if selected_movement_type == 'rent' %}selected{% endif %}>{{ _('Rent') }}</option>
|
||||
<option value="purchase" {% if selected_movement_type == 'purchase' %}selected{% endif %}>{{ _('Purchase') }}</option>
|
||||
<option value="return" {% if selected_movement_type == 'return' %}selected{% endif %}>{{ _('Return') }}</option>
|
||||
<option value="waste" {% if selected_movement_type == 'waste' %}selected{% endif %}>{{ _('Waste') }}</option>
|
||||
|
||||
@@ -78,8 +78,8 @@
|
||||
</td>
|
||||
<td class="p-4 font-mono text-sm">{{ item_data.stock.stock_item.sku }}</td>
|
||||
<td class="p-4">{{ item_data.stock.stock_item.category or '—' }}</td>
|
||||
<td class="p-4">{{ item_data.stock.quantity_on_hand }}</td>
|
||||
<td class="p-4">{{ "%.2f"|format(item_data.stock.stock_item.default_cost or 0) }} EUR</td>
|
||||
<td class="p-4">{{ "%.2f"|format(item_data.quantity) if item_data.quantity is not none else item_data.stock.quantity_on_hand }}</td>
|
||||
<td class="p-4">{{ "%.2f"|format(item_data.cost) if item_data.cost is not none else (item_data.stock.stock_item.default_cost or 0) }} EUR</td>
|
||||
<td class="p-4 font-semibold">{{ "%.2f"|format(item_data.value) }} EUR</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<option value="adjustment" {% if selected_movement_type == 'adjustment' %}selected{% endif %}>{{ _('Adjustment') }}</option>
|
||||
<option value="transfer" {% if selected_movement_type == 'transfer' %}selected{% endif %}>{{ _('Transfer') }}</option>
|
||||
<option value="sale" {% if selected_movement_type == 'sale' %}selected{% endif %}>{{ _('Sale') }}</option>
|
||||
<option value="rent" {% if selected_movement_type == 'rent' %}selected{% endif %}>{{ _('Rent') }}</option>
|
||||
<option value="purchase" {% if selected_movement_type == 'purchase' %}selected{% endif %}>{{ _('Purchase') }}</option>
|
||||
<option value="return" {% if selected_movement_type == 'return' %}selected{% endif %}>{{ _('Return') }}</option>
|
||||
<option value="waste" {% if selected_movement_type == 'waste' %}selected{% endif %}>{{ _('Waste') }}</option>
|
||||
|
||||
Reference in New Issue
Block a user