208 lines
7.5 KiB
GDScript
208 lines
7.5 KiB
GDScript
class_name ABGReadinessPageController
|
|
extends RefCounted
|
|
|
|
## UI-independent Readiness Review view-data assembly.
|
|
##
|
|
## FormationRiskService remains responsible for loading, saving and accepting
|
|
## the readiness snapshot. This controller only validates the supplied saved
|
|
## formation boundary and copies the facts the existing PTY-002 View renders.
|
|
|
|
const VALID_STATUSES: Array[String] = ["ordered", "ready"]
|
|
|
|
|
|
func snapshot(
|
|
company: Dictionary,
|
|
formation: Dictionary,
|
|
warnings: Array = [],
|
|
reviewed_recruit_id := "",
|
|
locale := "zh_CN"
|
|
) -> Dictionary:
|
|
if company.is_empty():
|
|
return {"ok": false, "code": "COMPANY_REQUIRED"}
|
|
var raw_recruits = company.get("owned_recruits", [])
|
|
if not raw_recruits is Array:
|
|
return {"ok": false, "code": "COMPANY_INVALID"}
|
|
var raw_contract = company.get("active_contract", {})
|
|
if not raw_contract is Dictionary or Dictionary(raw_contract).is_empty():
|
|
return {"ok": false, "code": "FORMATION_CONTRACT_REQUIRED"}
|
|
if formation.is_empty():
|
|
return {"ok": false, "code": "FORMATION_REQUIRED"}
|
|
var contract: Dictionary = Dictionary(raw_contract).duplicate(true)
|
|
var members := _members_by_id(raw_recruits)
|
|
var capacity := int(formation.get("line_capacity", contract.get("line_capacity", 0)))
|
|
if capacity <= 0:
|
|
return {"ok": false, "code": "READINESS_CAPACITY_INVALID"}
|
|
var saved_order = formation.get("saved_front_to_rear_ids", [])
|
|
if not saved_order is Array or saved_order.is_empty():
|
|
return {"ok": false, "code": "READINESS_ORDER_REQUIRED"}
|
|
var order_validation := _validate_saved_order(saved_order, members, capacity)
|
|
if not order_validation.ok:
|
|
return order_validation
|
|
var status := String(formation.get("status", ""))
|
|
if status not in VALID_STATUSES:
|
|
return {"ok": false, "code": "READINESS_STATUS_INVALID"}
|
|
var raw_receipts = formation.get("readiness_receipts", [])
|
|
if not raw_receipts is Array:
|
|
return {"ok": false, "code": "READINESS_RECEIPTS_INVALID"}
|
|
if status == "ready" and raw_receipts.is_empty():
|
|
return {"ok": false, "code": "READINESS_RECEIPT_REQUIRED"}
|
|
if not warnings is Array:
|
|
return {"ok": false, "code": "READINESS_WARNINGS_INVALID"}
|
|
|
|
var normalized_order: Array = order_validation.ids
|
|
var copied_relations: Array = _copy_array(formation.get("relations", []))
|
|
var relation_by_id := _relations_by_id(copied_relations)
|
|
var ordered_recruits: Array[Dictionary] = []
|
|
for position in range(normalized_order.size()):
|
|
var recruit_id := String(normalized_order[position])
|
|
var recruit: Dictionary = Dictionary(members[recruit_id]).duplicate(true)
|
|
ordered_recruits.append({
|
|
"position": position,
|
|
"recruit_id": recruit_id,
|
|
"recruit": recruit,
|
|
"relation": Dictionary(relation_by_id.get(recruit_id, {})).duplicate(true),
|
|
})
|
|
var selected := String(reviewed_recruit_id) if String(reviewed_recruit_id) in normalized_order else ""
|
|
var copied_coverage: Dictionary = Dictionary(formation.get("coverage", {})).duplicate(true)
|
|
var copied_first_threat: Dictionary = Dictionary(formation.get("first_threat", {})).duplicate(true)
|
|
var copied_receipts: Array = _copy_array(raw_receipts)
|
|
var copied_snapshot: Dictionary = Dictionary(formation.get("accepted_party_snapshot", {})).duplicate(true)
|
|
var front_recruit: Dictionary = Dictionary(members[String(normalized_order[0])]).duplicate(true)
|
|
var state := "ready" if status == "ready" else "ordered"
|
|
var order_hash := _hash(normalized_order)
|
|
var contract_data := {
|
|
"contract_id": String(contract.get("contract_id", "")),
|
|
"run_id": String(contract.get("run_id", "")),
|
|
"difficulty": String(contract.get("difficulty", "standard")),
|
|
"coin": int(contract.get("coin", 0)),
|
|
"line_capacity": int(contract.get("line_capacity", capacity)),
|
|
}
|
|
var company_data := {
|
|
"company_id": String(company.get("company_id", "")),
|
|
"owned_count": members.size(),
|
|
"line_capacity": capacity,
|
|
"recruits": _copy_array(raw_recruits),
|
|
}
|
|
return {
|
|
"ok": true,
|
|
"page_id": "readiness_review",
|
|
"locale": String(locale),
|
|
"company": company_data,
|
|
"contract": contract_data,
|
|
"formation": {
|
|
"status": status,
|
|
"state": state,
|
|
"orientation": String(formation.get("orientation", "rear_left_front_right")),
|
|
"front_position": int(formation.get("front_position", 0)),
|
|
"line_capacity": capacity,
|
|
"order_revision": int(formation.get("order_revision", 0)),
|
|
"first_threat": copied_first_threat,
|
|
"relations": copied_relations,
|
|
"coverage": copied_coverage,
|
|
"risk_option_ids": _risk_option_ids(formation.get("risk_options", []), copied_snapshot),
|
|
},
|
|
"order": {
|
|
"front_to_rear_ids": normalized_order.duplicate(true),
|
|
"order_hash": order_hash,
|
|
"ordered_recruits": ordered_recruits,
|
|
"front_recruit": front_recruit,
|
|
"reviewed_recruit_id": selected,
|
|
"review_reason": "selected" if not selected.is_empty() else "no_selection",
|
|
},
|
|
"warnings": {
|
|
"rows": _copy_array(warnings),
|
|
"count": warnings.size(),
|
|
},
|
|
"receipt": {
|
|
"present": not copied_receipts.is_empty(),
|
|
"latest": Dictionary(copied_receipts.back()).duplicate(true) if not copied_receipts.is_empty() and copied_receipts.back() is Dictionary else {},
|
|
"all": copied_receipts,
|
|
},
|
|
"accepted_party_snapshot": copied_snapshot,
|
|
"commands": {
|
|
"settings_enabled": true,
|
|
"member_review_enabled": true,
|
|
"warning_review_enabled": true,
|
|
"revise_enabled": true,
|
|
"back_enabled": true,
|
|
"accept_enabled": state == "ordered",
|
|
"risk_enabled": state == "ready",
|
|
},
|
|
}
|
|
|
|
|
|
func _validate_saved_order(values: Array, members: Dictionary, capacity: int) -> Dictionary:
|
|
if values.size() != capacity:
|
|
return {"ok": false, "code": "READINESS_COUNT_INVALID"}
|
|
var ids: Array = []
|
|
for value in values:
|
|
var recruit_id := String(value)
|
|
if not members.has(recruit_id):
|
|
return {"ok": false, "code": "READINESS_RECRUIT_UNKNOWN"}
|
|
if recruit_id in ids:
|
|
return {"ok": false, "code": "READINESS_RECRUIT_DUPLICATE"}
|
|
ids.append(recruit_id)
|
|
return {"ok": true, "code": "READINESS_ORDER_VALID", "ids": ids}
|
|
|
|
|
|
func _members_by_id(values) -> Dictionary:
|
|
var result := {}
|
|
for value in values:
|
|
if value is Dictionary:
|
|
var copied: Dictionary = Dictionary(value).duplicate(true)
|
|
var recruit_id := String(copied.get("recruit_id", ""))
|
|
if recruit_id.is_empty():
|
|
continue
|
|
result[recruit_id] = copied
|
|
return result
|
|
|
|
|
|
func _relations_by_id(values: Array) -> Dictionary:
|
|
var result := {}
|
|
for value in values:
|
|
if value is Dictionary:
|
|
result[String(value.get("recruit_id", ""))] = Dictionary(value).duplicate(true)
|
|
return result
|
|
|
|
|
|
func _risk_option_ids(options, accepted_snapshot: Dictionary) -> Array:
|
|
var result: Array = []
|
|
if options is Array:
|
|
for option in options:
|
|
if option is Dictionary:
|
|
result.append(String(option.get("risk_option_id", "")))
|
|
if result.is_empty() and accepted_snapshot.has("risk_option_ids"):
|
|
result = _copy_array(accepted_snapshot.get("risk_option_ids", []))
|
|
return result
|
|
|
|
|
|
func _copy_array(values) -> Array:
|
|
var result: Array = []
|
|
if not values is Array:
|
|
return result
|
|
for value in values:
|
|
result.append(value.duplicate(true) if value is Dictionary or value is Array else value)
|
|
return result
|
|
|
|
|
|
func _hash(value) -> String:
|
|
return JSON.stringify(_canonical(value)).sha256_text()
|
|
|
|
|
|
func _canonical(value):
|
|
if value is float and is_equal_approx(value, round(value)):
|
|
return int(round(value))
|
|
if value is Dictionary:
|
|
var result := {}
|
|
var keys: Array = value.keys()
|
|
keys.sort_custom(func(left, right): return String(left) < String(right))
|
|
for key in keys:
|
|
result[key] = _canonical(value[key])
|
|
return result
|
|
if value is Array:
|
|
var result: Array = []
|
|
for item in value:
|
|
result.append(_canonical(item))
|
|
return result
|
|
return value
|