|
|
|
@@ -3,10 +3,14 @@ const FixtureCleanup := preload("res://tests/helpers/ui_audio_fixture_cleanup.gd
|
|
|
|
|
|
|
|
|
|
## P05-02: front (表金) and hidden (裏金) funds, the A09 payment rule, the A10 rank
|
|
|
|
|
## tax, the A11 loan interest, the three business day reconciliation and saves.
|
|
|
|
|
## Sales, debt and drains are explicit ledger fixtures; the daily closes are the
|
|
|
|
|
## production Operations boundaries. The last check opens the ledger from the
|
|
|
|
|
## game's own Main (F7), so the runner gives this suite the ordinary Main
|
|
|
|
|
## audio cleanup proof (tools/regression/suite-policy.json).
|
|
|
|
|
## The policy and reconciliation scenes use explicit ledger fixtures for sales,
|
|
|
|
|
## debt and drains, with the production Operations closes. The repair 2 scenes
|
|
|
|
|
## run the production paths themselves: built and staffed facilities whose guests
|
|
|
|
|
## complete their services, the automatic purchase, the repayment command and the
|
|
|
|
|
## legacy 00:00 upkeep; a fixture there only sets a balance (a loan, a drain), never
|
|
|
|
|
## the payment under test. The last check opens the ledger from the game's own Main
|
|
|
|
|
## (F7), so the runner gives this suite the ordinary Main audio cleanup proof
|
|
|
|
|
## (tools/regression/suite-policy.json).
|
|
|
|
|
|
|
|
|
|
const FRONT := &"PLAYER_CASH"
|
|
|
|
|
const BACK := &"BACK_CASH"
|
|
|
|
@@ -34,6 +38,10 @@ func _run() -> void:
|
|
|
|
|
_test_repayment_top_up()
|
|
|
|
|
_test_purchase_top_up()
|
|
|
|
|
_test_upkeep_top_up()
|
|
|
|
|
_test_actual_service_funds()
|
|
|
|
|
_test_operation_top_ups_and_day_total()
|
|
|
|
|
_test_legacy_midnight_top_up()
|
|
|
|
|
_test_tax_arrears_next_day()
|
|
|
|
|
await _test_ledger_opened_from_main()
|
|
|
|
|
await process_frame
|
|
|
|
|
if _failures == 0:
|
|
|
|
@@ -402,6 +410,289 @@ func _test_upkeep_top_up() -> void:
|
|
|
|
|
_free_fixture(fixture)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _command(runtime: Variant, type: StringName, payload: Dictionary) -> bool:
|
|
|
|
|
var result: CommandResult = runtime.submit_command(type, payload)
|
|
|
|
|
if not result.is_success():
|
|
|
|
|
push_error("%s: %s %s" % [type, result.message, result.details.get(&"reason", &"")])
|
|
|
|
|
return result.is_success()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _facility_of(runtime: Variant, definition_id: StringName) -> StringName:
|
|
|
|
|
for facility: FacilityInstance in runtime.facility_service.get_instances():
|
|
|
|
|
if facility.definition_id == definition_id:
|
|
|
|
|
return facility.instance_id
|
|
|
|
|
return &""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _day_funds(runtime: Variant, day: int) -> Dictionary:
|
|
|
|
|
return runtime.operations_coordinator.get_day_summary(day).get(&"settlement", {}).get(&"funds", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _closed_ledger_text(runtime: Variant, clock: Variant) -> String:
|
|
|
|
|
var panel := OperatingLedgerPanel.new()
|
|
|
|
|
root.add_child(panel)
|
|
|
|
|
panel.setup(runtime, clock)
|
|
|
|
|
panel.select_latest_close()
|
|
|
|
|
var text: String = panel._closed_label.text
|
|
|
|
|
panel.queue_free()
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# F1/F5 and F2 (wages) on the production paths. A front facility (massage_room,
|
|
|
|
|
# staffed by an ordinary hire on a DAY assignment) and the registered hidden-service
|
|
|
|
|
# facility (adult_private_room, underground, its service_attendant on a NIGHT
|
|
|
|
|
# assignment) are built and open; the arriving guests choose and complete the
|
|
|
|
|
# services themselves and the 00:00 closes settle the day.
|
|
|
|
|
func _test_actual_service_funds() -> void:
|
|
|
|
|
var fixture := _new_fixture(5031)
|
|
|
|
|
var runtime: Variant = fixture[&"runtime"]
|
|
|
|
|
var clock: Variant = fixture[&"clock"]
|
|
|
|
|
var economy: Variant = runtime.economy_service
|
|
|
|
|
# Rank 2 opens the underground, by the original room-zone suite's own route.
|
|
|
|
|
_expect(_command(runtime, &"change_resources", {&"deltas": {&"gold": 5000, &"materials": 100}})
|
|
|
|
|
and _command(runtime, &"update_progression_metrics", {&"metrics": {&"reputation": 25}})
|
|
|
|
|
and _command(runtime, &"set_world_tree_rank", {&"rank": 2}), "rank 2 through the public commands")
|
|
|
|
|
for x: int in range(0, 4):
|
|
|
|
|
_expect(_command(runtime, &"start_excavation", {&"cell": Vector3i(x, -1, 0)}), "excavate (%d, -1)" % x)
|
|
|
|
|
clock.advance_minutes(60)
|
|
|
|
|
# Guests go down by a guest corridor and stairs, staff by a service corridor and stairs.
|
|
|
|
|
_expect(_command(runtime, &"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(0, -1, 0)}), "underground guest corridor")
|
|
|
|
|
for x: int in range(1, 4):
|
|
|
|
|
_expect(_command(runtime, &"build_facility", {&"definition_id": &"service_corridor", &"origin": Vector3i(x, -1, 1)}), "underground service corridor (%d, -1)" % x)
|
|
|
|
|
clock.advance_minutes(30)
|
|
|
|
|
_expect(_command(runtime, &"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(0, 0, 0)})
|
|
|
|
|
and _command(runtime, &"build_facility", {&"definition_id": &"service_stairs", &"origin": Vector3i(3, 0, 1)}), "stairs down for guests and staff")
|
|
|
|
|
clock.advance_minutes(30)
|
|
|
|
|
_expect(not runtime.path_service.find_path(Vector3i(-2, 0, 0), Vector3i(0, -1, 0)).is_empty(), "guests can walk down to the room")
|
|
|
|
|
_expect(_command(runtime, &"build_facility", {&"definition_id": &"adult_private_room", &"origin": Vector3i(1, -1, 0)})
|
|
|
|
|
and _command(runtime, &"build_facility", {&"definition_id": &"massage_room", &"origin": Vector3i(-6, 0, 0)}), "build the hidden-service and the front-service facility")
|
|
|
|
|
clock.advance_minutes(240)
|
|
|
|
|
var back_room := _facility_of(runtime, &"adult_private_room")
|
|
|
|
|
var front_room := _facility_of(runtime, &"massage_room")
|
|
|
|
|
_expect(not back_room.is_empty() and not front_room.is_empty(), "both facilities complete")
|
|
|
|
|
_expect(_command(runtime, &"hire_staff_candidate", {&"candidate_id": &"staff_petra", &"current_cell": Vector3i(-5, 0, 0)})
|
|
|
|
|
and _command(runtime, &"set_staff_work_assignment", {&"staff_id": &"staff_petra", &"facility_id": front_room, &"template_id": &"DAY"}), "an ordinary hire works the massage room by day")
|
|
|
|
|
# No hire candidate holds the room's service_attendant role (REPAIR_02.md), so it
|
|
|
|
|
# is registered through the Runtime's register_staff command.
|
|
|
|
|
_expect(_command(runtime, &"register_staff", {&"staff_id": &"p05_attendant", &"role_id": &"service_attendant", &"options": {
|
|
|
|
|
&"current_cell": Vector3i(3, 0, 1), &"state": StaffRuntime.State.OFF_DUTY,
|
|
|
|
|
&"permitted_task_tags": [&"service", &"work", &"adult_service"], &"equipment_tags": [&"uniform"],
|
|
|
|
|
&"role_tags": [&"service_attendant"], &"skills": {&"service": 75}}})
|
|
|
|
|
and _command(runtime, &"set_staff_work_assignment", {&"staff_id": &"p05_attendant", &"facility_id": back_room, &"template_id": &"NIGHT"}), "the room's attendant works by night")
|
|
|
|
|
clock.advance_minutes(721 - int(clock.absolute_minute))
|
|
|
|
|
_expect(runtime.facility_service.get_instance(front_room).state == FacilityInstance.State.OPEN, "the massage room opens with its day staff")
|
|
|
|
|
clock.advance_minutes(1141 - int(clock.absolute_minute))
|
|
|
|
|
_expect(runtime.facility_service.get_instance(back_room).state == FacilityInstance.State.OPEN, "the room opens with its night staff")
|
|
|
|
|
clock.advance_minutes(1440 - int(clock.absolute_minute))
|
|
|
|
|
# Day 1: every sale of the massage room went to front funds, of the room to hidden funds.
|
|
|
|
|
var front_sales := 0
|
|
|
|
|
var back_sales := 0
|
|
|
|
|
var misrouted := 0
|
|
|
|
|
var back_payers := {}
|
|
|
|
|
for entry: LedgerEntry in economy.get_entries():
|
|
|
|
|
if entry.category == &"SERVICE_SALE" and entry.source_entity_id == back_room and String(entry.credit_account).begins_with("GUEST_WALLET:guest-auto-"):
|
|
|
|
|
back_payers[entry.credit_account] = true
|
|
|
|
|
if entry.category != &"SERVICE_CASH_SWEEP" or entry.absolute_minute >= 1440:
|
|
|
|
|
continue
|
|
|
|
|
if entry.source_entity_id == front_room and entry.debit_account == FRONT:
|
|
|
|
|
front_sales += entry.amount
|
|
|
|
|
elif entry.source_entity_id == back_room and entry.debit_account == BACK:
|
|
|
|
|
back_sales += entry.amount
|
|
|
|
|
else:
|
|
|
|
|
misrouted += 1
|
|
|
|
|
_expect(front_sales > 0 and back_sales > 0 and misrouted == 0, "front service sales sweep to PLAYER_CASH, hidden ones to BACK_CASH (front %d, hidden %d)" % [front_sales, back_sales])
|
|
|
|
|
_expect(not back_payers.is_empty(), "arriving guests went down to the room and paid for its service themselves (%d guests)" % back_payers.size())
|
|
|
|
|
var day1: Dictionary = runtime.operations_coordinator.get_day_summary(1)
|
|
|
|
|
var funds1 := _day_funds(runtime, 1)
|
|
|
|
|
_expect(int(funds1.get(&"revenue_front", -1)) == front_sales and int(funds1.get(&"revenue_back", -1)) == back_sales, "the 00:00 close reports the day's sales by fund")
|
|
|
|
|
_expect(int(day1[&"category_totals"][&"revenue"]) == front_sales + back_sales, "sales are front + hidden")
|
|
|
|
|
_expect(int(funds1.get(&"rank_tax", -1)) == FinancePolicy.rank_tax(front_sales + back_sales) and int(funds1.get(&"rank_tax", -1)) == (front_sales + back_sales) / 10
|
|
|
|
|
and int(funds1.get(&"rank_tax_paid", -1)) == int(funds1[&"rank_tax"]), "A10: the close taxes 10% of both sales, paid")
|
|
|
|
|
_expect(int(funds1.get(&"back_top_up", -1)) == 0 and economy.get_balance(BACK) == back_sales and _fund_delta(runtime, BACK, 0, 1440) == back_sales, "hidden funds hold exactly the day's hidden sales")
|
|
|
|
|
# Day 2: the front fund is lowered to 5 by a ledger fixture before the close, so the
|
|
|
|
|
# close must pay the real wages, upkeep and tax with hidden funds.
|
|
|
|
|
clock.advance_minutes(1440 + 1430 - int(clock.absolute_minute))
|
|
|
|
|
_expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", economy.get_balance(FRONT) - 5, &"OPERATING_EXPENSE", "service.d2.drain"), "front funds lowered to 5")
|
|
|
|
|
var back_before: int = economy.get_balance(BACK)
|
|
|
|
|
clock.advance_minutes(2880 - int(clock.absolute_minute))
|
|
|
|
|
var day2: Dictionary = runtime.operations_coordinator.get_day_summary(2)
|
|
|
|
|
var totals2: Dictionary = day2.get(&"category_totals", {})
|
|
|
|
|
var funds2 := _day_funds(runtime, 2)
|
|
|
|
|
var due := int(totals2.get(&"wages", 0)) + int(totals2.get(&"maintenance", 0)) + int(totals2.get(&"taxes_and_interest", 0))
|
|
|
|
|
_expect(int(totals2.get(&"wages", 0)) > 0 and int(funds2.get(&"revenue_back", 0)) > 0, "day 2 has real wages and hidden sales")
|
|
|
|
|
_expect(int(funds2.get(&"back_top_up", -1)) == due - 5, "A09: hidden funds cover the wages, upkeep and tax the 5 front funds cannot pay")
|
|
|
|
|
_expect(int(day2[&"settlement"].get(&"unpaid_after_total", -1)) == 0 and int(day2[&"settlement"][&"arrears_total"]) == 0, "the wages are paid in full, nothing is in arrears")
|
|
|
|
|
_expect(economy.get_balance(FRONT) == 0 and economy.get_balance(BACK) == back_before - (due - 5), "front 0, hidden funds paid the rest")
|
|
|
|
|
var saved: Dictionary = runtime.create_snapshot()
|
|
|
|
|
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the two service days restore exactly")
|
|
|
|
|
_free_fixture(fixture)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _has_key(dictionary: Dictionary, key: String) -> bool:
|
|
|
|
|
return dictionary.has(StringName(key)) or dictionary.has(key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _set_key(dictionary: Dictionary, key: String, value: Variant) -> void:
|
|
|
|
|
if dictionary.has(key):
|
|
|
|
|
dictionary[key] = value
|
|
|
|
|
else:
|
|
|
|
|
dictionary[StringName(key)] = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _erase_key(dictionary: Dictionary, key: String) -> void:
|
|
|
|
|
dictionary.erase(StringName(key))
|
|
|
|
|
dictionary.erase(key)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# The saved fund report of a day inside a Runtime snapshot; changing it changes the snapshot.
|
|
|
|
|
func _saved_funds(snapshot: Dictionary, day: int) -> Dictionary:
|
|
|
|
|
for summary: Dictionary in snapshot[&"systems"][&"runtime"][&"operations"][&"coordinator"][&"day_summaries"]:
|
|
|
|
|
if int(summary.get(&"day", summary.get("day", -1))) == day:
|
|
|
|
|
var settlement: Dictionary = summary.get(&"settlement", summary.get("settlement", {}))
|
|
|
|
|
return settlement.get(&"funds", settlement.get("funds", {}))
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# F2 (automatic purchase), F4 (interest after a repayment) and the day's top-up
|
|
|
|
|
# total: the warehouse's automatic order and the repayment command run on their
|
|
|
|
|
# production paths with too little front funds, then the 00:00 close. A report
|
|
|
|
|
# saved before the definition mark is then loaded by its own definition.
|
|
|
|
|
func _test_operation_top_ups_and_day_total() -> void:
|
|
|
|
|
var fixture := _new_fixture(5032)
|
|
|
|
|
var runtime: Variant = fixture[&"runtime"]
|
|
|
|
|
var clock: Variant = fixture[&"clock"]
|
|
|
|
|
var economy: Variant = runtime.economy_service
|
|
|
|
|
_expect(_command(runtime, &"build_facility", {&"definition_id": &"warehouse", &"origin": Vector3i(-8, 0, 1)}), "build a warehouse")
|
|
|
|
|
clock.advance_minutes(180)
|
|
|
|
|
var warehouse := _facility_of(runtime, &"warehouse")
|
|
|
|
|
_expect(not warehouse.is_empty(), "the warehouse completes")
|
|
|
|
|
_expect(_move(fixture, LOAN, FRONT, 1000, &"LOAN_PRINCIPAL_BORROWED", "ops.borrow"), "fixture principal 1000")
|
|
|
|
|
_expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", economy.get_balance(FRONT) - 2, &"OPERATING_EXPENSE", "ops.drain")
|
|
|
|
|
and _sale(fixture, BACK_SALES, BACK, 1000, "ops.back"), "front 2, hidden 1000")
|
|
|
|
|
_expect(_command(runtime, &"set_supply_auto_purchase", {&"warehouse_id": warehouse, &"item_id": SupplyCatalog.RAW_FOOD, &"enabled": true, &"target_stock": 10}), "an automatic order for 10 raw food")
|
|
|
|
|
clock.advance_minutes(1)
|
|
|
|
|
var plan: Dictionary = runtime.auto_purchase_plans.get_plan(warehouse, SupplyCatalog.RAW_FOOD)
|
|
|
|
|
var receipt := SupplyAutoPurchasePlans.receipt_from_ledger(economy, warehouse, SupplyCatalog.RAW_FOOD, clock.absolute_minute, StringName(plan.get(&"last_transaction_id", &"")))
|
|
|
|
|
_expect(plan.get(&"last_result") == &"PURCHASED" and receipt == {&"item_id": String(SupplyCatalog.RAW_FOOD), &"quantity": 10, &"unit_price": 3, &"total_cost": 30}, "the automatic order buys 10 for 30 and its receipt verifies")
|
|
|
|
|
var order_key := SupplyAutoPurchasePlans.order_key(warehouse, SupplyCatalog.RAW_FOOD, clock.absolute_minute)
|
|
|
|
|
var order_top_up: Array = economy.get_transaction_entries(economy.get_transaction_id_for_idempotency_key(StringName("finance.back_top_up.purchase." + String(order_key))))
|
|
|
|
|
_expect(order_top_up.size() == 1 and order_top_up[0].category == FinancePolicy.TOP_UP_CATEGORY and order_top_up[0].amount == 28, "A09: hidden funds cover the 28 the automatic order's front funds cannot pay")
|
|
|
|
|
_expect(economy.get_balance(FRONT) == 0 and economy.get_balance(BACK) == 972, "front 0, hidden 972 after the automatic order")
|
|
|
|
|
var repaid: CommandResult = runtime.submit_command(&"repay_loan_principal", {&"loan_id": &"p05.fixture", &"amount": 200}, &"player", &"p05.fixture", 50, -1, &"p05.ops.repay")
|
|
|
|
|
_expect(repaid.is_success() and int(repaid.details.get(&"outstanding_principal", -1)) == 800, "the repayment command pays 200 of the 1000")
|
|
|
|
|
_expect(economy.get_balance(FRONT) == 0 and economy.get_balance(BACK) == 772, "hidden funds paid the repayment")
|
|
|
|
|
clock.advance_minutes(1440 - int(clock.absolute_minute))
|
|
|
|
|
var day1: Dictionary = runtime.operations_coordinator.get_day_summary(1)
|
|
|
|
|
var funds := _day_funds(runtime, 1)
|
|
|
|
|
_expect(int(funds.get(&"loan_interest", -1)) == 8 and int(funds.get(&"loan_interest_paid", -1)) == 8, "A11: the close charges 1% of the 800 left after the repayment, 8, paid")
|
|
|
|
|
_expect(int(funds.get(&"rank_tax", -1)) == 100 and int(funds.get(&"rank_tax_paid", -1)) == 100, "the hidden sale's tax is paid")
|
|
|
|
|
var close_top_up := 0
|
|
|
|
|
for entry: LedgerEntry in economy.get_transaction_entries(economy.get_transaction_id_for_idempotency_key(&"finance.back_top_up.day.00000001")):
|
|
|
|
|
close_top_up += entry.amount
|
|
|
|
|
var every_top_up := 0
|
|
|
|
|
for entry: LedgerEntry in economy.get_entries():
|
|
|
|
|
if entry.category == FinancePolicy.TOP_UP_CATEGORY:
|
|
|
|
|
every_top_up += entry.amount
|
|
|
|
|
_expect(close_top_up == 8 + 100 + int(day1[&"category_totals"][&"maintenance"]), "the close tops up the interest, tax and upkeep")
|
|
|
|
|
var total := 28 + 200 + close_top_up
|
|
|
|
|
_expect(int(funds.get(&"back_top_up", -1)) == total and every_top_up == total, "funds.back_top_up is the day's every top-up: the order, the repayment and the close")
|
|
|
|
|
_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(_closed_ledger_text(runtime, clock).contains("裏金から表金へ補填 %dG" % total), "the ledger's close shows the day's every top-up")
|
|
|
|
|
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")
|
|
|
|
|
# A report saved before the mark (back_top_up = the close's own top-up only) loads by that definition.
|
|
|
|
|
var old := saved.duplicate(true)
|
|
|
|
|
var old_funds := _saved_funds(old, 1)
|
|
|
|
|
_expect(_has_key(old_funds, "fund_report_version"), "the saved report holds the mark")
|
|
|
|
|
_erase_key(old_funds, "fund_report_version")
|
|
|
|
|
_set_key(old_funds, "back_top_up", close_top_up)
|
|
|
|
|
_expect(runtime.restore_snapshot(old), "an unmarked report with the close-only top-up loads")
|
|
|
|
|
var loaded := _day_funds(runtime, 1)
|
|
|
|
|
_expect(int(loaded.get(&"back_top_up", -1)) == close_top_up and not _has_key(loaded, "fund_report_version"), "the loaded old report stays as it was saved")
|
|
|
|
|
_expect(_closed_ledger_text(runtime, clock).contains("裏金から表金へ補填(締めの時の分のみ) %dG" % close_top_up), "the ledger labels the old report's close-only top-up")
|
|
|
|
|
# No looser check: each definition accepts only its own value.
|
|
|
|
|
for case: Array in [[null, total, "an unmarked report with the day total"], [2, close_top_up, "a marked report with the close-only top-up"], [3, total, "an unknown mark"]]:
|
|
|
|
|
var bad := saved.duplicate(true)
|
|
|
|
|
var bad_funds := _saved_funds(bad, 1)
|
|
|
|
|
if case[0] == null:
|
|
|
|
|
_erase_key(bad_funds, "fund_report_version")
|
|
|
|
|
else:
|
|
|
|
|
_set_key(bad_funds, "fund_report_version", case[0])
|
|
|
|
|
_set_key(bad_funds, "back_top_up", case[1])
|
|
|
|
|
var before: Dictionary = runtime.create_snapshot()
|
|
|
|
|
_expect(not runtime.restore_snapshot(bad) and var_to_bytes(before) == var_to_bytes(runtime.create_snapshot()), "%s is rejected and changes nothing" % case[2])
|
|
|
|
|
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the current save loads again")
|
|
|
|
|
_free_fixture(fixture)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# F2 (legacy calendar): in a save from before the 00:00 close the upkeep is paid at
|
|
|
|
|
# calendar midnight and the financial day closes at 06:00 (as in
|
|
|
|
|
# tests/mvp/test_upkeep_legacy_midnight.gd). With no front funds the midnight upkeep
|
|
|
|
|
# is topped up from hidden funds, and that 06:00 day's report counts it.
|
|
|
|
|
func _test_legacy_midnight_top_up() -> void:
|
|
|
|
|
var fixture := _new_fixture(5033)
|
|
|
|
|
var runtime: Variant = fixture[&"runtime"]
|
|
|
|
|
var clock: Variant = fixture[&"clock"]
|
|
|
|
|
var economy: Variant = runtime.economy_service
|
|
|
|
|
var definition: FacilityDefinition = runtime.definition_registry.get_facility(&"guest_corridor")
|
|
|
|
|
var old_rate := definition.maintenance_gold
|
|
|
|
|
definition.maintenance_gold = 1440
|
|
|
|
|
_expect(_command(runtime, &"build_facility", {&"definition_id": &"guest_corridor", &"origin": Vector3i(4, 1, 0)}), "build a corridor with upkeep")
|
|
|
|
|
runtime.operations_coordinator._legacy_calendar_last_day = 1
|
|
|
|
|
clock.advance_minutes(1079)
|
|
|
|
|
var legacy: Dictionary = runtime.create_snapshot()
|
|
|
|
|
var data: Dictionary = legacy[&"systems"][&"runtime"]
|
|
|
|
|
data[&"runtime_schema_version"] = 9
|
|
|
|
|
preload("res://tests/fixtures/pre_upkeep_runtime.gd").strip_empty_upkeep(data)
|
|
|
|
|
data[&"operations"][&"coordinator"][&"schema_version"] = 3
|
|
|
|
|
data[&"operations"][&"coordinator"].erase(&"legacy_calendar_last_day")
|
|
|
|
|
data[&"operations"][&"coordinator"].erase(&"legacy_history_last_day")
|
|
|
|
|
data[&"operations"][&"coordinator"].erase(&"legacy_principal_profit_last_day")
|
|
|
|
|
_expect(runtime.restore_snapshot(legacy), "the legacy-calendar save loads at 23:59")
|
|
|
|
|
# The load replaces the Runtime's ledger service.
|
|
|
|
|
economy = runtime.economy_service
|
|
|
|
|
_expect(_move(fixture, FRONT, &"OPERATING_EXPENSE", economy.get_balance(FRONT), &"OPERATING_EXPENSE", "legacy.drain")
|
|
|
|
|
and _sale(fixture, BACK_SALES, BACK, 500, "legacy.back"), "front 0, hidden 500")
|
|
|
|
|
clock.advance_minutes(1)
|
|
|
|
|
var midnight: Array = economy.get_transaction_entries(economy.get_transaction_id_for_idempotency_key(&"finance.back_top_up.midnight.1440"))
|
|
|
|
|
_expect(midnight.size() == 1 and midnight[0].category == FinancePolicy.TOP_UP_CATEGORY and midnight[0].amount == 1, "A09: hidden funds cover the 00:00 upkeep front funds cannot pay")
|
|
|
|
|
_expect(economy.get_balance(FacilityUpkeepSettlement.EXPENSE) == 1 and runtime.upkeep_settlement.get_arrears_total() == 0
|
|
|
|
|
and economy.get_balance(FRONT) == 0 and economy.get_balance(BACK) == 499, "the midnight upkeep is paid, nothing in arrears")
|
|
|
|
|
clock.advance_minutes(360)
|
|
|
|
|
var day1: Dictionary = runtime.operations_coordinator.get_day_summary(1)
|
|
|
|
|
var funds := _day_funds(runtime, 1)
|
|
|
|
|
_expect(int(day1.get(&"closed_at", -1)) == 1800 and int(funds.get(&"rank_tax", -1)) == 50 and int(funds.get(&"rank_tax_paid", -1)) == 50, "the 06:00 close taxes the hidden sale, paid")
|
|
|
|
|
_expect(int(funds.get(&"back_top_up", -1)) == 51 and economy.get_balance(BACK) == 449, "the 06:00 day's report counts the midnight top-up and the close's")
|
|
|
|
|
var saved: Dictionary = runtime.create_snapshot()
|
|
|
|
|
_expect(runtime.restore_snapshot(saved) and var_to_bytes(saved) == var_to_bytes(runtime.create_snapshot()), "the legacy day restores exactly")
|
|
|
|
|
definition.maintenance_gold = old_rate
|
|
|
|
|
_free_fixture(fixture)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# F3: the day's tax that front and hidden funds cannot pay stays payable, and the
|
|
|
|
|
# next close pays that older tax before the new one.
|
|
|
|
|
func _test_tax_arrears_next_day() -> void:
|
|
|
|
|
var fixture := _new_fixture(5034)
|
|
|
|
|
var runtime: Variant = fixture[&"runtime"]
|
|
|
|
|
var economy: Variant = runtime.economy_service
|
|
|
|
|
_advance_to(fixture, 420)
|
|
|
|
|
_expect(_sale(fixture, FRONT_SALES, FRONT, 200, "tax.d1") and _move(fixture, FRONT, &"OPERATING_EXPENSE", economy.get_balance(FRONT), &"OPERATING_EXPENSE", "tax.d1.drain"), "day 1 sale 200, then no funds")
|
|
|
|
|
_advance_to(fixture, 1440)
|
|
|
|
|
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(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")
|
|
|
|
|
_advance_to(fixture, 1440 + 600)
|
|
|
|
|
_expect(_sale(fixture, FRONT_SALES, FRONT, 100, "tax.d2"), "day 2 sale 100")
|
|
|
|
|
_advance_to(fixture, 2880)
|
|
|
|
|
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(economy.get_balance(FRONT) == 70 and economy.get_balance(FinancePolicy.RANK_TAX_PAYABLE) == 0, "front 70, no tax payable left")
|
|
|
|
|
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")
|
|
|
|
|
_free_fixture(fixture)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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.
|
|
|
|
|
func _check_ledger_panel(runtime: Variant, clock: Variant) -> void:
|
|
|
|
|