Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions inventory/templates/inventory/sale_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -1346,10 +1346,6 @@ <h5 class="payment-title mb-3">{% if request.LANGUAGE_CODE == 'en' %}Payment Met
<i class="bi bi-wallet2"></i>
{% if request.LANGUAGE_CODE == 'en' %}Balance{% else %}余额{% endif %}
</button>
<button type="button" class="payment-btn" data-payment="credit">
<i class="bi bi-journal-text"></i>
{% if request.LANGUAGE_CODE == 'en' %}Credit{% else %}挂账{% endif %}
</button>
</div>
</div>
</div>
Expand Down
11 changes: 7 additions & 4 deletions inventory/templates/inventory/sale_item_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -476,16 +476,19 @@ <h4 class="card-title mb-0">添加销售商品</h4>
</tr>
</thead>
<tbody id="sale-items">
{% for item in sale.saleitems.all %}
{% for item in items %}
<tr>
<td>{{ item.product.name }}</td>
<td>{{ item.quantity }}</td>
<td>{{ item.actual_price }}</td>
<td>{{ item.subtotal }}</td>
<td>
<a href="{% url 'sale_item_delete' sale.id item.id %}" class="btn btn-sm btn-outline-danger" title="删除">
<i class="bi bi-trash"></i>
</a>
<form method="post" action="{% url 'sale_item_delete' sale.id item.id %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger" title="删除">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
Expand Down
37 changes: 37 additions & 0 deletions inventory/templates/inventory/system/delete_backup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends 'inventory/base.html' %}

{% block title %}删除备份 - {{ block.super }}{% endblock %}

{% block content %}
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h2 class="card-title mb-1">删除备份</h2>
<p class="text-muted mb-0">确认删除系统备份</p>
</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-8">
<div class="card border-danger">
<div class="card-header bg-danger text-white">
<h5 class="card-title mb-0">危险操作</h5>
</div>
<div class="card-body">
<p>确定要删除备份 <strong>{{ backup_name }}</strong> 吗?</p>
<p class="text-danger">此操作不可恢复。</p>

<form method="post" action="{% url 'delete_backup' backup_name %}">
{% csrf_token %}
<input type="hidden" name="confirm" value="on">
<a href="{% url 'backup_list' %}" class="btn btn-outline-secondary">取消</a>
<button type="submit" class="btn btn-danger">确认删除</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
52 changes: 52 additions & 0 deletions inventory/templates/inventory/system/maintenance.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{% extends 'inventory/base.html' %}

{% block title %}系统维护 - {{ block.super }}{% endblock %}

{% block content %}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-white py-2">
<li class="breadcrumb-item"><a href="{% url 'index' %}">主页</a></li>
<li class="breadcrumb-item"><a href="{% url 'system_settings' %}">系统设置</a></li>
<li class="breadcrumb-item active" aria-current="page">系统维护</li>
</ol>
</nav>

<h1 class="h3 mb-4">系统维护</h1>

<div class="row mb-4">
<div class="col-md-3 mb-3">
<div class="card h-100">
<div class="card-body">磁盘使用率:{{ disk_usage_percent }}%</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100">
<div class="card-body">内存使用率:{{ memory_usage_percent }}%</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100">
<div class="card-body">日志大小:{{ log_size_mb }} MB</div>
</div>
</div>
<div class="col-md-3 mb-3">
<div class="card h-100">
<div class="card-body">活跃会话:{{ active_sessions }}</div>
</div>
</div>
</div>

<div class="card">
<div class="card-header">维护操作</div>
<div class="card-body">
<form method="post" action="{% url 'system_maintenance' %}" class="d-flex flex-wrap gap-2">
{% csrf_token %}
<button type="submit" name="operation" value="clear_sessions" class="btn btn-outline-primary">清理过期会话</button>
<button type="submit" name="operation" value="clear_logs" class="btn btn-outline-secondary">清理日志</button>
<button type="submit" name="operation" value="optimize_db" class="btn btn-outline-success">优化数据库</button>
</form>
</div>
</div>
</div>
{% endblock %}
39 changes: 39 additions & 0 deletions inventory/templates/inventory/system/store_settings.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{% extends 'inventory/base.html' %}

{% block title %}商店设置 - {{ block.super }}{% endblock %}

{% block content %}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-white py-2">
<li class="breadcrumb-item"><a href="{% url 'index' %}">主页</a></li>
<li class="breadcrumb-item"><a href="{% url 'system_settings' %}">系统设置</a></li>
<li class="breadcrumb-item active" aria-current="page">商店设置</li>
</ol>
</nav>

<h1 class="h3 mb-4">商店设置</h1>

<div class="card">
<div class="card-body">
<form method="post" action="{% url 'store_settings' %}">
{% csrf_token %}
<div class="mb-3">
<label for="store_name" class="form-label">商店名称</label>
<input type="text" class="form-control" id="store_name" name="store_name" value="{{ store.name|default:'' }}" required>
</div>
<div class="mb-3">
<label for="address" class="form-label">地址</label>
<input type="text" class="form-control" id="address" name="address" value="{{ store.address|default:'' }}">
</div>
<div class="mb-3">
<label for="phone" class="form-label">联系电话</label>
<input type="text" class="form-control" id="phone" name="phone" value="{{ store.phone|default:'' }}">
</div>
<a href="{% url 'system_settings' %}" class="btn btn-outline-secondary">返回</a>
<button type="submit" class="btn btn-primary">保存设置</button>
</form>
</div>
</div>
</div>
{% endblock %}
76 changes: 76 additions & 0 deletions inventory/templates/inventory/system/system_info.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
{% extends 'inventory/base.html' %}

