订单修改金额

This commit is contained in:
2026-05-08 16:07:39 +08:00
parent f20a45a1ad
commit 14735af0b3
7 changed files with 252 additions and 3 deletions
+30 -2
View File
@@ -18,12 +18,40 @@ If you're using Codex or another agent-capable tool, additional project-scoped h
## Subagents ## Subagents
- ALWAYS wait for all subagents to complete before yielding. - ALWAYS wait for every spawned subagent to reach a terminal status before yielding, acting on partial results, or spawning followups.
- On Codex, this means calling the `wait` tool with the subagent's thread id (requires `multi_agent_v2`). Do NOT infer completion from elapsed time.
- On Claude Code / OpenCode, this means awaiting the Task/agent tool result before continuing.
- NEVER cancel or re-spawn a subagent that hasn't finished. If a subagent appears stuck, raise the wait timeout (Codex default 30s, max 1h) before judging it broken.
- Spawn subagents automatically when: - Spawn subagents automatically when:
- Parallelizable work (e.g., install + verify, npm test + typecheck, multiple tasks from plan) - Parallelizable work (e.g., install + verify, npm test + typecheck, multiple tasks from plan)
- Long-running or blocking tasks where a worker can run independently. - Long-running or blocking tasks where a worker can run independently
- Isolation for risky changes or checks - Isolation for risky changes or checks
### Codex-only — `spawn_agent` parameters
When calling `spawn_agent`, ALWAYS pass `fork_turns="none"`. Without it the child inherits the parent transcript and sees your prior `spawn_agent(...)` records, then applies the "wait for spawned subagents" rule to itself — causing `wait_agent` self-deadlock.
```text
spawn_agent(agent_type="trellis-implement", message="...", fork_turns="none")
```
### Codex-only — multi-subagent close-loop
When `wait` returns a `completed` notification, treat it as an event signal — not as "all done". Run this loop:
1. Maintain an `expected_agents` set of dispatched sub-agent thread IDs.
2. After each `wait` update:
1. Call `list_agents` to inspect ALL live agents' status.
2. For each agent now in a terminal state:
- Verify its promised deliverable exists (e.g. `{task_dir}/research/*.md`).
- Read or summarize as needed.
- `close_agent` to release the slot.
- Remove from `expected_agents`.
3. If `expected_agents` still contains running agents → keep waiting.
4. If `expected_agents` is empty → continue main flow.
3. Never `wait` on an agent that has already reported `completed`.
4. If a `completed` agent is missing its deliverable, treat it as failed — surface that in your report instead of re-waiting.
Managed by Trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `trellis update`. Managed by Trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `trellis update`.
<!-- TRELLIS:END --> <!-- TRELLIS:END -->
+5
View File
@@ -480,6 +480,11 @@ export function prescriptionOrderLogs(params: { id: number }) {
return request.get({ url: '/tcm.prescriptionOrder/logs', params }) return request.get({ url: '/tcm.prescriptionOrder/logs', params })
} }
/** 修改订单金额 */
export function prescriptionOrderUpdateAmount(params: { id: number; amount_update: number }) {
return request.post({ url: '/tcm.prescriptionOrder/updateAmount', params })
}
// ========== 处方库 ========== // ========== 处方库 ==========
// 处方库列表 // 处方库列表
@@ -663,7 +663,19 @@
<el-row :gutter="16" class="mb-5"> <el-row :gutter="16" class="mb-5">
<el-col :xs="12" :sm="6"> <el-col :xs="12" :sm="6">
<el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50"> <el-card shadow="never" class="stat-card border border-gray-100 bg-gray-50/50">
<div class="stat-title text-gray-500 text-xs mb-1">总金额</div> <div class="flex items-center justify-between mb-1">
<div class="stat-title text-gray-500 text-xs">总金额</div>
<el-button
v-if="canUpdateAmount(detailData)"
v-perms="['tcm.prescriptionOrder/updateAmount']"
type="primary"
size="small"
link
@click="openUpdateAmount"
>
修改
</el-button>
</div>
<div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div> <div class="stat-value text-red-500 font-bold text-xl">¥{{ detailData.amount }}</div>
</el-card> </el-card>
</el-col> </el-col>
@@ -1185,6 +1197,48 @@
</div> </div>
</el-drawer> </el-drawer>
<el-dialog
v-model="updateAmountVisible"
title="修改订单金额"
width="420px"
destroy-on-close
:close-on-click-modal="false"
@closed="resetUpdateAmountDialog"
>
<el-form
ref="updateAmountFormRef"
:model="updateAmountForm"
:rules="updateAmountRules"
label-width="96px"
class="pr-2"
@submit.prevent="submitUpdateAmount"
>
<el-form-item label="当前金额">
<div class="text-sm text-gray-700 font-medium">¥{{ formatMoney(detailData?.amount) }}</div>
</el-form-item>
<el-form-item label="新金额" prop="amount_update">
<el-input-number
v-model="updateAmountForm.amount_update"
:min="0.01"
:step="0.01"
:precision="2"
controls-position="right"
class="w-full"
placeholder="请输入新金额"
/>
</el-form-item>
<div class="text-xs text-gray-400 pl-[96px] -mt-1 leading-relaxed">
已完成或已取消订单不可修改。提交成功后会刷新详情与操作日志。
</div>
</el-form>
<template #footer>
<el-button @click="updateAmountVisible = false">取消</el-button>
<el-button type="primary" :loading="updateAmountSaving" @click="submitUpdateAmount">
确认修改
</el-button>
</template>
</el-dialog>
<!-- 编辑(分步向导与「创建业务订单」弹窗统一) --> <!-- 编辑(分步向导与「创建业务订单」弹窗统一) -->
<el-dialog <el-dialog
v-model="editVisible" v-model="editVisible"
@@ -2294,6 +2348,7 @@ import {
prescriptionOrderLogisticsTrace, prescriptionOrderLogisticsTrace,
prescriptionOrderShip, prescriptionOrderShip,
prescriptionOrderLogs, prescriptionOrderLogs,
prescriptionOrderUpdateAmount,
prescriptionOrderAddPayOrder, prescriptionOrderAddPayOrder,
prescriptionOrderComplete, prescriptionOrderComplete,
prescriptionOrderRevokeRxAudit, prescriptionOrderRevokeRxAudit,
@@ -3058,6 +3113,12 @@ function canQuickTrackRow(row: { fulfillment_status?: number }) {
return fs === 5 || fs === 6 return fs === 5 || fs === 6
} }
function canUpdateAmount(row: { id?: number; fulfillment_status?: number } | null | undefined) {
if (!row?.id) return false
const fs = Number(row.fulfillment_status)
return fs !== 3 && fs !== 4
}
function canUploadGancaoRow(row: { function canUploadGancaoRow(row: {
prescription_audit_status?: number prescription_audit_status?: number
gancao_reciperl_order_no?: string gancao_reciperl_order_no?: string
@@ -3290,6 +3351,29 @@ const logisticsTracePayload = ref<Record<string, any> | null>(null)
const logisticsTracePhoneTail = ref('') const logisticsTracePhoneTail = ref('')
const detailLogs = ref<any[]>([]) const detailLogs = ref<any[]>([])
const updateAmountVisible = ref(false)
const updateAmountSaving = ref(false)
const updateAmountFormRef = ref<FormInstance>()
const updateAmountForm = reactive({
id: 0,
amount_update: undefined as number | undefined
})
const updateAmountRules: FormRules = {
amount_update: [
{ required: true, message: '请输入订单金额', trigger: 'blur' },
{
validator: (_rule, value, callback) => {
const num = Number(value)
if (!Number.isFinite(num) || num <= 0) {
callback(new Error('订单金额必须大于0'))
return
}
callback()
},
trigger: 'blur'
}
]
}
function canViewPrescriptionOrderLogs() { function canViewPrescriptionOrderLogs() {
const p = userStore.perms || [] const p = userStore.perms || []
@@ -3325,6 +3409,7 @@ function logActionText(act: string) {
revoke_pay_audit: '撤回支付审核', revoke_pay_audit: '撤回支付审核',
gancao_submit: '甘草下单', gancao_submit: '甘草下单',
patch_rx_patient: '处方患者信息', patch_rx_patient: '处方患者信息',
update_amount: '修改订单金额',
complete: '完成订单' complete: '完成订单'
} }
return m[act] || act return m[act] || act
@@ -3409,6 +3494,24 @@ function resetPatchRxPatientForm() {
patchRxPatientForm.phone = '' patchRxPatientForm.phone = ''
} }
function resetUpdateAmountDialog() {
updateAmountForm.id = 0
updateAmountForm.amount_update = undefined
updateAmountFormRef.value?.clearValidate()
}
function openUpdateAmount() {
const row = detailData.value
if (!canUpdateAmount(row)) {
feedback.msgWarning('当前订单状态不允许修改金额')
return
}
updateAmountForm.id = Number(row?.id) || 0
updateAmountForm.amount_update = Number(row?.amount) || undefined
updateAmountVisible.value = true
nextTick(() => updateAmountFormRef.value?.clearValidate())
}
async function refreshCurrentPrescriptionOrderDetail() { async function refreshCurrentPrescriptionOrderDetail() {
const id = detailData.value?.id const id = detailData.value?.id
if (!id) return if (!id) return
@@ -3448,6 +3551,37 @@ async function submitPatchRxPatient() {
} }
} }
async function submitUpdateAmount() {
const form = updateAmountFormRef.value
if (!form) return
try {
await form.validate()
} catch {
return
}
if (!canUpdateAmount(detailData.value)) {
feedback.msgWarning('当前订单状态不允许修改金额')
return
}
const orderId = updateAmountForm.id
updateAmountSaving.value = true
try {
await prescriptionOrderUpdateAmount({
id: orderId,
amount_update: Number(updateAmountForm.amount_update)
})
feedback.msgSuccess('修改成功')
updateAmountVisible.value = false
await refreshCurrentPrescriptionOrderDetail()
await fetchLogs(orderId)
getLists()
} catch {
/* 拦截器已提示 */
} finally {
updateAmountSaving.value = false
}
}
async function openDetail(id: number) { async function openDetail(id: number) {
// 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留 // 修复 Bug:显式彻底清空缓存,防止前一次弹窗的数据残留
detailData.value = null detailData.value = null
@@ -246,6 +246,21 @@ class PrescriptionOrderController extends BaseAdminController
return $this->success('', $result); return $this->success('', $result);
} }
/**
* 修改订单金额
*/
public function updateAmount()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('updateAmount');
$result = PrescriptionOrderLogic::updateAmount($params, $this->adminId, $this->adminInfo);
if ($result === false) {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('修改成功');
}
/** /**
* 为「已发货」订单新增一条关联支付单,并重置支付单审核状态为待审核 * 为「已发货」订单新增一条关联支付单,并重置支付单审核状态为待审核
*/ */
@@ -2432,6 +2432,52 @@ class PrescriptionOrderLogic
]; ];
} }
/**
* 修改订单金额
*/
public static function updateAmount(array $params, int $adminId, array $adminInfo): bool
{
$id = (int) $params['id'];
$newAmount = round((float) $params['amount_update'], 2);
// 查询订单
$order = PrescriptionOrder::find($id);
if (!$order) {
self::setError('订单不存在');
return false;
}
// 数据权限检查
if (!self::checkDataScope($order->toArray(), $adminId, $adminInfo)) {
self::setError('无权限操作此订单');
return false;
}
// 状态检查:已完成(3)和已取消(4)不允许修改
if (in_array((int) $order->fulfillment_status, [3, 4], true)) {
self::setError('已完成或已取消的订单不允许修改金额');
return false;
}
$oldAmount = round((float) $order->amount, 2);
// 金额未变化
if (abs($newAmount - $oldAmount) < 0.01) {
self::setError('金额未发生变化');
return false;
}
// 更新金额
$order->amount = $newAmount;
$order->save();
// 记录日志
$summary = sprintf('将订单金额从 ¥%.2f 修改为 ¥%.2f', $oldAmount, $newAmount);
self::writeLog($id, $adminId, $adminInfo, 'update_amount', $summary);
return true;
}
private static function writeLog(int $orderId, int $adminId, array $adminInfo, string $action, string $summary): void private static function writeLog(int $orderId, int $adminId, array $adminInfo, string $action, string $summary): void
{ {
$adminName = $adminInfo['name'] ?? ''; $adminName = $adminInfo['name'] ?? '';
@@ -36,6 +36,7 @@ class PrescriptionOrderValidate extends BaseValidate
'patient_name' => 'require|max:50', 'patient_name' => 'require|max:50',
'phone' => 'require|max:20', 'phone' => 'require|max:20',
'phone_tail' => 'max:20|regex:/^\\d*$/', 'phone_tail' => 'max:20|regex:/^\\d*$/',
'amount_update' => 'require|float|gt:0',
]; ];
protected $message = [ protected $message = [
@@ -50,6 +51,8 @@ class PrescriptionOrderValidate extends BaseValidate
'patient_name.require' => '请输入患者姓名', 'patient_name.require' => '请输入患者姓名',
'phone.require' => '请输入手机号', 'phone.require' => '请输入手机号',
'phone_tail.regex' => '手机后四位仅支持数字', 'phone_tail.regex' => '手机后四位仅支持数字',
'amount_update.require' => '请输入订单金额',
'amount_update.gt' => '订单金额必须大于0',
]; ];
protected $scene = [ protected $scene = [
@@ -77,5 +80,6 @@ class PrescriptionOrderValidate extends BaseValidate
'submitGancaoRecipel' => ['id'], 'submitGancaoRecipel' => ['id'],
'previewGancaoRecipel' => ['id'], 'previewGancaoRecipel' => ['id'],
'patchPrescriptionPatient' => ['id', 'patient_name', 'phone'], 'patchPrescriptionPatient' => ['id', 'patient_name', 'phone'],
'updateAmount' => ['id', 'amount_update'],
]; ];
} }
@@ -0,0 +1,17 @@
-- 处方业务订单:修改订单金额
-- 执行前确认表前缀为 zyt_;若已有相同 perms 则跳过。
-- 需在「角色权限」中为有权限的角色勾选本按钮权限。
SET @po_menu_id := (SELECT id FROM zyt_system_menu WHERE perms = 'tcm.prescriptionOrder/lists' LIMIT 1);
INSERT INTO zyt_system_menu (
pid, type, name, icon, sort, perms, paths, component,
selected, params, is_cache, is_show, is_disable, create_time, update_time
)
SELECT
@po_menu_id, 'A', '修改订单金额', '', 60,
'tcm.prescriptionOrder/updateAmount', '', '',
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM DUAL
WHERE @po_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM zyt_system_menu WHERE perms = 'tcm.prescriptionOrder/updateAmount');