From 29938ecbe09bb4ed3c740ca99c2503d431ecd07e Mon Sep 17 00:00:00 2001 From: Willie Date: Mon, 25 May 2026 22:37:41 +0800 Subject: [PATCH] vault backup: 2026-05-25 22:37:41 --- .obsidian/workspace.json | 8 +- .../deposit/audit-monthly-deposit-balance.md | 241 ++++++++++++++++++ .../deposit/exception-deposit-on-frozen.md | 175 +++++++++++++ .../scenarios/deposit/force-close-forfeit.md | 170 ++++++++++++ .../scenarios/deposit/force-close-retain.md | 230 +++++++++++++++++ 5 files changed, 820 insertions(+), 4 deletions(-) create mode 100644 prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md create mode 100644 prop-acc/scenarios/deposit/exception-deposit-on-frozen.md create mode 100644 prop-acc/scenarios/deposit/force-close-forfeit.md create mode 100644 prop-acc/scenarios/deposit/force-close-retain.md diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index e9981a0..78bb931 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -196,6 +196,10 @@ }, "active": "b06ed69835363258", "lastOpenFiles": [ + "prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md", + "prop-acc/scenarios/deposit/exception-deposit-on-frozen.md", + "prop-acc/scenarios/deposit/force-close-retain.md", + "prop-acc/scenarios/deposit/force-close-forfeit.md", "prop-acc/scenarios/deposit/force-close-refund.md", "prop-acc/scenarios/deposit/close-manual-with-zero-balance.md", "prop-acc/scenarios/deposit/close-after-zero-balance.md", @@ -220,10 +224,6 @@ "prop-acc/concepts/deposit/account-state-machine.md", "prop-acc/concepts/deposit/deposit-account-vs-transaction.md", "prop-acc/concepts/deposit", - "prop-acc/maps/adhoc-knowledge-map.md", - "prop-acc/scenarios/adhoc/cancel-resident-withdrawal.md", - "prop-acc/scenarios/adhoc/flow-a-counter-buy-ic-card.md", - "概念-CollectionOrder与Receipt.md", "prop-acc/scenarios/adhoc", "prop-acc/concepts/adhoc", "resident-portal/scenarios", diff --git a/prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md b/prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md new file mode 100644 index 0000000..916f744 --- /dev/null +++ b/prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md @@ -0,0 +1,241 @@ +--- +title: prop-acc · deposit · 场景 - 月度押金账户余额对账 +aliases: + - 押金月对账 + - 押金账面与银行对账 + - audit-monthly-deposit-balance + - 场景-押金月度对账 +tags: + - 场景 + - prop-acc + - 保证金 + - 审计 +audience: + - 财务 + - 业务人员 +status: 已发布 +sub_feature: deposit +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:月度押金账户余额对账 + +物业财务**每月底**对所有押金账户做一次余额校验,确保系统账面 = 银行专户余额 = 流水累计净值,三方平衡。是审计内控的标准动作。 + +## 典型情境 + +> [!example] 真实情境 +> 物业财务王主管每月最后一个工作日做"押金账户余额三方对账": +> +> 1. 系统所有 Active + Closed-retain 账户的 `balance` + `meta.balance_held_amount` 之和 +> 2. 银行"装修押金专户"的实际余额 +> 3. 全部 `DepositTransaction` 按净值累加 + retain 保留金额 +> +> 三者必须**完全相等**,任何差额都要查清。 + +## 三方对账详解 + +### 1. 系统账面余额(System Balance) + +```sql +-- Active 账户的当前余额 +SELECT SUM(balance) FROM acc_deposit_accounts WHERE status = 'active'; + +-- + Frozen 账户的当前余额(冻结期间余额仍属业户) +SELECT SUM(balance) FROM acc_deposit_accounts WHERE status = 'frozen'; + +-- + Closed 但保留资金的账户(retain disposition) +SELECT SUM(JSON_EXTRACT(meta, '$.balance_held_amount')) +FROM acc_deposit_accounts +WHERE status = 'closed' AND JSON_EXTRACT(meta, '$.force_closed_disposition') = 'retain'; +``` + +三者之和 = **物业代管负债总额**。 + +### 2. 银行专户余额(Bank Balance) + +物业的"装修押金银行专户"(单独开立的隔离账户)的月底余额。 + +> [!info] 为什么要专户隔离 +> 押金是代管资金,**不应与物业自有资金混同**。专户的好处: +> - 内控明确,资金不被挪用 +> - 对账简单(银行余额 == 应付业户的钱) +> - 监管合规(部分地区物业法规要求) + +### 3. 流水累计净值(Transaction Sum) + +```sql +-- 全部 DepositTransaction 的净值(deposit 加,refund/forfeiture 减) +SELECT + SUM(CASE WHEN type = 'deposit' THEN amount ELSE -amount END) AS net_balance +FROM acc_deposit_transactions; + +-- 这个值理论上 == 所有 Active + Frozen 账户的 balance 之和 +-- (不包括 retain Closed 账户,因为它们的 balance 字段未变,但已不在 Transaction 累加范围内 —— 实际上也累加了,因为 forfeiture/refund 没发生) +``` + +> [!info] 完整公式 +> ``` +> SUM(transactions 净值) = SUM(active.balance) + SUM(frozen.balance) + SUM(closed.balance where status=closed and balance>0) +> ``` +> 任何不一致 → 有 bug 或人为绕过(详见 [[deposit-account-vs-transaction]] "两者的契约")。 + +### 三方平衡的预期 + +``` +系统账面余额 == 银行专户余额 == 流水累计净值 +``` + +任何一对不等 → 调查。 + +## 业务人员视角(月底操作) + +### 第 1 步:导出系统账面余额报表 + +后台 → 保证金 → 列表 → 按"状态 Active / Frozen / Closed-retain"过滤 → 导出 Excel,合计 `balance` 列 + `meta.balance_held_amount` 列。 + +(或运行 SQL 查询,见上方) + +### 第 2 步:取银行专户对账单 + +- 登录银行网银 / 联系银行客户经理 +- 取本月最后一天的余额(精确到分) + +### 第 3 步:做对账 + +| 维度 | 数值 | 来源 | +|---|---|---| +| A. 系统账面余额 | ¥X | 后台导出 | +| B. 银行专户余额 | ¥Y | 银行对账单 | +| C. 流水累计净值 | ¥Z | SQL 查询 | +| **差额(应 0)** | A - B 和 A - C | | + +如果三个值不相等,**这才是对账的核心工作**:查清差异。 + +### 第 4 步:差额排查 + +常见差异原因: + +| 差异 | 可能原因 | 排查方法 | +|---|---|---| +| A > B(系统多于银行) | 账面收了但银行未到账(在途资金) | 查最近 deposit 的支付渠道(POS / 微信)是否回滚 | +| A < B(系统少于银行) | 银行多了钱但系统没记(混账)| 查银行流水那笔多的钱来源 | +| A ≠ C(系统账面与流水不一致)| 系统 bug 或 tinker 改过余额 | `$account->verifyBalance()` 找出哪个账户不一致 | + +> [!warning] 任何不一致都是事故 +> 即使差额很小(¥1),也必须查清。押金是负债 = 业户的钱,任何不平都是合规风险。 + +### 第 5 步:出对账报告 + +格式: + +```markdown +# 2026 年 5 月 装修押金账户对账报告 + +## 三方余额 +- 系统账面:¥152,300.00(Active 89 + Frozen 3 + Retain 4) +- 银行专户:¥152,300.00 +- 流水净值:¥152,300.00 + +## 平衡情况:✅ 完全平衡 + +## 本月新增账户:12 +## 本月关账:8(其中 retain 1) +## 本月扣罚 / 退款总额:¥85,400 / ¥73,200 +``` + +如有差异: + +```markdown +## ⚠️ 差异 +- 系统账面 ¥X 与银行 ¥Y 差 ¥Z +- 已查:第 N 笔 deposit on 2026-05-22 的微信支付实际未到账(支付平台冻结审核) +- 处理:待 5-28 微信渠道放款,再次对账确认 + +## 本月需复盘 +- ID 4567 账户余额异常:`verifyBalance()` 返 false,差 ¥0.5(疑似浮点误差或人为编辑) +- 已 tinker 修复 + 加备注 +``` + +## 系统流程 + +```mermaid +flowchart TD + A[月底触发] --> B[导出系统账面] + A --> C[取银行专户对账单] + A --> D[查询流水累计] + + B --> E{三方相等?} + C --> E + D --> E + + E -- 是 --> F[出对账报告 ✅] + E -- 否 --> G[排查差异] + G --> H[追溯交易 / 查 verifyBalance / 排查 tinker 操作] + H --> I[修复 + 备注] + I --> J[重新对账] + J --> E +``` + +## 报表 SQL 汇总(供财务复用) + +```sql +-- 本月新增账户 +SELECT COUNT(*) FROM acc_deposit_accounts +WHERE created_at BETWEEN '2026-05-01' AND '2026-05-31 23:59:59'; + +-- 本月关账(含自动关账 + 主动关账 + ForceClose) +SELECT COUNT(*), + COUNT(*) FILTER (WHERE JSON_EXTRACT(meta, '$.force_closed_disposition') IS NOT NULL) AS force_closed, + COUNT(*) FILTER (WHERE JSON_EXTRACT(meta, '$.force_closed_disposition') = 'retain') AS retain_count +FROM acc_deposit_accounts +WHERE status = 'closed' + AND updated_at BETWEEN '2026-05-01' AND '2026-05-31 23:59:59'; + +-- 本月扣罚总额 +SELECT SUM(amount) FROM acc_deposit_transactions +WHERE type = 'forfeiture' + AND created_at BETWEEN '2026-05-01' AND '2026-05-31 23:59:59'; + +-- 本月退款总额 +SELECT SUM(amount) FROM acc_deposit_transactions +WHERE type = 'refund' + AND created_at BETWEEN '2026-05-01' AND '2026-05-31 23:59:59'; +``` + +## 常见问题 + +> [!question] 没有银行专户怎么对账? +> 退一步,与物业总账户对账,但只能看"已收 / 已退"流水净值,**无法判断资金是否被挪用**(被混入其他业务用资金池)。**强烈建议设置专户**。 + +> [!question] retain 账户的资金算物业代管负债吗? +> **算**。即使账户 Closed,只要 `meta.balance_held_amount > 0`,资金法律上仍属业户,会计上仍是"其他应付款"。详见 [[force-close-retain]]。 + +> [!question] 月度对账要审计师参与吗? +> 内部对账物业财务自己做。**年度审计**时审计师会重点检查这部分(物业的代管资金合规性、是否有挪用迹象)。月度对账留下完整记录是年审顺利的前提。 + +> [!question] 差额查不出来怎么办? +> 上报物业财务总监 + 法务。可能需要: +> - 委托第三方审计核对 +> - 银行流水逐笔重对 +> - tinker 操作日志审查(谁改了什么) +> +> **不能放任**,押金对账差异是严重合规问题。 + +> [!question] 多个物业项目的押金一起对吗? +> 通常每个 `community_id` 独立对账(每个物业项目一个银行专户)。系统层面也可按 community 分组导出。 + +## 异常分支 + +- 发现 `verifyBalance()=false` 的账户 → tinker 修复 + 排查根因 +- 发现银行专户少于账面 → 资金挪用嫌疑,法务介入 +- 发现长期 retain 的资金对应不上 → [[audit-long-pending-accounts|长期账户排查]] + +## 相关文档 + +- [[deposit-account-vs-transaction]] +- [[transaction-types]] +- [[force-close-retain]] +- [[audit-long-pending-accounts]] +- [[deposit-vs-adhoc-vs-prepaid]] diff --git a/prop-acc/scenarios/deposit/exception-deposit-on-frozen.md b/prop-acc/scenarios/deposit/exception-deposit-on-frozen.md new file mode 100644 index 0000000..7ddc22f --- /dev/null +++ b/prop-acc/scenarios/deposit/exception-deposit-on-frozen.md @@ -0,0 +1,175 @@ +--- +title: prop-acc · deposit · 场景 - 冻结账户尝试缴款被拦截 +aliases: + - 冻结账户缴款被拒 + - exception-deposit-on-frozen + - 场景-冻结账户缴款异常 +tags: + - 场景 + - prop-acc + - 保证金 + - 异常 +audience: + - 业务人员 +status: 已发布 +sub_feature: deposit +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:冻结账户尝试缴款被拦截 + +业务人员**误对一个 Frozen 账户**点击"追加缴款"按钮,系统按 [[account-state-machine|状态机]] 守护**直接拦截**,不让任何资金进入。 + +## 典型情境 + +> [!example] 真实情境 +> 物业职员小李在前台办业户陈先生的押金追加业务。陈先生说"我再补 ¥1,000"。小李没注意到陈先生的账户当前处于 **Frozen 状态**(因之前墙面损坏纠纷),习惯性打开账户点了"追加缴款"按钮。 +> +> 系统提示:**"账户处于冻结状态,无法缴款"**,操作被拒绝。 + +## 业务人员视角 + +### 您看到什么 + +| 时刻 | 看到 | +|---|---| +| 进入 `ViewDepositAccount` | 状态栏显示 "🧊 Frozen" | +| 状态管理组里的按钮 | "追加缴款 / 退款 / 扣罚" 全部**灰化**,只有 "Unfreeze / ForceClose" 可点 | +| 如果硬调 API | 系统抛出 `RuntimeException`:"账户处于冻结状态,canDeposit returns false" | + +> [!info] 三道防御 +> 1. **UI 层**:按钮根据 `canDeposit()` 状态自动灰化 +> 2. **Policy 层**:`DepositAccountPolicy::update()` + 状态检查 +> 3. **业务 Action 层**:`DepositIntoAccountAction` 入口校验 `canDeposit()`,失败抛异常 +> +> 即使前两道被绕过(直接调 API、tinker 操作),第三道仍兜底。 + +### 第 1 步:看到操作被拒,理解原因 + +提示框信息:**"账户处于冻结状态,canDeposit returns false"**(系统消息 + 业务消息)。 + +### 第 2 步:与业户沟通 + +不要硬要绕过。说明: + +> "您的账户当前因纠纷冻结,需先调解完成解冻后才能继续缴款。" + +业户的几个可能反应: + +| 业户反应 | 处理 | +|---|---| +| "那快帮我解冻" | 看是否有调解结果。无书面凭证不解冻 | +| "我先把钱给你,等解冻再录" | **不要这么做**。物业不能"暂存"业户的钱 —— 没有凭证 = 不合规。让业户先离开,等解冻 | +| "那这账户先不动了" | OK,告诉业户冻结期间状态、预计解冻时间 | + +### 第 3 步:正确流程 + +1. 调解 → 拿到书面调解协议 +2. [[unfreeze-after-mediation|解冻]] → 账户变 Active +3. [[deposit-additional-topup|追加缴款]] → 正常缴款流程 + +## 系统视角:守护链路 + +```mermaid +sequenceDiagram + participant 职员 + participant Filament + participant DepositIntoAccountAction + participant DepositAccount + + 职员->>Filament: 点击"追加缴款" → DepositAction modal + Note over Filament: UI 守护:Frozen 状态按钮已灰化(防误点) + 职员->>Filament: 如果绕过 UI,直接提交 + Filament->>DepositIntoAccountAction: handle(account, amount) + DepositIntoAccountAction->>DepositAccount: canDeposit()? + DepositAccount-->>DepositIntoAccountAction: false (status=Frozen) + DepositIntoAccountAction-->>Filament: throw RuntimeException + Filament-->>职员: 显示错误:"账户处于冻结状态,无法缴款" + + Note over 职员: 不会建任何 CO/Transaction/Receipt +``` + +## 为什么这条守护比直觉更严 + +历史上曾经的代码允许 `[Active, Frozen]` 都能缴款,直觉上"反正多存钱不亏",收紧成只允许 Active 是经过教训的。详见 [[account-state-machine]] "关键守护:Frozen 不允许任何资金动作" 段。 + +真实风险: + +| 反例 | 后果 | +|---|---| +| 装修公司持续往受冻结的押金账户灌钱 | 资金被困、责任更复杂(冻结期间是否构成新债权?) | +| 业户与物业纠纷期间业户继续存钱 | 资金混同,扣罚 / 退款时算不清"哪一笔是原押金、哪一笔是纠纷后存的" | +| 系统给"暂存"开口子 | 业务人员可能用它做不规范操作("先放着,以后调整") | + +收紧后**所有冻结期间的资金问题都必须先解冻**,语义清晰、审计可追。 + +## 测试断言 + +代码层有专门测试覆盖此异常路径: + +```php +test('cannot deposit on frozen account', function () { + $account = DepositAccount::factory()->frozen()->create(['balance' => 5000]); + + expect(fn () => app(DepositIntoAccountAction::class)->handle($account, 1000)) + ->toThrow(RuntimeException::class, '账户处于冻结状态'); + + expect($account->fresh()->balance)->toBe(5000.0); // 余额未变 + expect(DepositTransaction::count())->toBe(0); // 流水未建 +}); +``` + +任何对 `canDeposit()` 的修改都会触发此测试,确保守护不被无意中放宽。 + +## 常见问题 + +> [!question] 业户坚持要存钱,职员怎么办? +> 解释 + 引导: +> - 解释为什么不能存(冻结期间所有资金动作禁止,无论方向) +> - 引导先完成调解 / 解冻 +> - 如果业户着急,加快内部调解流程(联系物业管家) + +> [!question] 业户已经把钱转过来了(对公转账已到账)但账户冻结,怎么办? +> 这是**业务问题**,不是系统问题: +> - 物业账户已收到这笔钱,但**不能挂在该业户的 DepositAccount 上**(冻结不允许) +> - 选项: +> - 退回业户(原路退,清晰) +> - 系统外暂留(物业银行账户里的"挂账" / 财务备查) +> - 等解冻后再录入 +> - **推荐第一个**(退回业户),最干净 + +> [!question] 同样的守护对 ForceClose 适用吗? +> ForceClose 走**独立的 Policy 方法** `forceClose()`,守护是 `isFrozen() && hasBalance()` —— 这是**专门**为 Frozen 状态设计的,与 deposit / refund / forfeit 守护刚好"互补": +> +> | 守护 | 适用状态 | 阻止 | +> |---|---|---| +> | `canDeposit()` | Active only | Frozen / Closed 缴款 | +> | `canWithdraw()` | Active only | Frozen / Closed 退款 / 扣罚 | +> | `forceClose()` Policy | Frozen + hasBalance only | Active / Closed / Frozen-zero-balance 强制关账 | +> +> 这条对应详见 [[account-state-machine]]。 + +> [!question] 这个守护影响小程序业户自助操作吗? +> 当前没有小程序自助缴款 —— Action 假设手工操作。**未来加小程序在线缴款时**: +> - 前端调同一个 `DepositIntoAccountAction`,守护自动生效 +> - 业户在小程序操作冻结账户时同样被拦截 +> - UI 应在小程序侧显示更友好的错误信息(避免裸抛 RuntimeException 给业户) + +> [!question] 怎么手工验证某账户是否 Frozen? +> 后台 → 保证金 → 列表 → 状态列;或 tinker `DepositAccount::find($id)->status`。 + +## 异常分支 + +- 想加缴款 → 先 [[unfreeze-after-mediation|解冻]] → [[deposit-additional-topup|追加]] +- 想退款 → 同上,或 [[force-close-refund|强制关账退还]] +- 想扣罚 → 同上,或 [[force-close-forfeit|强制关账扣罚]] +- 长期冻结无解 → [[force-close-retain|资金保留关账]] + +## 相关文档 + +- [[account-state-machine]] +- [[freeze-during-dispute]] +- [[deposit-additional-topup]] +- [[force-close-refund]] +- [[force-close-forfeit]] diff --git a/prop-acc/scenarios/deposit/force-close-forfeit.md b/prop-acc/scenarios/deposit/force-close-forfeit.md new file mode 100644 index 0000000..f97b0ad --- /dev/null +++ b/prop-acc/scenarios/deposit/force-close-forfeit.md @@ -0,0 +1,170 @@ +--- +title: prop-acc · deposit · 场景 - 冻结状态强制全扣并关账 +aliases: + - 强制全扣关账 + - ForceClose forfeit + - force-close-forfeit + - 场景-冻结强制扣罚关账 +tags: + - 场景 + - prop-acc + - 保证金 + - 强制关账 +audience: + - 业务人员 +status: 已发布 +sub_feature: deposit +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:冻结状态强制全扣并关账 + +账户处于 **Frozen** 状态、**还有余额**,**纠纷结果明确利向物业**(损坏归责确认业户全责、违约严重、司法判决物业胜诉等)。物业用 `ForceCloseAction` 选 `forfeit` disposition,**一步完成"解冻 + 全额扣罚 + 关账"**。 + +## 典型情境 + +> [!example] 真实情境 +> 刘先生家装修时**多次违反装修管理协议**(深夜施工、随意丢弃建筑垃圾、损坏 3 处公共设施),物业累计要扣 ¥6,000,而账户只有 ¥5,000。其间刘先生拒绝补缴,账户被冻结进入仲裁。仲裁裁决"业户全责,押金 ¥5,000 全部用于罚款,差额 ¥1,000 业户另行支付"。 +> +> 物业财务依据仲裁书走 ForceClose forfeit,把账户里的 ¥5,000 全扣转入物业收入,账户关闭。差额 ¥1,000 通过司法执行另案处理(不在本系统内)。 + +## 业户视角 + +### 您会感受到什么 + +- 收到通知:"您的押金账户已结清,余额 ¥5,000 全额扣罚" +- 收到**红字收据**:"装修保证金扣罚 ¥-5,000(强制关账扣罚,事由:仲裁裁决业户全责)" +- 银行 / 微信**没有退款**(钱已转入物业收入) +- 小程序"我的押金账户"显示 "🔒 已结清,余额 0" + +### 您要做什么 + +- 接受裁决结果(钱已转出账户,无法追回) +- 保管红字收据(税务凭证,有时报税) +- 如有异议走司法上诉(不在本系统内) + +## 业务人员视角 + +### 第 1 步:确认全扣判定 + +- 仲裁裁决书 / 司法判决书 / 物业内部决议(必须书面) +- 物业内部已审批,具备实施权限 + +> [!warning] 比 ForceClose refund 更敏感 +> 全扣是把业户的钱全部转入物业,**法律风险**比"全退"更高。书面凭证必须完整,内部多层审批,任何疏漏未来纠纷物业败诉概率大。 + +### 第 2 步:打开 Frozen 账户 + +后台 → 保证金 → 账户列表 → 找 Frozen 账户 → 进 `ViewDepositAccount`。 + +### 第 3 步:点击 `ForceCloseAction`(标签"强制关账") + +Modal 表单: + +| 字段 | 填什么 | +|---|---| +| **处置方式 (disposition)** | 选 ✅ **`forfeit`(扣罚)** | +| **扣罚事由(memo)** | 必填且详细。例: "2026-XX-XX 仲裁案号 XXX 裁决:业户多次违约,押金 5000 全额作为罚款。" | + +> [!info] forfeit 不需要选退款渠道 +> 扣罚的钱**不退给业户**,直接转入物业收入,无需 PaymentChannel。Modal 表单选 forfeit 后退款渠道字段隐藏。 + +> [!warning] Policy 守护 +> 同 [[force-close-refund]]:`update` 权限 + `isFrozen() && hasBalance()`。 + +### 第 4 步:提交 + +系统调 `ForceCloseDepositAccountAction(disposition=forfeit)`,事务内: + +1. 校验 `isFrozen() && hasBalance()` +2. 建 `CollectionOrder`(`actual_amount=-5000` 红字,`status=Completed`)—— 表达"从代管负债转出" +3. 加 `DepositTransaction`(`type=forfeiture`,`amount=5000`,`balance_before=5000`,`balance_after=0`,关联红字 CO) +4. 更新 `balance=0` +5. 直接 `status=Closed`(Frozen → Closed) +6. 在 `meta.force_closed_disposition=forfeit`、`meta.force_closed_memo=...`、`meta.force_closed_at=...` 记审计字段 +7. 触发 `CollectionOrderCompleted` → Listener 建红字 Receipt"装修保证金扣罚 ¥-5,000(强制关账扣罚,事由 XXX)" + +### 第 5 步:无线下退款,只给红字凭证 + +后台找到红字 Receipt → 发业户。**不要走任何转账操作** —— 资金已通过 forfeiture 流水转入物业维修收入科目,账面已完成。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 业户 + participant 财务 + participant Filament + participant ForceCloseDepositAccountAction + participant 数据库 + participant 监听器 + + Note over 业户,财务: 账户 Frozen + balance=5000,仲裁裁决全扣 + + 财务->>Filament: ViewDepositAccount → ForceCloseAction (disposition=forfeit) + Filament->>ForceCloseDepositAccountAction: handle(account, disposition=forfeit, memo) + ForceCloseDepositAccountAction->>ForceCloseDepositAccountAction: isFrozen() && hasBalance()? yes + ForceCloseDepositAccountAction->>数据库: 开启事务 + ForceCloseDepositAccountAction->>数据库: 1. 建 CO (-5000 红字, Completed) + ForceCloseDepositAccountAction->>数据库: 2. 建 DepositTransaction (forfeiture, 5000→0) + ForceCloseDepositAccountAction->>数据库: 3. balance=0, status=Closed + ForceCloseDepositAccountAction->>数据库: 4. meta.force_closed_disposition=forfeit + memo + at + ForceCloseDepositAccountAction->>监听器: 5. 触发 CollectionOrderCompleted + 监听器->>数据库: 6. 建 Receipt (强制关账扣罚 ¥-5,000) + ForceCloseDepositAccountAction->>数据库: 提交事务 + Filament-->>财务: 成功通知 + 财务-->>业户: 红字凭证(无退款) +``` + +## 资金流意义 + +```mermaid +flowchart LR + A[业户押金 5000
物业代管负债] -->|forfeit| B[物业维修/罚没收入
5000] +``` + +会计上:**其他应付款 → 装修维修收入**,资金从未离开物业账户,只是科目变化。 + +## 常见问题 + +> [!question] forfeit 的钱进什么科目? +> 通常进"装修维修收入"或"罚没收入"科目(视物业财务核算细则)。账面通过 `Receipt` 上的 line item 描述触发科目映射(Listener `generateDepositReceiptItems` 按 `DepositTransaction.type=forfeiture` 选词)。 + +> [!question] 业户能反悔追讨吗? +> 已扣的钱要回去**只能走司法**: +> - 业户起诉物业不当扣罚 +> - 法院判决物业败诉 → 物业按判决退款(可能需开新账户做反向 deposit + refund 操作记账,审计完整) +> - 法院判决物业胜诉 → 维持原状 +> +> 系统层面的 ForceClose 不可逆,司法判决物业败诉走"补偿"路径,不"撤销"原 ForceClose。 + +> [!question] 仲裁裁决有补充条款(例如要求物业方做某些工作)怎么办? +> 系统只处理资金,补充条款(如修复方案、整改要求)需物业线下执行 + 留档。系统不强制关联。 + +> [!question] 业户已经搬走,联系不上怎么给红字收据? +> 系统层面凭证已生成,业务上若联系不上业户: +> - 用挂号信寄到登记地址 +> - 物业内部档案保留 +> - 等业户联系时再补发 +> +> 如果业户失联前提下连扣罚都不应做(可能未让业户充分应诉),走 [[force-close-retain|资金保留]] 等业户出现更稳妥。 + +> [!question] 比 forfeit 多扣怎么办(扣 5000 还不够,要扣 6000)? +> 账户最多扣到余额清零(`amount ≤ balance` 守护)。**差额追偿不在本系统内**: +> - 单独的 [[adhoc-flow-a-vs-flow-b|adhoc 一次性收费]] 流(开违约金账单) +> - 司法执行(系统不参与) + +## 异常分支 + +- 部分扣部分退 → 走 [[unfreeze-after-mediation|解冻]] + 普通 [[refund-partial-after-forfeit|扣罚后退余]] +- 全退给业户 → [[force-close-refund]] +- 资金保留待定 → [[force-close-retain]] + +## 相关文档 + +- [[force-close-refund]] +- [[force-close-retain]] +- [[freeze-during-dispute]] +- [[account-state-machine]] +- [[forfeit-damage-public-area]] diff --git a/prop-acc/scenarios/deposit/force-close-retain.md b/prop-acc/scenarios/deposit/force-close-retain.md new file mode 100644 index 0000000..8c2e77b --- /dev/null +++ b/prop-acc/scenarios/deposit/force-close-retain.md @@ -0,0 +1,230 @@ +--- +title: prop-acc · deposit · 场景 - 资金保留并关账(法律保留/业户失联) +aliases: + - 强制保留关账 + - ForceClose retain + - 资金保留归档 + - force-close-retain + - 场景-押金资金保留关账 +tags: + - 场景 + - prop-acc + - 保证金 + - 强制关账 +audience: + - 业务人员 +status: 已发布 +sub_feature: deposit +last_review: 2026-05-25 +code_version: 2026-05-22 +--- + +# 场景:资金保留并关账(法律保留 / 业户失联) + +账户 **Frozen** 状态、**有余额**,但**不确定该退还还是扣罚** —— 业户失联、遗产分配延迟、法律保留期未满、案件审理中等。物业用 `ForceCloseAction` 选 `retain` disposition,**关闭账户但保留余额** —— 资金留在物业账上,等业户回来或法律决定。 + +## 典型情境 + +> [!example] 真实情境(一) +> 装修公司倒闭,法人失联。"王装修有限公司"账户里还有 ¥15,000(为 3 户业主代缴的押金)。3 户业主中 2 户已自费维修,1 户仍在装修。物业不知该退给装修公司(已失联)、退给业主(账面缴款人不是业主)、还是扣罚作维修款。决定:**保留资金,关账户,等法律或公司清算结果**。 + +> [!example] 真实情境(二) +> 张阿姨因家中变故住进养老院,装修保证金账户里还有 ¥4,000。家属正在办继承公证,需 6 个月。物业不能擅自退给"自称是子女的人",也不能扣罚(无任何违约)。**保留资金,关账户,等继承公证结果**。 + +## 业户 / 业户家属视角 + +### 您(或亲属)会感受到什么 + +- 账户被关闭,但**没有任何退款**或扣罚通知 +- 系统通知:"您的押金账户已结清,资金 ¥X 暂留物业代管,事由 XXX" +- 小程序"我的押金账户"显示 "🔒 已结清,余额保留中" +- **资金仍属于您 / 您家属 / 法定继承人**,物业不可挪用 + +### 您要做什么 + +- 在法律 / 继承 / 公司清算等程序结束后,**主动联系物业** +- 出示身份证明(本人 / 继承证明 / 司法授权文件) +- 物业核实后**重新进入退款流程**(走线下操作,系统层面**不重启账户**) + +## 业务人员视角 + +### 第 1 步:确认 retain 判定 + +**业务上要符合**以下任一情境: + +- 业户失联(无法核实身份,无法退款) +- 业户已故,继承未定 +- 装修公司清算 / 倒闭,无明确债权人 +- 法律保留期内(司法要求资金不动) +- 任何**不该退、不该扣**的临时归档需求 + +> [!warning] 不要把 retain 当万能选项 +> retain 是为**长期不确定**的情境设计的。短期纠纷应走 [[freeze-during-dispute|冻结]] 等结果。retain 是终态,关账后**永久无法在本账户继续操作**。 + +### 第 2 步:打开 Frozen 账户 + +后台 → 保证金 → 账户列表 → 找 Frozen 账户 → 进 `ViewDepositAccount`。 + +### 第 3 步:点击 `ForceCloseAction`(标签"强制关账") + +Modal 表单: + +| 字段 | 填什么 | +|---|---| +| **处置方式 (disposition)** | 选 ✅ **`retain`(保留归档)** | +| **保留事由(retain_reason)** | **必填**(关键!),如 "装修公司倒闭,3 户业主代缴款,清算未结" | +| **关账事由(memo)** | 必填,如 "ForceClose retain,等待清算结果" | + +> [!info] retain_reason 与 memo 的区别 +> - `retain_reason`:**业务背景**,为什么保留(写给将来要追溯的人看) +> - `memo`:**操作意图**,这次操作做了什么 +> +> 两个字段都进 `meta`,审计时一起查。 + +> [!warning] retain 不写 retain_reason 会被守护拦截 +> `ForceCloseDepositAccountAction` 对 disposition=retain 时强制要求 `retain_reason` 非空。这是刻意设计 —— 防止"无理由保留"导致后续追溯困难。 + +### 第 4 步:提交 + +系统调 `ForceCloseDepositAccountAction(disposition=retain)`,事务内: + +1. 校验 `isFrozen() && hasBalance()` +2. 校验 `retain_reason` 非空 +3. **不建 CollectionOrder**(没有资金动作,余额不变) +4. **不建 DepositTransaction**(同上) +5. **不建 Receipt**(同上) +6. 更新 `status=Closed`(从 Frozen) +7. 在 `meta` 写入审计字段: + - `force_closed_disposition: 'retain'` + - `force_closed_memo: ...` + - `force_closed_at: now` + - **`balance_held_amount`**: 保留的金额 + - **`balance_held_reason`**: retain_reason 的副本 +8. 账户 `balance` 字段保持原值(例如 ¥5,000)—— **不清零** + +### 第 5 步:线下记录归档 + +- 物业财务把这种"已 Closed 但有余额"账户列入**待处理代管资金清单** +- 银行账户里对应的资金做**专项隔离**(账面与物业自有资金分开) +- 每月 / 每季对账时核对 + +### 第 6 步:业户出现时 + +业户(或家属 / 继承人 / 清算人)出现并出示合法身份后: + +| 选项 | 操作 | +|---|---| +| 退还给业户 | **开新账户** → 走 `Deposit` 把 retain 余额转入新账户(系统层 deposit + 备注"原 retain 账户转入")→ 立刻 [[refund-full-no-damage|退款]] | +| 扣罚处理 | **开新账户** → 同上流程 → [[forfeit-damage-public-area|扣罚]] | +| 物业内部决议无主资金 | 走法律程序(物业法务 / 街道办)→ 司法判决归属 → 按判决处理 | + +> [!warning] 不要试图"重启" Closed 账户 +> Closed 账户永久关闭。`canBeReopened` 永远 false。所有"业户回来了"的处理都通过**新账户**走。 + +## 系统流程 + +```mermaid +sequenceDiagram + participant 业户家属 + participant 财务 + participant Filament + participant ForceCloseDepositAccountAction + participant 数据库 + + Note over 业户家属,财务: 业户失联,余额 5000 留 Frozen 状态 + + 财务->>Filament: ViewDepositAccount → ForceCloseAction(disposition=retain, retain_reason) + Filament->>ForceCloseDepositAccountAction: handle(account, retain, retain_reason, memo) + ForceCloseDepositAccountAction->>ForceCloseDepositAccountAction: isFrozen() && hasBalance()? yes + ForceCloseDepositAccountAction->>ForceCloseDepositAccountAction: retain_reason 非空? yes + ForceCloseDepositAccountAction->>数据库: 开启事务 + ForceCloseDepositAccountAction->>数据库: 1. balance 不变(5000) + ForceCloseDepositAccountAction->>数据库: 2. status=Closed + ForceCloseDepositAccountAction->>数据库: 3. meta.force_closed_disposition=retain
+ balance_held_amount=5000
+ balance_held_reason + Note over 数据库: 不建 CO/Transaction/Receipt + ForceCloseDepositAccountAction->>数据库: 提交事务 + Filament-->>财务: 成功通知 + + Note over 业户家属,财务: 数年后家属持继承公证出现 + + 业户家属->>财务: 我是继承人,要领回 5000 + 财务->>财务: 核验继承文件 + 财务->>Filament: 开新 DepositAccount + 缴款 5000 (备注"原 retain 转入") + 财务->>Filament: 立即 RefundAction (5000) → 自动 Closed + 财务-->>业户家属: 退款 + 红字收据 +``` + +## 流水台账(本场景完整记录) + +| 流水 | 说明 | +|---|---| +| (无)| 整个 retain 关账过程**没有任何 DepositTransaction**,余额未变,只有账户状态变更 + meta 审计字段 | + +后续如果业户家属出现: + +| 流水 | 说明 | +|---|---| +| (新账户的)deposit | 把 retain 余额转入新账户 | +| (新账户的)refund | 退给业户家属 | + +## meta 字段示例(retain 关账后) + +```json +{ + "force_closed_disposition": "retain", + "force_closed_memo": "ForceClose retain,等待清算结果", + "force_closed_at": "2026-05-25T14:32:01+08:00", + "force_closed_by": 42, + "balance_held_amount": 5000.00, + "balance_held_reason": "装修公司倒闭,3 户业主代缴款,清算未结" +} +``` + +## 常见问题 + +> [!question] retain 后账户有余额但 status=Closed,这不是矛盾吗? +> 不矛盾。Closed 表示"业务终结",`balance` 字段表示"账面余额"。两者**独立**: +> +> - Closed + balance=0:正常关账(refund / forfeit 后) +> - Closed + balance>0:retain 关账,业务终结但资金仍归业户 +> +> 系统设计上,`canBeClosed()` 要求 `balance==0` 是给**正常路径**用的;ForceClose 走专用 Policy 不受此限制。 + +> [!question] retain 之后报表上余额怎么算? +> 物业代管资金报表: +> - 全 Active 账户 `balance` 之和 +> - **加上** Closed 账户中 `meta.balance_held_amount` 之和 +> +> 后者是"待处置代管资金",银行账户里对应的钱仍属业户。详见 [[audit-monthly-deposit-balance]]。 + +> [!question] retain 的资金物业能挪用吗? +> **不能,法律上是业户的钱**。物业的会计科目仍为"其他应付款",银行账户里这部分资金做隔离专户最规范。任何挪用都是违法。 + +> [!question] 长期 retain(10 年以上)的资金怎么处理? +> 根据各地民法 / 物业管理条例,长期无主资金可能转入: +> - 街道办无主资金账户 +> - 公益基金 +> - 财政 +> +> 具体走流程视司法管辖,**系统不主动处理** —— 物业法务发起,系统配合记录(可能在该账户 meta 加一笔 `transferred_to_*` 备注,或者依照流程做反向 forfeit 然后转出)。 + +> [!question] retain 的金额能在已关账户上"动一下"吗(例如部分释放、部分继续保留)? +> 不能。账户 Closed 后任何操作都不允许。如果业务需要"部分释放":开新账户 → 把保留余额按 X 元转入(deposit)→ 退给业户(refund)→ 剩余在新账户继续 retain(再 ForceClose retain 一次)。 + +> [!question] retain 不写 retain_reason 系统会怎样? +> `ForceCloseDepositAccountAction` 会校验失败,抛出错误。Modal 表单也会前端校验阻止提交。这是**刻意设计**:无理由 retain = 后续无法追溯 = 不合规。 + +## 异常分支 + +- 业户出现配合退款 → 开新账户 + deposit + refund(详见上方"业户出现时"步骤) +- 业户最终被司法判定无主 → 物业法务发起转出流程 +- 短期纠纷不适用 retain → 走 [[freeze-during-dispute]] 然后 [[unfreeze-after-mediation]] + +## 相关文档 + +- [[force-close-refund]] +- [[force-close-forfeit]] +- [[account-state-machine]] +- [[freeze-during-dispute]] +- [[audit-monthly-deposit-balance]] +- [[audit-long-pending-accounts]]