P05-02: 全費目と資金区分:表金・裏金、維持費未払い、仕入れ、人件費、元本、利息、税の帳簿への接続(原 P05-02 の完了の条件)

This commit is contained in:
Agent Control Runner
2026-10-23 21:14:42 +09:00
parent d65e3da9f3
commit 8954914564
6 changed files with 115 additions and 14 deletions
+4 -1
View File
@@ -3677,7 +3677,10 @@ func get_loan_principal_report() -> Dictionary:
loans.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return String(left[&"loan_id"]) < String(right[&"loan_id"])) loans.sort_custom(func(left: Dictionary, right: Dictionary) -> bool: return String(left[&"loan_id"]) < String(right[&"loan_id"]))
var valid: bool = _state.get_resource(&"gold") == cash and (loans.is_empty() or _loan_principal_ledger_is_valid(economy_service)) var valid: bool = _state.get_resource(&"gold") == cash and (loans.is_empty() or _loan_principal_ledger_is_valid(economy_service))
var ended: bool = _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal() var ended: bool = _operations_enabled and operations_coordinator != null and operations_coordinator.is_terminal()
return {&"available": true, &"ledger_valid": valid, &"terminal": ended, &"cash": cash, &"next_command_id": _next_command_id, &"loans": loans} # A09: a repayment may use the hidden fund for what the front fund (cash) cannot pay.
return {&"available": true, &"ledger_valid": valid, &"terminal": ended, &"cash": cash,
&"back_cash": economy_service.get_balance(FinancePolicy.BACK_CASH), &"available_funds": FinancePolicy.available_funds(economy_service),
&"next_command_id": _next_command_id, &"loans": loans}
## Atomic principal payment against an already attested loan liability. ## Atomic principal payment against an already attested loan liability.
@@ -3931,7 +3931,7 @@
"template": "PASS: <checks> checks", "template": "PASS: <checks> checks",
"required": true, "required": true,
"fixed_check_count_used": false, "fixed_check_count_used": false,
"source_sha256_at_schema_audit": "0dcef5fb731753e1a8f4269b445aa0df68e5428431e3422712c27056008edeba", "source_sha256_at_schema_audit": "4c8f929b0de76198182dfaaef4be4bea7961faa9c31c01889497d6675e0c5def",
"authority": { "authority": {
"path": "docs/plans/original-completion-2026-10-02/execution/p05-02/SPEC.md", "path": "docs/plans/original-completion-2026-10-02/execution/p05-02/SPEC.md",
"scope": "new P05-02 suite marker; native execution pending" "scope": "new P05-02 suite marker; native execution pending"
@@ -0,0 +1,24 @@
# P05-02 修正3: 通常の帳簿の画面の返済(A09)と、残りの試験と開示
- S `d65e3da9f33d399c598ad88296e4a9d88d847a7b` の whole-unit の独立レビュー(fresh、local_92520fb7)は changes_requested(medium 1・low 4)。F1〜F8 は証拠の上で満たすとされ、残りは下の指摘。正規の受付で `repair_pending`、commander の修正3(`COMMANDER_REPAIR_PLAN_P05_02_R03.md`、event 28、repairs 3)。S `d65e3da` までの native・全 roster・validation・review・容量の記録と失敗は全て保持し、新しい S に流用しない。原本は 23/88 のまま。
## 直したもの
- 指摘1(medium、F2 と横断条件「原本条件と UI 操作の一致」): 通常の帳簿の画面(`scripts/ui/operating_ledger_panel.gd`)の返済の上限・案内・拒否を、表金だけ(`cash`)から表金+裏金(`FinancePolicy.available_funds`)にした。`SimulationRuntime.get_loan_principal_report` に `back_cash` と `available_funds` を足した(`cash` は今までどおり表金)。案内は「表金 xG・裏金 yG(計 zG)から元本を返済します。表金で足りない分は裏金から補填します。」と出し、完済に足りない時は「完済には表金と裏金の合計が不足しています。」と出す。返済の command は今までどおり Runtime の `repay_loan_principal` で、表金から先に払い、残りを裏金からの補填と同じ1つの取引で記帳する(A09)。既存の試験が確かめる文言(「借入はありません」「受理済み」「実行できません」「…Gを返済」)は残した。
- 試験 `_test_ledger_repayment_uses_hidden_funds`: 通常の帳簿の画面の部品で、表金 20・裏金 300 の時に 200 の返済が押せ、1つの取引(裏金からの補填 180 → 元本 200)になること、残元本 800 の完済は表金+裏金 120 では押せず、500 の指定も押せず、押しても状態が変わらないこと、裏金が 820 になると完済が押せ、1つの取引(補填 800 → 元本 800)で完済し、表金 0・裏金 20・Gold が表金を写すこと、save から戻してそのまま一致すること。
- 指摘2(low): 税と利息の滞納も §3.8 の `arrears_total` に入るので、建設と投資の制限にも入る(開示)。締めの日次の記録の `arrears_total` に税・利息の滞納が入り、Operations の調整役の判定(`can_start_construction_or_investment`)が `ARREARS_REMAIN` で止める。その後の Runtime の判定(`_operations_construction_investment_gate`。`_operations_total_arrears` に `RANK_TAX_PAYABLE` と `LOAN_INTEREST_PAYABLE` の残高が入る)も、残高が残る間は `UNPAID_EXPENSES` で止める。試験 `_test_tax_arrears_next_day`: 税の滞納 20 がある日に建設の command が `ARREARS_REMAIN`(arrears 20)で拒まれ、翌日の締めで払った後に建てられる(表金から 20)こと(局所の確認で実際の理由が `ARREARS_REMAIN` だったので、レビューの指摘の `UNPAID_EXPENSES` ではなくそれを確かめる)。
- 指摘3(low): 試験を足した。表金 0・裏金 663 の時に建設(`guest_corridor`、20)が拒まれ、裏金が減らず、補填が増えないこと(建設は A09 の対象外。`_test_operation_top_ups_and_day_total`)。税の滞納が残る日に、帳簿の画面の締めの欄が「未払い 税 20G・利息 0G」、今日の欄が「(未払い 20G)」と出すこと(`_test_tax_arrears_next_day`)。罰金・event の支払など他の対象外の支払は、コードで表金だけから払うことを確かめたが試験は無い(開示)。
## 文言の補足(指摘4、low)
- 凍結の SPEC(契約の `spec_sha256`)と DB の契約は変えない。SPEC §3.5 の「元本の出入り(financing_principal_*)は今までどおり売上と経費に入れない」は、原本どおり次の意味で読む: 借入の元本の入金は売上に入れない(F4)。元本の返済は原本どおり日次の経費(`financing_principal_outflows`。`scripts/operations/operations_coordinator.gd` の `_net_profit` が日次の利益から引き、帳簿の画面は「元本返済(決算経費)」と出す。原子要件の借金返済の経費)。この単位はこの挙動を変えていない。
## 範囲の外で直していないもの(指摘5、low)
- 仕入れの画面 `scripts/ui/supply_management_panel.gd` は契約の allowed_paths の外なので、この単位では直さない。今の画面は「手持ち %dG」に表金だけを出し、見積の「購入後 %dG」は preview の `cash_after`(A09 で表金+裏金からの残り)を出すので、例えば表金 2・裏金 100 で 15 の仕入れでは「手持ち 2G」と「購入後 87G」が並ぶ。操作は A09 どおりに通る。
- 具体的な差分の案: 「手持ち %dG」を「表金 %dG・裏金 %dG」に分けて出し(`economy_service.get_balance(FinancePolicy.BACK_CASH)`)、「購入後 %dG」を「購入後(表金+裏金)%dG」とし、表金で足りない分を裏金から補うことを一言出す。
- 正規の扱い: この画面を範囲に含む別の登録(仕入れ・UI の単位、または P05-03 の登録で allowed_paths に入れる)で直す。それまでは未修正で、P05-02 の完了に数えない。
## 変えていないもの
- 登録の argv、`tools/run_p05_funds_tests.py`、狭い roster(8 suites+再開の2段、実 10 段)、試験の既存の期待値(`EXPECTATION_CHANGES.json` に足すものは無い)、凍結の SPEC、DB の契約。
+13 -11
View File
@@ -230,7 +230,7 @@ func _build_repayment_controls(style_button: Callable) -> void:
loan_amount = LineEdit.new() loan_amount = LineEdit.new()
loan_amount.name = "LoanRepaymentAmount" loan_amount.name = "LoanRepaymentAmount"
loan_amount.placeholder_text = "返済する元本(整数G)" loan_amount.placeholder_text = "返済する元本(整数G)"
loan_amount.tooltip_text = "残元本と手持ちを超えない整数額を入力" loan_amount.tooltip_text = "残元本と表金・裏金の合計を超えない整数額を入力"
_loan_box.add_child(loan_amount) _loan_box.add_child(loan_amount)
loan_amount.text_changed.connect(func(_text: String) -> void: _refresh_repayment_controls()) loan_amount.text_changed.connect(func(_text: String) -> void: _refresh_repayment_controls())
loan_amount.text_submitted.connect(func(_text: String) -> void: submit_repayment(false)) loan_amount.text_submitted.connect(func(_text: String) -> void: submit_repayment(false))
@@ -289,12 +289,14 @@ func _refresh_repayment_controls() -> void:
var row := _loan_row(report) var row := _loan_row(report)
var remaining := int(row.get(&"outstanding_principal", 0)) var remaining := int(row.get(&"outstanding_principal", 0))
var cash := int(report.get(&"cash", 0)) var cash := int(report.get(&"cash", 0))
# A09: the front fund pays first and the hidden fund covers the rest in the same posting.
var funds := int(report.get(&"available_funds", cash))
var waiting := not (row.get(&"pending_command_ids", []) as Array).is_empty() var waiting := not (row.get(&"pending_command_ids", []) as Array).is_empty()
var allowed := bool(report.get(&"available", false)) and bool(report.get(&"ledger_valid", false)) and not bool(report.get(&"terminal", false)) and remaining > 0 and not waiting var allowed := bool(report.get(&"available", false)) and bool(report.get(&"ledger_valid", false)) and not bool(report.get(&"terminal", false)) and remaining > 0 and not waiting
var text := loan_amount.text.strip_edges() var text := loan_amount.text.strip_edges()
var amount := int(text) if text.is_valid_int() else 0 var amount := int(text) if text.is_valid_int() else 0
repay_button.disabled = not allowed or amount <= 0 or amount > remaining or amount > cash repay_button.disabled = not allowed or amount <= 0 or amount > remaining or amount > funds
repay_all_button.disabled = not allowed or remaining > cash repay_all_button.disabled = not allowed or remaining > funds
loan_amount.editable = allowed loan_amount.editable = allowed
if not bool(report.get(&"available", false)) or not bool(report.get(&"ledger_valid", false)): if not bool(report.get(&"available", false)) or not bool(report.get(&"ledger_valid", false)):
_loan_hint.text = "返済の帳簿を確認できません。" _loan_hint.text = "返済の帳簿を確認できません。"
@@ -303,13 +305,13 @@ func _refresh_repayment_controls() -> void:
elif loans.is_empty(): elif loans.is_empty():
_loan_hint.text = "借入はありません。" _loan_hint.text = "借入はありません。"
elif waiting: elif waiting:
_loan_hint.text = "返済を受理済みです。次の時間更新で実行し、手持ちと残元本を再確認します。" _loan_hint.text = "返済を受理済みです。次の時間更新で実行し、表金・裏金と残元本を再確認します。"
elif remaining == 0: elif remaining == 0:
_loan_hint.text = "この借入は完済しています。" _loan_hint.text = "この借入は完済しています。"
else: else:
_loan_hint.text = "手持ち %dGから元本を返済します。残元本 %dG。返済額は日次経費に含まれます。" % [cash, remaining] _loan_hint.text = "表金 %dG・裏金 %dG(計 %dG)から元本を返済します。表金で足りない分は裏金から補填します。残元本 %dG。返済額は日次経費に含まれます。" % [cash, funds - cash, funds, remaining]
if remaining > cash: _loan_hint.text += "\n完済には手持ちが不足しています。" if remaining > funds: _loan_hint.text += "\n完済には表金と裏金の合計が不足しています。"
if not text.is_empty() and repay_button.disabled: _loan_hint.text += "\n残元本と手持ちを超えない正の整数額を入力してください。" if not text.is_empty() and repay_button.disabled: _loan_hint.text += "\n残元本と表金・裏金の合計を超えない正の整数額を入力してください。"
func submit_repayment(full: bool = false) -> void: func submit_repayment(full: bool = false) -> void:
@@ -318,8 +320,8 @@ func submit_repayment(full: bool = false) -> void:
var remaining := int(row.get(&"outstanding_principal", 0)) var remaining := int(row.get(&"outstanding_principal", 0))
var text := loan_amount.text.strip_edges() var text := loan_amount.text.strip_edges()
var amount := remaining if full else (int(text) if text.is_valid_int() else 0) var amount := remaining if full else (int(text) if text.is_valid_int() else 0)
if not bool(report.get(&"ledger_valid", false)) or bool(report.get(&"terminal", false)) or row.is_empty() or not (row.get(&"pending_command_ids", []) as Array).is_empty() or amount <= 0 or amount > remaining or amount > int(report.get(&"cash", 0)): if not bool(report.get(&"ledger_valid", false)) or bool(report.get(&"terminal", false)) or row.is_empty() or not (row.get(&"pending_command_ids", []) as Array).is_empty() or amount <= 0 or amount > remaining or amount > int(report.get(&"available_funds", report.get(&"cash", 0))):
_set_repayment_result("返済できません。手持ち・残元本・返済待ちを確認してください。", false) _set_repayment_result("返済できません。表金・裏金・残元本・返済待ちを確認してください。", false)
_refresh_repayment_controls() _refresh_repayment_controls()
return return
var key := StringName("ui.loan.repay.%d" % int(report[&"next_command_id"])) var key := StringName("ui.loan.repay.%d" % int(report[&"next_command_id"]))
@@ -332,7 +334,7 @@ func submit_repayment(full: bool = false) -> void:
var after := _loan_row(_loan_report()) var after := _loan_row(_loan_report())
if result.is_success() and (after.get(&"pending_command_ids", []) as Array).has(int(result.command_id)): if result.is_success() and (after.get(&"pending_command_ids", []) as Array).has(int(result.command_id)):
_loan_pending_command = int(result.command_id) _loan_pending_command = int(result.command_id)
_set_repayment_result("返済を受理しました。実行時に手持ちと残元本を確認します。", true) _set_repayment_result("返済を受理しました。実行時に表金・裏金と残元本を確認します。", true)
else: else:
_show_repayment_result(result) _show_repayment_result(result)
_refresh_repayment_controls() _refresh_repayment_controls()
@@ -343,7 +345,7 @@ func _show_repayment_result(result: Variant) -> void:
_set_repayment_result("元本 %dGを返済しました。残元本 %dG。" % [int(result.details.get(&"paid_principal", 0)), int(result.details.get(&"outstanding_principal", 0))], true) _set_repayment_result("元本 %dGを返済しました。残元本 %dG。" % [int(result.details.get(&"paid_principal", 0)), int(result.details.get(&"outstanding_principal", 0))], true)
loan_amount.text = "" loan_amount.text = ""
else: else:
_set_repayment_result("返済を実行できませんでした。手持ちと残元本を確認してください。", false) _set_repayment_result("返済を実行できませんでした。表金・裏金と残元本を確認してください。", false)
func _on_repayment_completed(result: Variant) -> void: func _on_repayment_completed(result: Variant) -> void:
+72
View File
@@ -42,6 +42,7 @@ func _run() -> void:
_test_operation_top_ups_and_day_total() _test_operation_top_ups_and_day_total()
_test_legacy_midnight_top_up() _test_legacy_midnight_top_up()
_test_tax_arrears_next_day() _test_tax_arrears_next_day()
_test_ledger_repayment_uses_hidden_funds()
await _test_ledger_opened_from_main() await _test_ledger_opened_from_main()
await process_frame await process_frame
if _failures == 0: if _failures == 0:
@@ -599,6 +600,14 @@ func _test_operation_top_ups_and_day_total() -> void:
_expect(int(funds.get(&"fund_report_version", -1)) == 2, "the report carries its definition version") _expect(int(funds.get(&"fund_report_version", -1)) == 2, "the report carries its definition version")
_expect(economy.get_balance(BACK) == 1000 - total and economy.get_balance(FRONT) == 0, "hidden funds fell by exactly the day's top-ups") _expect(economy.get_balance(BACK) == 1000 - total and economy.get_balance(FRONT) == 0, "hidden funds fell by exactly the day's top-ups")
_expect(_closed_ledger_text(runtime, clock).contains("裏金から表金へ補填 %dG" % total), "the ledger's close shows the day's every top-up") _expect(_closed_ledger_text(runtime, clock).contains("裏金から表金へ補填 %dG" % total), "the ledger's close shows the day's every top-up")
# Construction is outside A09: with no front funds it is refused and the hidden fund is not used.
var refused: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)})
var top_ups_after := 0
for entry: LedgerEntry in economy.get_entries():
if entry.category == FinancePolicy.TOP_UP_CATEGORY:
top_ups_after += entry.amount
_expect(not refused.is_success() and economy.get_balance(BACK) == 1000 - total and top_ups_after == total,
"A09 does not cover construction: refused with front 0, hidden funds untouched (%s)" % refused.details.get(&"reason", refused.message))
var saved: Dictionary = runtime.create_snapshot() var saved: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the day with three top-ups restores exactly") _expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the day with three top-ups restores exactly")
# A report saved before the mark (back_top_up = the close's own top-up only) loads by that definition. # A report saved before the mark (back_top_up = the close's own top-up only) loads by that definition.
@@ -682,17 +691,80 @@ func _test_tax_arrears_next_day() -> void:
var funds1 := _day_funds(runtime, 1) var funds1 := _day_funds(runtime, 1)
_expect(int(funds1.get(&"rank_tax", -1)) == 20 and int(funds1.get(&"rank_tax_paid", -1)) == 0 and int(funds1.get(&"rank_tax_arrears", -1)) == 20, "A09: the unpaid tax carries over") _expect(int(funds1.get(&"rank_tax", -1)) == 20 and int(funds1.get(&"rank_tax_paid", -1)) == 0 and int(funds1.get(&"rank_tax_arrears", -1)) == 20, "A09: the unpaid tax carries over")
_expect(int(runtime.operations_coordinator.get_day_summary(1)[&"settlement"][&"arrears_total"]) == 20 and int(runtime.get_operations_report().get(&"arrears_total", -1)) == 20, "the arrears include the tax") _expect(int(runtime.operations_coordinator.get_day_summary(1)[&"settlement"][&"arrears_total"]) == 20 and int(runtime.get_operations_report().get(&"arrears_total", -1)) == 20, "the arrears include the tax")
# F7 with arrears: the ledger shows the unpaid tax in the close and in today's cash section.
var panel := OperatingLedgerPanel.new()
root.add_child(panel)
panel.setup(runtime, fixture[&"clock"])
panel.select_latest_close()
_expect(panel._closed_label.text.contains("未払い 税 20G・利息 0G") and panel._cash_label.text.contains("(未払い 20G)"), "the ledger shows the unpaid tax")
panel.queue_free()
# The closed day's arrears_total includes the tax arrears, so the Operations gate holds
# construction and investment until they are paid.
var blocked: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)})
_expect(not blocked.is_success() and StringName(blocked.details.get(&"reason", &"")) == &"ARREARS_REMAIN" and int(blocked.details.get(&"arrears_total", -1)) == 20,
"tax arrears stop construction (ARREARS_REMAIN, arrears 20)")
_advance_to(fixture, 1440 + 600) _advance_to(fixture, 1440 + 600)
_expect(_sale(fixture, FRONT_SALES, FRONT, 100, "tax.d2"), "day 2 sale 100") _expect(_sale(fixture, FRONT_SALES, FRONT, 100, "tax.d2"), "day 2 sale 100")
_advance_to(fixture, 2880) _advance_to(fixture, 2880)
var funds2 := _day_funds(runtime, 2) var funds2 := _day_funds(runtime, 2)
_expect(int(funds2.get(&"rank_tax", -1)) == 10 and int(funds2.get(&"rank_tax_paid", -1)) == 30 and int(funds2.get(&"rank_tax_arrears", -1)) == 0, "day 2 pays the older 20 and the new 10") _expect(int(funds2.get(&"rank_tax", -1)) == 10 and int(funds2.get(&"rank_tax_paid", -1)) == 30 and int(funds2.get(&"rank_tax_arrears", -1)) == 0, "day 2 pays the older 20 and the new 10")
_expect(economy.get_balance(FRONT) == 70 and economy.get_balance(FinancePolicy.RANK_TAX_PAYABLE) == 0, "front 70, no tax payable left") _expect(economy.get_balance(FRONT) == 70 and economy.get_balance(FinancePolicy.RANK_TAX_PAYABLE) == 0, "front 70, no tax payable left")
var built: CommandResult = runtime.submit_command(&"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)})
_expect(built.is_success() and economy.get_balance(FRONT) == 50, "once the tax is paid, construction is allowed again (20 from front funds)")
var saved: Dictionary = runtime.create_snapshot() var saved: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the arrears days restore exactly") _expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the arrears days restore exactly")
_free_fixture(fixture) _free_fixture(fixture)
# F2 through the ordinary ledger: the panel's repayment controls cap by front + hidden
# funds (A09), and a specified or full repayment that front funds cannot pay is one
# transaction: the front fund pays first and the hidden fund covers the rest.
func _test_ledger_repayment_uses_hidden_funds() -> void:
var fixture := _new_fixture(5035)
var runtime: Variant = fixture[&"runtime"]
var clock: Variant = fixture[&"clock"]
var economy: Variant = runtime.economy_service
_advance_to(fixture, 420)
_expect(_move(fixture, LOAN, FRONT, 1000, &"LOAN_PRINCIPAL_BORROWED", "panel.borrow"), "fixture principal 1000")
_expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", economy.get_balance(FRONT) - 20, &"OPERATING_EXPENSE", "panel.drain")
and _sale(fixture, BACK_SALES, BACK, 300, "panel.back"), "front 20, hidden 300")
var panel := OperatingLedgerPanel.new()
root.add_child(panel)
panel.setup(runtime, clock)
panel.loan_amount.text = "200"
panel.loan_amount.text_changed.emit("200")
var hint: String = panel.get_node("LoanRepaymentHint").text
_expect(not panel.repay_button.disabled and hint.contains("表金 20G・裏金 300G(計 320G)") and hint.contains("裏金から補填"), "a 200 repayment over front 20 is offered with the hidden fund")
var key := StringName("ui.loan.repay.%d" % int(runtime.get_loan_principal_report()[&"next_command_id"]))
panel.repay_button.pressed.emit()
var entries: Array = economy.get_transaction_entries(economy.get_transaction_id_for_idempotency_key(key))
_expect(entries.size() == 2 and entries[0].category == FinancePolicy.TOP_UP_CATEGORY and entries[0].amount == 180 and entries[1].category == &"LOAN_PRINCIPAL_REPAID" and entries[1].amount == 200,
"one transaction: 180 hidden top-up, then the 200 principal")
_expect(economy.get_balance(FRONT) == 0 and economy.get_balance(BACK) == 120 and -economy.get_balance(LOAN) == 800 and panel.get_node("LoanRepaymentResult").text.contains("200Gを返済"),
"front 20 first, hidden 180 for the rest; 800 left")
_expect(panel.repay_all_button.disabled and panel.get_node("LoanRepaymentHint").text.contains("完済には表金と裏金の合計が不足しています"), "full repayment of 800 waits: front + hidden is 120")
panel.loan_amount.text = "500"
panel.loan_amount.text_changed.emit("500")
_expect(panel.repay_button.disabled, "500 over front + hidden 120 is not offered")
var before: Dictionary = runtime.create_snapshot()
panel.submit_repayment(true)
panel.submit_repayment(false)
_expect(var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "pressed anyway, the unaffordable repayments change nothing")
_expect(_sale(fixture, BACK_SALES, BACK, 700, "panel.back2"), "hidden funds reach 820")
panel.refresh()
_expect(not panel.repay_all_button.disabled, "full repayment of 800 is offered with front 0 and hidden 820")
var full_key := StringName("ui.loan.repay.%d" % int(runtime.get_loan_principal_report()[&"next_command_id"]))
panel.repay_all_button.pressed.emit()
var full: Array = economy.get_transaction_entries(economy.get_transaction_id_for_idempotency_key(full_key))
_expect(full.size() == 2 and full[0].category == FinancePolicy.TOP_UP_CATEGORY and full[0].amount == 800 and full[1].amount == 800,
"the full repayment is one transaction, all from hidden funds")
_expect(economy.get_balance(LOAN) == 0 and economy.get_balance(FRONT) == 0 and economy.get_balance(BACK) == 20 and fixture[&"state"].get_resource(&"gold") == 0, "the loan is repaid; front 0, hidden 20, Gold mirrors front funds")
panel.queue_free()
var saved: Dictionary = runtime.create_snapshot()
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the panel repayments restore exactly")
_free_fixture(fixture)
# F7: the ordinary ledger panel reads both funds, the rank tax, the interest, the # F7: the ordinary ledger panel reads both funds, the rank tax, the interest, the
# top-ups and their arrears from the same ledger and daily closes. # top-ups and their arrears from the same ledger and daily closes.
func _check_ledger_panel(runtime: Variant, clock: Variant) -> void: func _check_ledger_panel(runtime: Variant, clock: Variant) -> void:
+1 -1
View File
@@ -416,7 +416,7 @@
"new_audio_QA": false "new_audio_QA": false
}, },
"tests/economy/test_p05_funds_runtime.gd": { "tests/economy/test_p05_funds_runtime.gd": {
"source_sha256": "0dcef5fb731753e1a8f4269b445aa0df68e5428431e3422712c27056008edeba", "source_sha256": "4c8f929b0de76198182dfaaef4be4bea7961faa9c31c01889497d6675e0c5def",
"ordinary_cleanup_prepared": true, "ordinary_cleanup_prepared": true,
"ordinary_control_labels": [ "ordinary_control_labels": [
"single" "single"