P05-02: 全費目と資金区分:表金・裏金、維持費未払い、仕入れ、人件費、元本、利息、税の帳簿への接続(原 P05-02 の完了の条件)
This commit is contained in:
@@ -65,6 +65,33 @@ func settle(period: Dictionary, economy: EconomyService) -> Dictionary:
|
||||
return {&"ok": true, &"replayed": false, &"receipt": receipt.duplicate(true), &"transaction_id": transaction_id}
|
||||
|
||||
|
||||
## Whole Gold that settling these closed periods would owe (arrears included),
|
||||
## planned with no cash; -1 if a period is invalid or already settled. Nothing changes.
|
||||
func preview_debt(periods: Array) -> int:
|
||||
var arrears := _arrears.duplicate()
|
||||
var fractions := _fractions.duplicate()
|
||||
var last_end := int(_receipts[-1].period.period_end) if not _receipts.is_empty() else -1
|
||||
var candidate := FacilityUpkeepSettlement.new()
|
||||
for raw: Variant in periods:
|
||||
if not raw is Dictionary:
|
||||
return -1
|
||||
var canonical := _parse_period(raw)
|
||||
if canonical.is_empty() or (last_end >= 0 and canonical.period_start != last_end):
|
||||
return -1
|
||||
candidate._arrears = arrears
|
||||
candidate._fractions = fractions
|
||||
var plan := candidate._plan(canonical, 0)
|
||||
if not plan[&"ok"]:
|
||||
return -1
|
||||
arrears = plan.arrears
|
||||
fractions = plan.fractions
|
||||
last_end = canonical.period_end
|
||||
var debt := 0
|
||||
for amount: int in arrears.values():
|
||||
debt += amount
|
||||
return debt
|
||||
|
||||
|
||||
func get_arrears_through(minute: int) -> int:
|
||||
var low := 0
|
||||
var high := _receipts.size()
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
class_name FinancePolicy
|
||||
extends RefCounted
|
||||
|
||||
## Front (表金) and hidden (裏金) funds, the World Tree rank tax and dark bank interest.
|
||||
## Values are the user-approved initial test values (decisions A09-A12, 2026-10-23).
|
||||
## The front fund keeps the existing PLAYER_CASH account; saves made before the split
|
||||
## carry no hidden fund account and are read with every Gold as front funds (A12).
|
||||
|
||||
const FRONT_CASH: StringName = &"PLAYER_CASH"
|
||||
const BACK_CASH: StringName = &"BACK_CASH"
|
||||
const RANK_TAX_EXPENSE: StringName = &"RANK_TAX_EXPENSE"
|
||||
const RANK_TAX_PAYABLE: StringName = &"RANK_TAX_PAYABLE"
|
||||
const LOAN_INTEREST_EXPENSE: StringName = &"LOAN_INTEREST_EXPENSE"
|
||||
const LOAN_INTEREST_PAYABLE: StringName = &"LOAN_INTEREST_PAYABLE"
|
||||
const LOAN_LIABILITY_PREFIX := "LOAN_LIABILITY:"
|
||||
|
||||
## A10: 10% of the closed day's sales (front + hidden), whole Gold rounded down.
|
||||
const RANK_TAX_PERCENT := 10
|
||||
## A11: 1% of the outstanding principal per day, whole Gold rounded down.
|
||||
const LOAN_INTEREST_PERCENT_PER_DAY := 1
|
||||
|
||||
## Services of facilities with one of these tags earn hidden funds (原本: 成人施設、
|
||||
## カジノ、脅迫金、ユニット売却は裏の収益). Every other service earns front funds.
|
||||
const BACK_FUND_TAGS: Array[StringName] = [&"restricted_service", &"adult_only"]
|
||||
|
||||
## Ledger categories. Accruals are expenses of the closed day; payments, top-ups and
|
||||
## the payable balances are not income or expense.
|
||||
const TOP_UP_CATEGORY: StringName = &"BACK_FUND_TOP_UP"
|
||||
const RANK_TAX_CATEGORY: StringName = &"RANK_TAX"
|
||||
const RANK_TAX_PAYMENT_CATEGORY: StringName = &"RANK_TAX_PAYMENT"
|
||||
const LOAN_INTEREST_CATEGORY: StringName = &"LOAN_INTEREST"
|
||||
const LOAN_INTEREST_PAYMENT_CATEGORY: StringName = &"LOAN_INTEREST_PAYMENT"
|
||||
const NON_PROFIT_CATEGORIES: Array[StringName] = [TOP_UP_CATEGORY, RANK_TAX_PAYMENT_CATEGORY, LOAN_INTEREST_PAYMENT_CATEGORY]
|
||||
|
||||
|
||||
static func rank_tax(sales: int) -> int:
|
||||
return maxi(sales, 0) / 100 * RANK_TAX_PERCENT + maxi(sales, 0) % 100 * RANK_TAX_PERCENT / 100
|
||||
|
||||
|
||||
static func loan_interest(outstanding_principal: int) -> int:
|
||||
return maxi(outstanding_principal, 0) / 100 * LOAN_INTEREST_PERCENT_PER_DAY \
|
||||
+ maxi(outstanding_principal, 0) % 100 * LOAN_INTEREST_PERCENT_PER_DAY / 100
|
||||
|
||||
|
||||
static func is_back_fund_service(tags: Array) -> bool:
|
||||
for tag: StringName in BACK_FUND_TAGS:
|
||||
if tag in tags:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
## Accounts of the split funds. A save without BACK_CASH predates the split and must
|
||||
## not hold any of the other accounts either; they are then created empty (A12).
|
||||
## Any other partial or incompatible set is rejected.
|
||||
static func prepare_accounts(economy: EconomyService) -> bool:
|
||||
if economy == null or not economy.has_account(FRONT_CASH) or economy.account_allows_negative(FRONT_CASH):
|
||||
return false
|
||||
var expected := {
|
||||
BACK_CASH: false, RANK_TAX_EXPENSE: false, LOAN_INTEREST_EXPENSE: false,
|
||||
RANK_TAX_PAYABLE: true, LOAN_INTEREST_PAYABLE: true,
|
||||
}
|
||||
if not economy.has_account(BACK_CASH):
|
||||
for account_id: StringName in expected:
|
||||
if economy.has_account(account_id):
|
||||
return false
|
||||
for account_id: StringName in expected:
|
||||
if not economy.register_account(account_id, bool(expected[account_id])):
|
||||
return false
|
||||
return true
|
||||
for account_id: StringName in expected:
|
||||
if not economy.has_account(account_id) \
|
||||
or economy.account_allows_negative(account_id) != bool(expected[account_id]):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
static func outstanding_principal(economy: EconomyService) -> int:
|
||||
var total := 0
|
||||
for account: Dictionary in economy.get_accounts():
|
||||
if String(account[&"account_id"]).begins_with(LOAN_LIABILITY_PREFIX):
|
||||
total += -int(account[&"balance"])
|
||||
return total
|
||||
|
||||
|
||||
static func available_funds(economy: EconomyService) -> int:
|
||||
return economy.get_balance(FRONT_CASH) + economy.get_balance(BACK_CASH)
|
||||
@@ -0,0 +1 @@
|
||||
uid://l14tq0avw0ux
|
||||
Reference in New Issue
Block a user