05/08 Phase 4

This commit is contained in:
2026-05-08 10:15:40 -04:00
parent 959f54ed84
commit bf9b54f6b5
26 changed files with 1509 additions and 42 deletions
+32
View File
@@ -0,0 +1,32 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}Adjust Stock — {{ item.name }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-5">
<div class="card shadow-sm">
<div class="card-header fw-semibold">Adjust Stock — {{ item.name }}</div>
<div class="card-body">
<p class="text-muted">Current quantity: <strong>{{ item.qty_on_hand }}</strong></p>
{% if error %}<div class="alert alert-danger py-2">{{ error }}</div>{% endif %}
<form method="POST" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Adjustment <small class="text-muted">(positive = add, negative = remove)</small></label>
<input type="number" class="form-control form-control-lg text-center" name="delta"
placeholder="e.g. +10 or -3" autofocus required>
</div>
<div class="mb-3">
<label class="form-label">Reason</label>
<input type="text" class="form-control" name="reason"
placeholder="e.g. Stock count, Supplier delivery, Damage">
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Apply Adjustment</button>
<a href="{{ url_for('inventory.index') }}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
+61
View File
@@ -0,0 +1,61 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}{{ 'Edit' if mode == 'edit' else 'New' }} Inventory Item{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-header fw-semibold">{{ 'Edit' if mode == 'edit' else 'Add' }} Inventory Item</div>
<div class="card-body">
{% if error %}<div class="alert alert-danger py-2">{{ error }}</div>{% endif %}
<form method="POST" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label">Name <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="name"
value="{{ item.name if item else '' }}" required autofocus>
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">SKU</label>
<input type="text" class="form-control font-monospace" name="sku"
value="{{ item.sku if item else '' }}">
</div>
<div class="col">
<label class="form-label">Category</label>
<input type="text" class="form-control" name="category"
value="{{ item.category if item else '' }}">
</div>
</div>
{% if mode == 'create' %}
<div class="mb-3">
<label class="form-label">Initial Quantity</label>
<input type="number" class="form-control" name="qty_on_hand" min="0" value="0">
</div>
{% endif %}
<div class="mb-3">
<label class="form-label">Reorder Level <small class="text-muted">(alert when stock reaches this)</small></label>
<input type="number" class="form-control" name="reorder_level" min="0"
value="{{ item.reorder_level if item else 5 }}">
</div>
<div class="row g-2 mb-3">
<div class="col">
<label class="form-label">Cost Price ($)</label>
<input type="number" step="0.01" class="form-control" name="cost_price" min="0"
value="{{ "%.2f"|format(item.cost_price) if item and item.cost_price else '' }}">
</div>
<div class="col">
<label class="form-label">Sale Price ($)</label>
<input type="number" step="0.01" class="form-control" name="sale_price" min="0"
value="{{ "%.2f"|format(item.sale_price) if item and item.sale_price else '' }}">
</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Save</button>
<a href="{{ url_for('inventory.index') }}" class="btn btn-outline-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
+52
View File
@@ -0,0 +1,52 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}Inventory{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="fw-bold mb-0"><i class="bi bi-box-seam me-2"></i>Inventory</h4>
<a href="{{ url_for('inventory.create') }}" class="btn btn-primary btn-sm">+ Add Item</a>
</div>
{% if low_stock %}
<div class="alert alert-warning d-flex align-items-center mb-3">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<div>
<strong>{{ low_stock|length }} item(s) at or below reorder level:</strong>
{{ low_stock|map(attribute='name')|join(', ') }}
</div>
</div>
{% endif %}
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr><th>Name</th><th>SKU</th><th>Category</th><th>On Hand</th><th>Reorder At</th><th>Cost</th><th>Sale</th><th></th></tr>
</thead>
<tbody>
{% for item in items %}
<tr class="{{ 'table-warning' if item.qty_on_hand <= item.reorder_level else '' }}">
<td class="fw-semibold">{{ item.name }}</td>
<td class="small text-muted font-monospace">{{ item.sku or '—' }}</td>
<td class="small">{{ item.category or '—' }}</td>
<td>
<span class="fw-bold {{ 'text-danger' if item.qty_on_hand <= item.reorder_level else '' }}">
{{ item.qty_on_hand }}
</span>
</td>
<td class="small">{{ item.reorder_level }}</td>
<td class="small">{{ '$' ~ "%.2f"|format(item.cost_price) if item.cost_price else '—' }}</td>
<td class="small">{{ '$' ~ "%.2f"|format(item.sale_price) if item.sale_price else '—' }}</td>
<td class="d-flex gap-1">
<a href="{{ url_for('inventory.adjust', item_id=item.id) }}" class="btn btn-outline-primary btn-sm">Adjust</a>
<a href="{{ url_for('inventory.edit', item_id=item.id) }}" class="btn btn-outline-secondary btn-sm">Edit</a>
<a href="{{ url_for('inventory.log', item_id=item.id) }}" class="btn btn-outline-secondary btn-sm">Log</a>
</td>
</tr>
{% else %}
<tr><td colspan="8" class="text-center text-muted py-4">No inventory items yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
+31
View File
@@ -0,0 +1,31 @@
{% extends "tenant/layouts/base.html" %}
{% block title %}Stock Log — {{ item.name }}{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="fw-bold mb-0">Stock Log — {{ item.name }}</h4>
<span class="badge bg-primary fs-6">Current: {{ item.qty_on_hand }} units</span>
</div>
<div class="card shadow-sm">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light"><tr><th>Date</th><th>Change</th><th>Reason</th></tr></thead>
<tbody>
{% for e in entries %}
<tr>
<td class="small text-muted">{{ e.created_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td class="fw-bold {{ 'text-success' if e.delta > 0 else 'text-danger' }}">
{{ '+' if e.delta > 0 else '' }}{{ e.delta }}
</td>
<td class="small">{{ e.reason or '—' }}</td>
</tr>
{% else %}
<tr><td colspan="3" class="text-center text-muted py-4">No log entries.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<a href="{{ url_for('inventory.index') }}" class="btn btn-outline-secondary btn-sm mt-3">
<i class="bi bi-arrow-left me-1"></i>Back to Inventory
</a>
{% endblock %}