Files
TheTree_mock/scripts/ui/operating_ledger_panel.gd
T
Takeshi 5dde0c5ec2 Record original-alignment implementation and traceability checkpoint
Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
2026-09-30 23:52:24 +09:00

163 lines
7.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class_name OperatingLedgerPanel
extends VBoxContainer
const LayerSkin := preload("res://scripts/ui/layer_interface_skin.gd")
## Projection only: authoritative close and ledger entries own all amounts.
var _initial_layer := 0
var _runtime: Variant
var _clock: Variant
var _cash_label: Label
var _day_picker: OptionButton
var _closed_label: Label
var _facility_label: Label
var _selected_day := -1
var _picker_days: Array[int] = []
func setup(runtime: Variant, clock: Variant, style_button := Callable(), layer := 0) -> void:
_initial_layer = layer
_runtime = runtime
_clock = clock
name = "OperatingLedgerPanel"
add_theme_constant_override("separation", 8)
var heading := Label.new()
heading.text = "帳簿 · 00:00締め"
heading.add_theme_color_override("font_color", Color("55371e"))
LayerSkin.register_text(heading, _initial_layer)
add_child(heading)
_cash_label = _label()
_day_picker = OptionButton.new()
_day_picker.name = "LedgerDayPicker"
if style_button.is_valid(): style_button.call(_day_picker)
_day_picker.item_selected.connect(func(index: int) -> void:
_selected_day = _day_picker.get_item_id(index)
refresh())
add_child(_day_picker)
_closed_label = _label()
_facility_label = _label()
refresh()
func select_latest_close() -> void:
_selected_day = -1
_day_picker.get_popup().hide()
refresh()
func get_report() -> Dictionary:
if _runtime == null:
return {}
var minute: int = _clock.absolute_minute
var business_day := minute / 1440 + 1
var start := (business_day - 1) * 1440
if _runtime.operations_coordinator != null:
business_day = _runtime.operations_coordinator.get_open_financial_day()
start = _runtime.operations_coordinator.get_financial_period_start(business_day)
var inflow := 0
var outflow := 0
var construction_outflow := 0
var wage_outflow := 0
var upkeep_outflow := 0
for entry: LedgerEntry in _runtime.economy_service.get_entries():
if entry.absolute_minute < start or entry.absolute_minute > minute or entry.category == &"INITIALIZATION":
continue
if entry.debit_account == &"PLAYER_CASH":
inflow += entry.amount
if entry.credit_account == &"PLAYER_CASH":
outflow += entry.amount
if entry.category == &"CONSTRUCTION":
construction_outflow += entry.amount
elif entry.category == &"MAINTENANCE_PAYMENT":
upkeep_outflow += entry.amount
elif entry.category == &"WAGE":
wage_outflow += entry.amount
var summaries: Array[Dictionary] = []
if _runtime.operations_coordinator != null:
summaries = _runtime.operations_coordinator.get_day_summaries()
var selected: Dictionary = {}
for summary: Dictionary in summaries:
if int(summary.get(&"day", -1)) == _selected_day:
selected = summary
if selected.is_empty() and not summaries.is_empty():
selected = summaries.back()
return {
&"business_day": business_day, &"period_start": start,
&"cash": _runtime.economy_service.get_balance(&"PLAYER_CASH"),
&"cash_in": inflow, &"cash_out": outflow, &"cash_net": inflow - outflow,
&"construction_cash_out": construction_outflow, &"wage_cash_out": wage_outflow,
&"upkeep_cash_out": upkeep_outflow,
&"upkeep_arrears": _runtime.upkeep_settlement.get_arrears_total(),
&"closed_days": summaries, &"selected_close": selected.duplicate(true),
}
func refresh() -> void:
if _runtime == null or _cash_label == null:
return
if _day_picker.get_popup().visible:
return
var report := get_report()
_cash_label.text = "第%d営業日 · 手持ち %dG\n本日の入金 +%dG / 出金 −%dG\nうち建築 %dG / 給与支払 %dG / 維持費支払 %dG\n未払い維持費 %dG\n資金増減 %+dG(営業利益は下の決算)" % [report[&"business_day"], report[&"cash"], report[&"cash_in"], report[&"cash_out"], report[&"construction_cash_out"], report[&"wage_cash_out"], report[&"upkeep_cash_out"], report[&"upkeep_arrears"], report[&"cash_net"]]
var summary: Dictionary = report[&"selected_close"]
if summary.is_empty():
if not _day_picker.disabled or _day_picker.item_count == 0:
_day_picker.clear()
_picker_days.clear()
_day_picker.add_item("決算は翌日00:00に確定します")
_day_picker.disabled = true
_closed_label.text = "建築後の手持ち資金だけで営業成績は決まりません。宿泊などの売上と日々の費用をここで確認できます。"
_facility_label.text = ""
return
_day_picker.disabled = false
var days: Array[int] = []
for day: Dictionary in report[&"closed_days"]:
days.append(int(day[&"day"]))
if not _day_picker.get_popup().visible:
if days != _picker_days:
_day_picker.clear()
_picker_days = days
for day_number: int in days:
_day_picker.add_item("第%d営業日 · 確定" % day_number, day_number)
_day_picker.select(_picker_days.find(int(summary[&"day"])))
var totals: Dictionary = summary.get(&"category_totals", {})
var other := 0
for key: StringName in [&"maintenance", &"utilities", &"taxes_and_interest", &"fines"]:
other += int(totals.get(key, 0))
_closed_label.text = "売上 %dG / 返金 %dG\n運営費(仕入れ・契約金等) %dG\n給与 %dG / その他 %dG\n営業利益 %+dG\n前日比 %+dG / 未払給与 %dG\n帳簿照合:%s" % [totals.get(&"revenue", 0), totals.get(&"refunds", 0), totals.get(&"cost_of_goods", 0), totals.get(&"wages", 0), other, summary.get(&"net_profit", 0), summary.get(&"previous_day_delta", 0), summary.get(&"arrears_total", 0), "一致" if bool(summary.get(&"balanced", false)) else "差額あり"]
var legacy: bool = _runtime.operations_coordinator.is_legacy_financial_history(int(summary[&"day"]))
_closed_label.text += "\n%s %d回%s" % ["旧版の連続未払い" if legacy else "連続赤字", int(summary.get(&"consecutive_insolvency_count", 0)), "" if legacy else "(3回でゲーム終了)"]
var close_minute := int(summary[&"closed_at"]) % 1440
_closed_label.text += "\n確定時刻 %02d:%02d" % [close_minute / 60, close_minute % 60]
var lines: PackedStringArray = ["施設別 · 売上 / 費用 / 利益"]
var rows: Dictionary = summary.get(&"facility_profit_loss", {})
var ids := rows.keys()
ids.sort()
for id: Variant in ids:
var facility: FacilityInstance = _runtime.facility_service.get_instance(StringName(id))
var label := String(id)
if facility != null:
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(facility.definition_id)
label = "%s (%d,%d)" % [definition.display_name if definition != null else facility.definition_id, facility.origin.x, facility.origin.y]
else:
# Corridors and stairs are completed construction entities without
# service instances, but their ownership upkeep still appears here.
var completed: Dictionary = _runtime.construction_service.get_facility(StringName(id))
if not completed.is_empty():
var definition: FacilityDefinition = _runtime.definition_registry.get_facility(completed[&"definition_id"])
var origin: Vector3i = completed[&"origin"]
label = "%s (%d,%d)" % [definition.display_name if definition != null else completed[&"definition_id"], origin.x, origin.y]
var row: Dictionary = rows[id]
lines.append("%s\n%dG / %dG / %+dG" % [label, row.get(&"revenue", 0), row.get(&"expenses", 0), row.get(&"net_profit", 0)])
_facility_label.text = "\n".join(lines) if not rows.is_empty() else "施設別の記帳はまだありません。"
func _label() -> Label:
var label := Label.new()
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.add_theme_color_override("font_color", Color("55371e"))
LayerSkin.register_text(label, _initial_layer)
label.add_theme_font_size_override("font_size", 13)
add_child(label)
return label