管理后台
This commit is contained in:
parent
3fb5192b4b
commit
53ce69a1f9
|
|
@ -15,6 +15,7 @@ server {
|
|||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
client_max_body_size 50m;
|
||||
}
|
||||
|
||||
# 上传文件代理
|
||||
|
|
|
|||
|
|
@ -55,16 +55,9 @@
|
|||
<el-table-column prop="id" label="ID" width="70" />
|
||||
<el-table-column prop="name" label="商品名称" min-width="150" />
|
||||
<el-table-column prop="style_no" label="款号" width="120" />
|
||||
<el-table-column prop="stock" label="当前库存" width="100" />
|
||||
<el-table-column prop="total_stock" label="总库存" width="100" />
|
||||
<el-table-column label="库存比例" width="140">
|
||||
<el-table-column prop="stock" label="库存" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-progress
|
||||
:percentage="row.total_stock > 0 ? Math.round((row.stock / row.total_stock) * 100) : 0"
|
||||
:color="row.stock / row.total_stock < 0.2 ? '#f56c6c' : '#e6a23c'"
|
||||
:stroke-width="16"
|
||||
:text-inside="true"
|
||||
/>
|
||||
<el-tag :type="row.stock <= 5 ? 'danger' : 'warning'" size="small">{{ row.stock }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
|
|
|
|||
|
|
@ -45,11 +45,7 @@
|
|||
<el-input-number v-model="form.stock" :min="0" controls-position="right" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="总库存" prop="totalStock">
|
||||
<el-input-number v-model="form.totalStock" :min="0" controls-position="right" style="width:100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 总库存已隐藏 -->
|
||||
</el-row>
|
||||
<el-row :gutter="32">
|
||||
<el-col :span="12">
|
||||
|
|
@ -318,7 +314,7 @@
|
|||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8"><el-form-item label="金耗"><el-input-number v-model="specForm.goldLoss" :precision="4" disabled controls-position="right" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="金价"><el-input-number v-model="specForm.goldPrice" :precision="2" :min="0" controls-position="right" style="width:100%" /></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="金价"><el-input-number v-model="specForm.goldPrice" :precision="2" disabled controls-position="right" style="width:100%" /><div style="font-size:11px;color:#999;margin-top:2px">读取自「金价配置」</div></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="金值"><el-input-number v-model="specForm.goldValue" :precision="2" disabled controls-position="right" style="width:100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<div class="dialog-section">主石信息</div>
|
||||
|
|
@ -431,7 +427,7 @@ const rules = {
|
|||
basePrice: [{ required: true, message: '请输入基础价格', trigger: 'blur' }],
|
||||
styleNo: [{ required: true, message: '请输入款号', trigger: 'blur' }],
|
||||
stock: [{ required: true, message: '请输入库存', trigger: 'blur' }],
|
||||
totalStock: [{ required: true, message: '请输入总库存', trigger: 'blur' }],
|
||||
|
||||
loss: [{ required: true, message: '请输入损耗', trigger: 'blur' }],
|
||||
laborCost: [{ required: true, message: '请输入工费', trigger: 'blur' }],
|
||||
categoryId: [{ required: true, validator: (_r: any, _v: any, cb: any) => form.categoryId.length > 0 ? cb() : cb(new Error('请选择分类')), trigger: 'change' }],
|
||||
|
|
@ -681,8 +677,9 @@ async function handleSubmit() {
|
|||
router.replace(`/products/${res.data.id}/edit`)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('保存失败')
|
||||
} catch (e: any) {
|
||||
const msg = e?.response?.data?.message || '保存失败'
|
||||
ElMessage.error(msg)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import pool from '../utils/db'
|
|||
import { RowDataPacket } from 'mysql2'
|
||||
|
||||
/**
|
||||
* Filter products where stock/totalStock < 0.1, sorted by stock ascending.
|
||||
* Filter products where stock < 20, sorted by stock ascending.
|
||||
* Pure logic extracted for testability.
|
||||
*/
|
||||
export function filterStockAlerts(
|
||||
products: { id: number; name: string; style_no: string; stock: number; total_stock: number }[]
|
||||
products: { id: number; name: string; style_no: string; stock: number }[]
|
||||
): typeof products {
|
||||
return products
|
||||
.filter((p) => p.total_stock > 0 && p.stock / p.total_stock < 0.1)
|
||||
.filter((p) => p.stock < 20)
|
||||
.sort((a, b) => a.stock - b.stock)
|
||||
}
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ export function filterStockAlerts(
|
|||
export async function getStockAlerts(_req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const [rows] = await pool.execute<RowDataPacket[]>(
|
||||
`SELECT id, name, style_no, stock, total_stock
|
||||
`SELECT id, name, style_no, stock
|
||||
FROM products
|
||||
WHERE total_stock > 0 AND stock / total_stock < 0.1
|
||||
WHERE stock < 20
|
||||
ORDER BY stock ASC`
|
||||
)
|
||||
res.json({ code: 0, data: rows })
|
||||
|
|
|
|||
|
|
@ -8,28 +8,26 @@ const productArb = fc.record({
|
|||
name: fc.string({ minLength: 1, maxLength: 20 }),
|
||||
style_no: fc.string({ minLength: 0, maxLength: 10 }),
|
||||
stock: fc.integer({ min: 0, max: 10000 }),
|
||||
total_stock: fc.integer({ min: 0, max: 10000 }),
|
||||
})
|
||||
|
||||
// Feature: jewelry-mall, Property 7: 库存预警阈值判断
|
||||
// **Validates: Requirements 8.4**
|
||||
describe('Property 7: 库存预警阈值判断', () => {
|
||||
it('预警列表应恰好包含所有低库存商品且按库存从少到多排序', () => {
|
||||
it('预警列表应恰好包含所有库存<20的商品且按库存从少到多排序', () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.array(productArb, { minLength: 0, maxLength: 20 }),
|
||||
(products) => {
|
||||
const alerts = filterStockAlerts(products)
|
||||
|
||||
// Every alert should satisfy the threshold condition
|
||||
// Every alert should satisfy the threshold condition (stock < 20)
|
||||
for (const a of alerts) {
|
||||
expect(a.total_stock).toBeGreaterThan(0)
|
||||
expect(a.stock / a.total_stock).toBeLessThan(0.1)
|
||||
expect(a.stock).toBeLessThan(20)
|
||||
}
|
||||
|
||||
// Every product meeting the condition should be in alerts
|
||||
const expectedIds = products
|
||||
.filter((p) => p.total_stock > 0 && p.stock / p.total_stock < 0.1)
|
||||
.filter((p) => p.stock < 20)
|
||||
.map((p) => p.id)
|
||||
const alertIds = alerts.map((a) => a.id)
|
||||
expect(alertIds.sort()).toEqual(expectedIds.sort())
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user