feat(qsl): set the QSL route per direction from the QSL Manager

The paper form had one "Via", which since the QSL_VIA split writes
QSL_SENT_VIA. QSL_RCVD_VIA had no way in at all outside the per-QSO editor —
a field with a column, an import and an export, and nothing to fill it.

Each direction now has its own route dropdown, sitting with the status and
date it belongs to: how the card was sent, beside Sent; how it came back,
beside Received. Both apply to the whole selection, which is the shape of the
job — a stack confirmed in one go usually arrived the same way, a bureau
delivery being exactly that, and doing it one QSO at a time in the editor was
the only option before.

Both store the ADIF enumeration (B/D/E), and neither touches QSL_VIA — that
holds the manager's callsign and belongs to the QSO, not to a stack of cards
being confirmed together.

The two columns were already available in the paper list: it renders through
RecentQSOsGrid, which gained them with the field itself.
This commit is contained in:
2026-08-14 16:32:12 +02:00
parent 2cd1274975
commit 7470ebf47e
5 changed files with 48 additions and 24 deletions
+22 -11
View File
@@ -486,6 +486,7 @@ var defaultBands = []string{
"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m",
"12m", "10m", "6m", "2m", "70cm", "23cm",
}
// defaultModes is what a fresh install starts with — the modes most operators
// actually use, in the order they are offered. AM and DIGITALVOICE are NOT here
// on purpose: they stay in the Available catalogue for whoever wants them, but
@@ -6217,9 +6218,13 @@ type QSLBulkUpdate struct {
RcvdStatus string `json:"rcvd_status"` // Y|N|R|I — "" = unchanged
SentDate string `json:"sent_date"` // YYYYMMDD — "" = unchanged
RcvdDate string `json:"rcvd_date"` // YYYYMMDD — "" = unchanged
Via string `json:"via"` // QSL_VIA — "" = unchanged
Notes string `json:"notes"` // NOTES — "" = unchanged
Comment string `json:"comment"` // COMMENT — "" = unchanged
// Via and RcvdVia are the ADIF QSL Via enumeration (B bureau / D direct /
// E electronic), NOT the manager — that is QSL_VIA and belongs on the QSO,
// not on a stack of cards being confirmed together. "" = unchanged.
Via string `json:"via"` // → QSL_SENT_VIA
RcvdVia string `json:"rcvd_via"` // → QSL_RCVD_VIA
Notes string `json:"notes"` // NOTES — "" = unchanged
Comment string `json:"comment"` // COMMENT — "" = unchanged
}
// BulkUpdateQSL applies paper-QSL sent/received status, dates and via to the
@@ -6256,6 +6261,12 @@ func (a *App) BulkUpdateQSL(ids []int64, u QSLBulkUpdate) (int, error) {
if v := adif.NormaliseQSLVia(u.Via); v != "" {
q.QSLSentVia, changed = v, true
}
// How the card CAME BACK. A stack confirmed together usually arrived the
// same way — a bureau delivery is exactly that — so it belongs here
// beside the received date rather than one QSO at a time in the editor.
if v := adif.NormaliseQSLVia(u.RcvdVia); v != "" {
q.QSLRcvdVia, changed = v, true
}
if v := strings.TrimSpace(u.Notes); v != "" {
q.Notes, changed = v, true
}
@@ -17399,14 +17410,14 @@ type clusterStatusCache struct {
// callCounties holds callsign → "STATE,County" for stations already logged
// with a county, so a spot shows the county the entry panel showed rather
// than the one derived from the licence ZIP. See qso.CallCounties.
callCounties map[string]string
workedPOTA map[string]struct{}
workedPfx map[string]struct{}
workedGrids map[string]struct{} // "GRID|MODE", mode normalised like the rest
normMode func(string) string // nil unless digital-mode grouping is on
groupDigital bool // settings the maps were built under —
sameSlot bool // a change rebuilds the snapshot
slotHighlight bool // (same: the slot index is built for either)
callCounties map[string]string
workedPOTA map[string]struct{}
workedPfx map[string]struct{}
workedGrids map[string]struct{} // "GRID|MODE", mode normalised like the rest
normMode func(string) string // nil unless digital-mode grouping is on
groupDigital bool // settings the maps were built under —
sameSlot bool // a change rebuilds the snapshot
slotHighlight bool // (same: the slot index is built for either)
}
// clusterStatusMaps returns the cached worked-index snapshot, building it once