feat: added FFMA award support

This commit is contained in:
2026-07-13 22:18:25 +02:00
parent f5ffe81c72
commit 08f4b61523
7 changed files with 4778 additions and 19 deletions
+44 -11
View File
@@ -2157,6 +2157,7 @@ func (a *App) awardDefs() []award.Def {
if json.Unmarshal([]byte(s), &defs) == nil && len(defs) > 0 {
// Upgrade legacy defs (pre-rich-model) in memory on every load.
migrated, _ := award.Migrate(defs)
migrated, _ = mergeCatalog(migrated)
return migrated
}
}
@@ -2164,6 +2165,27 @@ func (a *App) awardDefs() []award.Def {
return award.Defaults()
}
// mergeCatalog adds the catalog awards that are missing from the stored
// definitions. This is how an award SHIPPED in a new release (FFMA, say) reaches
// an operator who already has awards saved: without it awardDefs() would keep
// returning the stored copy and the new award would simply never appear. Add-only
// — an award already there keeps the operator's edits, Valid=false included.
func mergeCatalog(defs []award.Def) ([]award.Def, bool) {
have := make(map[string]struct{}, len(defs))
for _, d := range defs {
have[strings.ToUpper(strings.TrimSpace(d.Code))] = struct{}{}
}
added := false
for _, d := range award.Defaults() {
if _, ok := have[strings.ToUpper(strings.TrimSpace(d.Code))]; ok {
continue
}
defs = append(defs, d)
added = true
}
return defs, added
}
// GetAwardDefs returns the (editable) award definitions.
func (a *App) GetAwardDefs() []award.Def { return a.awardDefs() }
@@ -2189,6 +2211,11 @@ func (a *App) migrateAwardDefs() {
return
}
migrated, changed := award.Migrate(defs)
// Awards added to the catalog since this operator last saved (a new release
// ships one) must be written back, or the editor would keep showing the old set.
if merged, added := mergeCatalog(migrated); added {
migrated, changed = merged, true
}
// Version-gated correction of the built-in awards' Validate sources, which
// an earlier version wrongly set equal to Confirm (so VALIDATED == CONFIRMED
// even for paper-QSL-only entities). Re-apply the canonical Confirm/Validate
@@ -3825,8 +3852,10 @@ func freeAwardCode(code string, taken map[string]int) string {
const builtinRefsVersion = "2"
// seedBuiltinReferences populates the reference lists of built-in awards.
// - First run (no version stored): seed only awards that have NO references
// yet, so an online-loaded list (POTA…) or a user list isn't clobbered.
// - First run (no version stored), or already up to date: seed only awards that
// have NO references yet, so an online-loaded list (POTA…), a user list, or an
// edited one isn't clobbered. This is also what gives an award newly added to
// the catalog (FFMA and its 488 grids) its list on an existing install.
// - Version bump (stored != current): RE-SEED the derived built-in lists
// (DXCC, WAZ, WAC, WAS, DDFM) to push data corrections to existing installs.
// These lists are canonical, not user-maintained, so overwriting is safe.
@@ -3834,22 +3863,26 @@ func (a *App) seedBuiltinReferences() {
if a.awardRefs == nil || a.settings == nil {
return
}
ver, _ := a.settings.Get(a.ctx, keyAwardRefsSeeded)
if ver == builtinRefsVersion {
return
}
firstRun := ver == "" || ver == "1" // "1" was the old boolean flag
if ver == "1" {
firstRun = false // already seeded once → treat as a version upgrade
}
counts, err := a.awardRefs.Counts(a.ctx)
if err != nil {
return
}
ver, _ := a.settings.Get(a.ctx, keyAwardRefsSeeded)
firstRun := ver == "" || ver == "1" // "1" was the old boolean flag
if ver == "1" {
firstRun = false // already seeded once → treat as a version upgrade
}
if ver == builtinRefsVersion {
// Already on the current data. But an award ADDED to the catalog since the
// last run (FFMA, say) still has an EMPTY list, and bumping the version to
// seed it would re-seed every award and clobber lists the operator has
// edited. So fill only the empty ones.
firstRun = true
}
for _, d := range a.awardDefs() {
code := strings.ToUpper(d.Code)
if firstRun && counts[code] > 0 {
continue // don't overwrite an existing list on a fresh install
continue // don't overwrite an existing list
}
// A catalog award that SHIPS its own reference list wins: that is how an
// award added as a JSON file (a shared WAPC, with its provinces and their