From 96e80ab2934b3cd167dbc3a4548c62a2213d279f Mon Sep 17 00:00:00 2001 From: OpenFUT Agent Date: Thu, 13 Aug 2026 19:38:04 +0000 Subject: [PATCH] feat(import-fifa17): seed unopenedPackIds as Core entitlements Carry unopenedPackIds through the importer: Profile model -> Report -> ApplyPlan. GenericImportRequest now emits entitlements[] (one definition_id per unopened pack instance, order+duplicates preserved), which Core's import seeds as unconsumed packs rows in the same transaction. Closes the economy import gap so a migrated profile's unopened packs become Core entitlements (feeding purchasegroup/credits/userMassInfo). Idempotency unchanged (import fingerprint). +1 test; 26 pass, clippy -D warnings clean. --- openfut-import-fifa17/src/apply.rs | 16 ++++++++++++++++ openfut-import-fifa17/src/lib.rs | 3 +++ openfut-import-fifa17/src/model.rs | 4 ++++ openfut-import-fifa17/src/tests.rs | 23 +++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/openfut-import-fifa17/src/apply.rs b/openfut-import-fifa17/src/apply.rs index 4779f75..f517441 100644 --- a/openfut-import-fifa17/src/apply.rs +++ b/openfut-import-fifa17/src/apply.rs @@ -105,6 +105,15 @@ pub struct GenericImportRequest { pub owned: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub squad: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub entitlements: Vec, +} + +/// One unconsumed entitlement to seed into Core (a source `unopenedPackIds` +/// entry). `definition_id` is the pack id as text; Core stores it verbatim. +#[derive(Debug, Serialize)] +pub struct GenericEntitlement { + pub definition_id: String, } /// One preserved (opaque OwnedItemId ↔ source wire id) mapping to install into @@ -231,6 +240,13 @@ pub fn plan_apply( }, owned, squad, + entitlements: report + .unopened_pack_ids + .iter() + .map(|id| GenericEntitlement { + definition_id: id.to_string(), + }) + .collect(), }; Ok(ApplyPlan { diff --git a/openfut-import-fifa17/src/lib.rs b/openfut-import-fifa17/src/lib.rs index 97ac2d2..cab834a 100644 --- a/openfut-import-fifa17/src/lib.rs +++ b/openfut-import-fifa17/src/lib.rs @@ -632,6 +632,8 @@ pub struct Report { pub definitions: DefinitionPlan, pub identity: IdentityPlan, pub squad: SquadCoverage, + /// Unconsumed pack entitlements to seed (from `unopenedPackIds`). + pub unopened_pack_ids: Vec, } impl Report { @@ -729,6 +731,7 @@ pub fn analyze( definitions, identity, squad, + unopened_pack_ids: profile.unopened_pack_ids.clone(), } } diff --git a/openfut-import-fifa17/src/model.rs b/openfut-import-fifa17/src/model.rs index db8f1f2..2004d9e 100644 --- a/openfut-import-fifa17/src/model.rs +++ b/openfut-import-fifa17/src/model.rs @@ -26,6 +26,10 @@ pub struct Profile { pub items: Vec, #[serde(default)] pub squads: Vec, + /// Unconsumed pack entitlements (`unopenedPackIds`), seeded into Core as + /// generic entitlements on import. + #[serde(rename = "unopenedPackIds", default)] + pub unopened_pack_ids: Vec, } impl Profile { diff --git a/openfut-import-fifa17/src/tests.rs b/openfut-import-fifa17/src/tests.rs index fc016cb..6a04ef0 100644 --- a/openfut-import-fifa17/src/tests.rs +++ b/openfut-import-fifa17/src/tests.rs @@ -462,6 +462,29 @@ fn plan_apply_builds_generic_request_mappings_and_squad() { .any(|s| s.owned_item_id == cap_owned && s.is_captain)); } +#[test] +fn plan_apply_seeds_entitlements_from_unopened_packs() { + let items = [player(100000001, 20801, 20801, 94)]; + let json = format!( + r#"{{"personaId":33068179,"personaName":"CAGE","clubName":"OpenFUT","clubAbbr":"OFC", + "coins":1000,"nextItemId":100000500,"items":[{}],"squads":[],"unopenedPackIds":[70,70,5]}}"#, + items.join(",") + ); + let prof = Profile::from_json_str(&json).unwrap(); + let report = analyze(&prof, &roster(), &entities(), &none()); + assert_eq!(report.unopened_pack_ids, [70, 70, 5]); + let raw: serde_json::Value = serde_json::from_str(&json).unwrap(); + let plan = plan_apply(&report, &raw, "fp-ent").unwrap(); + let defs: Vec<&str> = plan + .request + .entitlements + .iter() + .map(|e| e.definition_id.as_str()) + .collect(); + // One entitlement per unopened pack instance, order preserved (dup 70 kept). + assert_eq!(defs, ["70", "70", "5"]); +} + #[test] fn plan_apply_refuses_when_blockers_present() { // unapproved same-resourceId conflict -> blocker.