feat(order): 订单支出台账(快递费/吊卡费等杂费)
订单上原本只有「卖给客户多少钱」,而一张单真实花出去的还有寄样品的快递费、 印吊卡的费用、商检报关拖车。不记下来就算不出这单赚没赚 —— 小单子几百块 杂费能把毛利吃掉一截。 · 新表 order_expenses,一张订单挂多笔:类型/金额/币种/日期/收款方/单号/ 已付未付/备注。单独成表而不是往 orders 加列,因为杂费是多笔不定项的, 寄三次样品就是三条快递费,各有单号;塞进 orders 只能存一个合计,事后 对不出「这 800 块是哪来的」。 · 跨币种强制填汇率并存快照。杂费基本是人民币、订单常是美元;不存快照的话 汇率一变历史订单毛利跟着变,账永远对不上。同币种强制按 1,防止前端传个 7.2 把美元支出又乘一遍。 · 新权限点 order.expense,不复用 order.edit。掏钱寄样品的是销售业务员, 而销售业务组只有 order.view —— 挂在 order.edit 上要么他记不了账,要么 为了让他记账得把「改订单金额」一起给他。启动时 syncNewPermissions() 会自动把这个新点补给四个默认组。 · 订单列表加「杂费」列(合计 + 未付)。SUM 外层再 ROUND 一次:不加会漏出 348.15999999999997 这种浮点尾巴直接显示在页面上。 · 金额逐行取两位再相加,保证明细几行加起来等于合计。 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3e9fd2dc97
commit
f32a2daaef
@@ -2,12 +2,16 @@ import React, { useState, useEffect } from 'react'
|
||||
import axios from 'axios'
|
||||
import { SkeletonTableRows } from '../components/Skeleton'
|
||||
import { useAuth, hasPermission } from '../auth'
|
||||
import OrderExpenses from '../components/OrderExpenses'
|
||||
|
||||
function OrderManagement() {
|
||||
const [orders, setOrders] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { user } = useAuth()
|
||||
const canEdit = hasPermission(user, 'order.edit')
|
||||
/* 支出单独一个权限点:掏钱寄样品的是业务员,而业务组没有 order.edit */
|
||||
const canExpense = hasPermission(user, 'order.expense')
|
||||
const [expenseOrder, setExpenseOrder] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetchOrders()
|
||||
@@ -51,12 +55,13 @@ function OrderManagement() {
|
||||
<th>订单日期</th>
|
||||
<th>交货日期</th>
|
||||
<th>金额</th>
|
||||
<th>杂费</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<SkeletonTableRows rows={5} widths={[1.2, 1.6, 1, 1, 1.2, 0.8, 1]} />
|
||||
<SkeletonTableRows rows={5} widths={[1.2, 1.6, 1, 1, 1.2, 1, 0.8, 1]} />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -80,6 +85,7 @@ function OrderManagement() {
|
||||
<th>订单日期</th>
|
||||
<th>交货日期</th>
|
||||
<th>金额</th>
|
||||
<th>杂费</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
@@ -87,7 +93,7 @@ function OrderManagement() {
|
||||
<tbody>
|
||||
{orders.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan="7" className="empty-state">暂无订单数据</td>
|
||||
<td colSpan="8" className="empty-state">暂无订单数据</td>
|
||||
</tr>
|
||||
) : (
|
||||
orders.map(order => (
|
||||
@@ -105,6 +111,14 @@ function OrderManagement() {
|
||||
? `${order.currency} ${order.total_amount.toFixed(2)}`
|
||||
: '-'}
|
||||
</td>
|
||||
<td>
|
||||
{order.expense_count ? (
|
||||
<button type="button" className="btn-link" onClick={() => setExpenseOrder(order)}>
|
||||
{order.currency} {Number(order.expense_total || 0).toFixed(2)}
|
||||
{order.expense_unpaid > 0 && <em className="order-expense-unpaid"> 未付 {Number(order.expense_unpaid).toFixed(2)}</em>}
|
||||
</button>
|
||||
) : <span className="text-dim">—</span>}
|
||||
</td>
|
||||
<td>
|
||||
<span className={`status-badge status-${order.status}`}>
|
||||
{getStatusText(order.status)}
|
||||
@@ -112,6 +126,7 @@ function OrderManagement() {
|
||||
</td>
|
||||
<td>
|
||||
<button className="btn-link">查看</button>
|
||||
{canExpense && <button type="button" className="btn-link" onClick={() => setExpenseOrder(order)}>支出</button>}
|
||||
{canEdit && <button className="btn-link">编辑</button>}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -120,6 +135,14 @@ function OrderManagement() {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{expenseOrder && (
|
||||
<OrderExpenses
|
||||
order={expenseOrder}
|
||||
onClose={() => setExpenseOrder(null)}
|
||||
onChanged={fetchOrders}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user