{% block title %}系统信息 - {{ block.super }}{% endblock %}

{% block content %}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb bg-white py-2">
<li class="breadcrumb-item"><a href="{% url 'index' %}">主页</a></li>
<li class="breadcrumb-item"><a href="{% url 'system_settings' %}">系统设置</a></li>
<li class="breadcrumb-item active" aria-current="page">系统信息</li>
</ol>
</nav>

<h1 class="h3 mb-4">系统信息</h1>

<div class="row">
<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header">运行环境</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-5">操作系统</dt>
<dd class="col-sm-7">{{ system_info.os }} {{ system_info.os_version }}</dd>
<dt class="col-sm-5">Python</dt>
<dd class="col-sm-7">{{ system_info.python_version }}</dd>
<dt class="col-sm-5">Django</dt>
<dd class="col-sm-7">{{ system_info.django_version }}</dd>
<dt class="col-sm-5">主机名</dt>
<dd class="col-sm-7">{{ system_info.hostname }}</dd>
<dt class="col-sm-5">服务器时间</dt>
<dd class="col-sm-7">{{ system_info.server_time|date:"Y-m-d H:i:s" }}</dd>
</dl>
</div>
</div>
</div>

<div class="col-lg-6 mb-4">
<div class="card h-100">
<div class="card-header">资源与数据</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-5">CPU 核心</dt>
<dd class="col-sm-7">{{ system_info.cpu_count }}</dd>
<dt class="col-sm-5">内存总量</dt>
<dd class="col-sm-7">{{ system_info.memory_total }} GB</dd>
<dt class="col-sm-5">可用内存</dt>
<dd class="col-sm-7">{{ system_info.memory_available }} GB</dd>
<dt class="col-sm-5">磁盘总量</dt>
<dd class="col-sm-7">{{ system_info.disk_total }} GB</dd>
<dt class="col-sm-5">磁盘剩余</dt>
<dd class="col-sm-7">{{ system_info.disk_free }} GB</dd>
<dt class="col-sm-5">媒体文件</dt>
<dd class="col-sm-7">{{ media_size_mb }} MB</dd>
<dt class="col-sm-5">日志文件</dt>
<dd class="col-sm-7">{{ log_size_mb }} MB / {{ log_entries }} 行</dd>
</dl>
</div>
</div>
</div>
</div>

<div class="card">
<div class="card-header">数据库记录数</div>
<div class="card-body">
<div class="row">
<div class="col-md-2 mb-2">商品:{{ db_stats.product_count }}</div>
<div class="col-md-2 mb-2">分类:{{ db_stats.category_count }}</div>
<div class="col-md-2 mb-2">库存:{{ db_stats.inventory_count }}</div>
<div class="col-md-2 mb-2">销售:{{ db_stats.sale_count }}</div>
<div class="col-md-2 mb-2">会员:{{ db_stats.member_count }}</div>
</div>
</div>
</div>
</div>
{% endblock %}
27 changes: 27 additions & 0 deletions inventory/tests/test_sale_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,33 @@ def test_delete_item_persists_recalculated_total(self):
sale.refresh_from_db()
self.assertEqual(sale.total_amount, Decimal('20.00')) # 删除后总额已落库

def test_sale_detail_does_not_rewrite_historical_amounts(self):
sale = self._make_sale(status='COMPLETED')
sale.total_amount = Decimal('1.00')
sale.discount_amount = Decimal('0.00')
sale.final_amount = Decimal('1.00')
sale.save()

response = self.client.get(reverse('sale_detail', args=[sale.id]))

self.assertEqual(response.status_code, 200)
sale.refresh_from_db()
self.assertEqual(sale.total_amount, Decimal('1.00'))
self.assertEqual(sale.final_amount, Decimal('1.00'))

def test_delete_item_requires_post(self):
sale = self._make_sale(status='DRAFT')
item = sale.items.get()
self.inventory.refresh_from_db()
before = self.inventory.quantity

response = self.client.get(reverse('sale_item_delete', args=[sale.id, item.id]))

self.assertRedirects(response, reverse('sale_detail', args=[sale.id]))
self.assertTrue(SaleItem.objects.filter(pk=item.pk).exists())
self.inventory.refresh_from_db()
self.assertEqual(self.inventory.quantity, before)

def test_sale_complete_page_renders_for_draft_sale(self):
sale = self._make_sale(status='DRAFT')

Expand Down
14 changes: 14 additions & 0 deletions inventory/tests/test_sales_balance_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,17 @@ def test_sale_create_balance_payment_rolls_back_when_balance_is_insufficient(sel
self.assertEqual(self.member.balance, Decimal('5.00'))
self.inventory.refresh_from_db()
self.assertEqual(self.inventory.quantity, 10)

def test_sale_create_rejects_unsupported_credit_payment(self):
response = self.client.post(reverse('sale_create'), self.sale_post_data('credit'))

self.assertRedirects(response, reverse('sale_create'))
self.assertFalse(Sale.objects.exists())
self.inventory.refresh_from_db()
self.assertEqual(self.inventory.quantity, 10)

def test_sale_create_page_does_not_offer_credit_payment(self):
response = self.client.get(reverse('sale_create'))

self.assertEqual(response.status_code, 200)
self.assertNotContains(response, 'data-payment="credit"')
Loading