fix(qsl): separate the QSL manager from the routing method (#16)
QSL_VIA is the manager. QSL_SENT_VIA and QSL_RCVD_VIA are the ADIF "QSL Via"
enumeration — B bureau, D direct, E electronic, M manager (import-only) —
and say how a card travelled. OpsLog had one column for all three:
- the import folded QSL_SENT_VIA into QSL_VIA whenever QSL_VIA was empty,
which is exactly a Log4OM export (it defaults QSL_SENT_VIA to E), so
OE6CLD saw "E" everywhere OpsLog shows the manager;
- QSL_RCVD_VIA was listed in adifPromoted with no column behind it, so it
was not stored, not kept among the extras, and not exported — dropped
outright on import;
- neither was ever written on export, so an import followed by an export
destroyed both;
- and OpsLog polluted the field itself: the QSL Manager panel wrote
"Bureau" / "Direct" / "Electronic", in full words, into QSL_VIA.
Two columns added (migration 0027), carried through the five places a
promoted ADIF field has to touch, with round-trip tests pinning the reported
case. The QSL panel now offers Bureau / Direct / Electronic for each
direction and stores the enumeration; the manager field is labelled as the
manager and holds only that. M is kept when a file gives it and never
written back out.
Existing logs hold a mixture of the two in one column. The repair is offered,
not performed: the count is shown once per log with a plain question, and a
"no" is remembered. It moves only where QSL_SENT_VIA is still empty, and only
values that normalise to the enumeration — a manager is a callsign and can
never be one of those six words, which a test pins against real manager calls.
This commit is contained in:
@@ -241,6 +241,14 @@ func writeRecord(bw *bufio.Writer, q qso.QSO, includeApp bool, allow map[string]
|
||||
w("QSLSDATE", q.QSLSentDate)
|
||||
w("QSLRDATE", q.QSLRcvdDate)
|
||||
w("QSL_VIA", q.QSLVia)
|
||||
// M (manager) is import-only in the QSL Via enumeration: we keep it when a
|
||||
// file gives it to us, but a file we write must not carry it.
|
||||
if q.QSLSentVia != QSLViaManager {
|
||||
w("QSL_SENT_VIA", q.QSLSentVia)
|
||||
}
|
||||
if q.QSLRcvdVia != QSLViaManager {
|
||||
w("QSL_RCVD_VIA", q.QSLRcvdVia)
|
||||
}
|
||||
w("QSLMSG", q.QSLMsg)
|
||||
w("QSLMSG_RCVD", q.QSLMsgRcvd)
|
||||
w("LOTW_QSL_SENT", q.LOTWSent)
|
||||
|
||||
@@ -135,8 +135,8 @@ var Fields = []FieldDef{
|
||||
{Name: "QSLSDATE", Kind: KindDate, Category: "QSL", Promoted: true},
|
||||
{Name: "QSLRDATE", Kind: KindDate, Category: "QSL", Promoted: true},
|
||||
{Name: "QSL_VIA", Kind: KindText, Category: "QSL", Promoted: true},
|
||||
{Name: "QSL_SENT_VIA", Kind: KindEnum, Category: "QSL"},
|
||||
{Name: "QSL_RCVD_VIA", Kind: KindEnum, Category: "QSL"},
|
||||
{Name: "QSL_SENT_VIA", Kind: KindEnum, Category: "QSL", Promoted: true},
|
||||
{Name: "QSL_RCVD_VIA", Kind: KindEnum, Category: "QSL", Promoted: true},
|
||||
{Name: "QSLMSG", Kind: KindText, Category: "QSL", Promoted: true},
|
||||
{Name: "QSLMSG_INTL", Kind: KindText, Category: "QSL", Intl: true},
|
||||
{Name: "QSLMSG_RCVD", Kind: KindText, Category: "QSL", Promoted: true},
|
||||
|
||||
+10
-3
@@ -464,10 +464,17 @@ func recordToQSO(rec Record) (qso.QSO, bool) {
|
||||
q.QSLRcvd = rec["qsl_rcvd"]
|
||||
q.QSLSentDate = rec["qslsdate"]
|
||||
q.QSLRcvdDate = rec["qslrdate"]
|
||||
// QSL_VIA is the manager. QSL_SENT_VIA / QSL_RCVD_VIA are the routing
|
||||
// method, an enumeration of their own.
|
||||
//
|
||||
// These used to be one field here: an empty QSL_VIA was filled from
|
||||
// QSL_SENT_VIA, on the theory that loggers writing one meant the other.
|
||||
// They do not — Log4OM defaults QSL_SENT_VIA to E, and the import put "E"
|
||||
// where every panel in OpsLog shows the manager's callsign. Keeping them
|
||||
// apart is also what lets an export give them back.
|
||||
q.QSLVia = rec["qsl_via"]
|
||||
if q.QSLVia == "" { // many loggers (Log4OM) write QSL_SENT_VIA instead
|
||||
q.QSLVia = rec["qsl_sent_via"]
|
||||
}
|
||||
q.QSLSentVia = NormaliseQSLVia(rec["qsl_sent_via"])
|
||||
q.QSLRcvdVia = NormaliseQSLVia(rec["qsl_rcvd_via"])
|
||||
q.QSLMsg = rec["qslmsg"]
|
||||
q.QSLMsgRcvd = rec["qslmsg_rcvd"]
|
||||
q.LOTWSent = rec["lotw_qsl_sent"]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package adif
|
||||
|
||||
import "strings"
|
||||
|
||||
// The ADIF "QSL Via" enumeration, used by QSL_SENT_VIA and QSL_RCVD_VIA. It
|
||||
// says how a card travelled, and is a different thing entirely from QSL_VIA,
|
||||
// which is the manager's callsign.
|
||||
const (
|
||||
QSLViaBureau = "B"
|
||||
QSLViaDirect = "D"
|
||||
QSLViaElectronic = "E"
|
||||
// QSLViaManager is import-only in the standard: it may be read from another
|
||||
// logger's file, never written to one. OpsLog keeps it when it arrives so
|
||||
// the operator's own data is not silently altered, and NormaliseQSLVia is
|
||||
// the only place that decides so.
|
||||
QSLViaManager = "M"
|
||||
)
|
||||
|
||||
// NormaliseQSLVia folds what other loggers and OpsLog's own older versions put
|
||||
// in a routing field down to the ADIF enumeration.
|
||||
//
|
||||
// It accepts the letter, the English word, and the French one — OpsLog wrote
|
||||
// "Bureau", "Direct" and "Electronic" in full for a long time, and the QSL
|
||||
// Manager panel still shows those words to a French operator. Anything it does
|
||||
// not recognise comes back empty rather than being passed through: this feeds
|
||||
// an enumerated ADIF field, and inventing a value there breaks the file for
|
||||
// every other logger that reads it.
|
||||
func NormaliseQSLVia(s string) string {
|
||||
switch strings.ToUpper(strings.TrimSpace(s)) {
|
||||
case "B", "BUREAU", "BURO", "VIA BUREAU":
|
||||
return QSLViaBureau
|
||||
case "D", "DIRECT":
|
||||
return QSLViaDirect
|
||||
case "E", "ELECTRONIC", "ELECTRONIQUE", "ÉLECTRONIQUE", "OQRS":
|
||||
return QSLViaElectronic
|
||||
case "M", "MANAGER":
|
||||
return QSLViaManager
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsQSLViaRouting reports whether a QSL_VIA value is in fact a routing method
|
||||
// that ended up in the manager field.
|
||||
//
|
||||
// It exists for one repair: OpsLog's QSL Manager panel wrote "Bureau",
|
||||
// "Direct" and "Electronic" into QSL_VIA, and imports folded QSL_SENT_VIA
|
||||
// there too, so logs hold a mixture of managers and routing words in one
|
||||
// column. A manager is a callsign, never one of these six words, so the test
|
||||
// is exact — but it is deliberately narrow: anything else, including a manager
|
||||
// whose callsign happens to be unusual, is left alone.
|
||||
func IsQSLViaRouting(s string) bool {
|
||||
return NormaliseQSLVia(s) != ""
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package adif
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormaliseQSLVia(t *testing.T) {
|
||||
for in, want := range map[string]string{
|
||||
"B": "B", "b": "B", "Bureau": "B", "BUREAU": "B", " buro ": "B",
|
||||
"D": "D", "Direct": "D", "direct": "D",
|
||||
"E": "E", "Electronic": "E", "électronique": "E", "OQRS": "E",
|
||||
"M": "M", "Manager": "M",
|
||||
// A manager's callsign is not a routing method, and neither is noise.
|
||||
"M0OXO": "", "EA5GL": "", "": "", "Bureau via M0OXO": "", "X": "",
|
||||
} {
|
||||
if got := NormaliseQSLVia(in); got != want {
|
||||
t.Errorf("NormaliseQSLVia(%q) = %q, want %q", in, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The repair moves values out of the manager column. A false positive would
|
||||
// erase a real manager, so the guard is worth its own test: every callsign-like
|
||||
// value must be refused.
|
||||
func TestIsQSLViaRoutingRefusesManagers(t *testing.T) {
|
||||
for _, call := range []string{
|
||||
"M0OXO", "EA5GL", "F5CWU", "DJ9ZB", "W3HNK", "IK2DUW", "N7RO",
|
||||
"BUREAU M0OXO", "via bureau DL1XYZ", "QSL DIRECT ONLY",
|
||||
} {
|
||||
if IsQSLViaRouting(call) {
|
||||
t.Errorf("%q was taken for a routing method — the repair would erase it", call)
|
||||
}
|
||||
}
|
||||
for _, v := range []string{"B", "D", "E", "M", "Bureau", "Direct", "Electronic"} {
|
||||
if !IsQSLViaRouting(v) {
|
||||
t.Errorf("%q should be recognised as a routing method", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,3 +119,85 @@ func renderRecord(q qso.QSO, includeApp bool) string {
|
||||
bw.Flush()
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// TestQSLViaFieldsRoundTrip covers the case reported as issue #16: a log
|
||||
// exported by another logger carries a manager in QSL_VIA and a routing method
|
||||
// in QSL_SENT_VIA, and the two must stay apart.
|
||||
//
|
||||
// Before this, QSL_SENT_VIA was folded into QSL_VIA whenever QSL_VIA was empty
|
||||
// — so a Log4OM log, which defaults QSL_SENT_VIA to E, showed "E" wherever
|
||||
// OpsLog displays the manager — and QSL_RCVD_VIA was thrown away outright: it
|
||||
// was listed as a promoted field with no column behind it, so it was not even
|
||||
// kept among the extras. Neither was ever exported, which made an import
|
||||
// followed by an export destroy both.
|
||||
func TestQSLViaFieldsRoundTrip(t *testing.T) {
|
||||
in := qso.QSO{
|
||||
Callsign: "3B9FR", Band: "20m", Mode: "CW",
|
||||
QSODate: time.Date(2026, 6, 6, 12, 0, 0, 0, time.UTC),
|
||||
QSLVia: "M0OXO", // the manager
|
||||
QSLSentVia: "D", // sent direct
|
||||
QSLRcvdVia: "B", // came back via the bureau
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
bw := bufio.NewWriter(&buf)
|
||||
bw.WriteString("<EOH>\n")
|
||||
writeRecord(bw, in, true, nil)
|
||||
bw.Flush()
|
||||
|
||||
var rec Record
|
||||
if err := Parse(strings.NewReader(buf.String()), func(r Record) error { rec = r; return nil }); err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
out, ok := recordToQSO(rec)
|
||||
if !ok {
|
||||
t.Fatal("recordToQSO returned !ok")
|
||||
}
|
||||
for name, c := range map[string]struct{ got, want string }{
|
||||
"QSL_VIA": {out.QSLVia, in.QSLVia},
|
||||
"QSL_SENT_VIA": {out.QSLSentVia, in.QSLSentVia},
|
||||
"QSL_RCVD_VIA": {out.QSLRcvdVia, in.QSLRcvdVia},
|
||||
} {
|
||||
if c.got != c.want {
|
||||
t.Errorf("%s: got %q, want %q", name, c.got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestQSLSentViaDoesNotBecomeManager pins the exact shape a Log4OM export has:
|
||||
// no QSL_VIA at all, QSL_SENT_VIA defaulted to E. The manager field must come
|
||||
// back empty rather than holding "E".
|
||||
func TestQSLSentViaDoesNotBecomeManager(t *testing.T) {
|
||||
const rec = "<CALL:5>OE6CLD<QSO_DATE:8>20260606<TIME_ON:4>1200<BAND:3>20m<MODE:2>CW" +
|
||||
"<QSL_SENT_VIA:1>E<EOR>\n"
|
||||
var got Record
|
||||
if err := Parse(strings.NewReader("<EOH>\n"+rec), func(r Record) error { got = r; return nil }); err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
q, ok := recordToQSO(got)
|
||||
if !ok {
|
||||
t.Fatal("recordToQSO returned !ok")
|
||||
}
|
||||
if q.QSLVia != "" {
|
||||
t.Errorf("QSL_VIA = %q — the routing method leaked into the manager field again", q.QSLVia)
|
||||
}
|
||||
if q.QSLSentVia != "E" {
|
||||
t.Errorf("QSL_SENT_VIA = %q, want %q", q.QSLSentVia, "E")
|
||||
}
|
||||
}
|
||||
|
||||
// M is import-only in the ADIF QSL Via enumeration: keep it when given, never
|
||||
// write it back out.
|
||||
func TestQSLViaManagerIsImportOnly(t *testing.T) {
|
||||
in := qso.QSO{
|
||||
Callsign: "3B9FR", Band: "20m", Mode: "CW",
|
||||
QSODate: time.Date(2026, 6, 6, 12, 0, 0, 0, time.UTC),
|
||||
QSLSentVia: "M", QSLRcvdVia: "M",
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
bw := bufio.NewWriter(&buf)
|
||||
writeRecord(bw, in, true, nil)
|
||||
bw.Flush()
|
||||
if s := buf.String(); strings.Contains(s, "QSL_SENT_VIA") || strings.Contains(s, "QSL_RCVD_VIA") {
|
||||
t.Errorf("exported an import-only value:\n%s", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
-- QSL_SENT_VIA / QSL_RCVD_VIA — the ADIF fields that say HOW a card travelled.
|
||||
--
|
||||
-- Until now OpsLog had one column, qsl_via, and used it for two unrelated
|
||||
-- things: the QSL manager (what ADIF's QSL_VIA holds) and the routing method.
|
||||
-- Imports made it worse — QSL_SENT_VIA was folded into qsl_via when qsl_via was
|
||||
-- empty, so a Log4OM log arrived with "E" sitting where the manager belongs —
|
||||
-- and QSL_RCVD_VIA was dropped outright, listed as a promoted field with no
|
||||
-- column behind it, so it was not even kept among the extras.
|
||||
--
|
||||
-- These two columns hold the ADIF enumeration: B (bureau), D (direct),
|
||||
-- E (electronic). M (manager) is accepted on import only, per the standard.
|
||||
--
|
||||
-- Adding the columns is all that happens here. Existing qsl_via values are NOT
|
||||
-- touched: separating a manager from a routing word rewrites what an operator
|
||||
-- can see in their own log, so it is offered once, with a count, and only runs
|
||||
-- when they say yes.
|
||||
ALTER TABLE qso ADD COLUMN qsl_sent_via TEXT;
|
||||
ALTER TABLE qso ADD COLUMN qsl_rcvd_via TEXT;
|
||||
+93
-6
@@ -99,7 +99,9 @@ type QSO struct {
|
||||
QSLRcvd string `json:"qsl_rcvd,omitempty"`
|
||||
QSLSentDate string `json:"qsl_sent_date,omitempty"`
|
||||
QSLRcvdDate string `json:"qsl_rcvd_date,omitempty"`
|
||||
QSLVia string `json:"qsl_via,omitempty"`
|
||||
QSLVia string `json:"qsl_via,omitempty"` // ADIF QSL_VIA — the QSL manager
|
||||
QSLSentVia string `json:"qsl_sent_via,omitempty"` // ADIF enumeration B/D/E — how the card was sent
|
||||
QSLRcvdVia string `json:"qsl_rcvd_via,omitempty"` // same enumeration, for the card received
|
||||
QSLMsg string `json:"qsl_msg,omitempty"`
|
||||
QSLMsgRcvd string `json:"qslmsg_rcvd,omitempty"`
|
||||
|
||||
@@ -246,7 +248,7 @@ const columnList = `callsign, qso_date, qso_date_off, band, band_rx, mode, submo
|
||||
grid, gridsquare_ext, vucc_grids,
|
||||
country, state, cnty, dxcc, cont, cqz, ituz, iota, sota_ref, pota_ref,
|
||||
age, lat, lon, rig, ant,
|
||||
qsl_sent, qsl_rcvd, qsl_sent_date, qsl_rcvd_date, qsl_via, qsl_msg, qslmsg_rcvd,
|
||||
qsl_sent, qsl_rcvd, qsl_sent_date, qsl_rcvd_date, qsl_via, qsl_sent_via, qsl_rcvd_via, qsl_msg, qslmsg_rcvd,
|
||||
lotw_sent, lotw_rcvd, lotw_sent_date, lotw_rcvd_date,
|
||||
eqsl_sent, eqsl_rcvd, eqsl_sent_date, eqsl_rcvd_date,
|
||||
clublog_qso_upload_date, clublog_qso_upload_status,
|
||||
@@ -321,7 +323,7 @@ func (q *QSO) args() []any {
|
||||
q.Grid, q.GridExt, q.VUCCGrids,
|
||||
q.Country, q.State, q.County, q.DXCC, q.Continent, q.CQZ, q.ITUZ, q.IOTA, q.SOTARef, q.POTARef,
|
||||
q.Age, q.Lat, q.Lon, q.Rig, q.Ant,
|
||||
q.QSLSent, q.QSLRcvd, q.QSLSentDate, q.QSLRcvdDate, q.QSLVia, q.QSLMsg, q.QSLMsgRcvd,
|
||||
q.QSLSent, q.QSLRcvd, q.QSLSentDate, q.QSLRcvdDate, q.QSLVia, q.QSLSentVia, q.QSLRcvdVia, q.QSLMsg, q.QSLMsgRcvd,
|
||||
q.LOTWSent, q.LOTWRcvd, q.LOTWSentDate, q.LOTWRcvdDate,
|
||||
q.EQSLSent, q.EQSLRcvd, q.EQSLSentDate, q.EQSLRcvdDate,
|
||||
q.ClublogUploadDate, q.ClublogUploadStatus,
|
||||
@@ -771,6 +773,8 @@ var bulkEditableCols = map[string]bool{
|
||||
"qsl_sent": true,
|
||||
"qsl_rcvd": true,
|
||||
"qsl_via": true,
|
||||
"qsl_sent_via": true,
|
||||
"qsl_rcvd_via": true,
|
||||
"qrzcom_qso_upload_status": true,
|
||||
"qrzcom_qso_download_status": true,
|
||||
"clublog_qso_upload_status": true,
|
||||
@@ -1250,7 +1254,7 @@ var filterableColumns = map[string]bool{
|
||||
"grid": true, "country": true, "state": true, "cnty": true,
|
||||
"dxcc": true, "cont": true, "cqz": true, "ituz": true,
|
||||
"iota": true, "sota_ref": true, "pota_ref": true, "wwff_ref": true, "rig": true, "ant": true,
|
||||
"qsl_sent": true, "qsl_rcvd": true, "qsl_via": true,
|
||||
"qsl_sent": true, "qsl_rcvd": true, "qsl_via": true, "qsl_sent_via": true, "qsl_rcvd_via": true,
|
||||
"lotw_sent": true, "lotw_rcvd": true, "eqsl_sent": true, "eqsl_rcvd": true,
|
||||
"qrzcom_qso_upload_status": true, "qrzcom_qso_download_status": true,
|
||||
"clublog_qso_upload_status": true, "hrdlog_qso_upload_status": true,
|
||||
@@ -2265,6 +2269,86 @@ func (r *Repo) WorkedCountyKeys(ctx context.Context, keyFn func(state, cnty stri
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// CountQSLViaRouting counts the QSOs whose qsl_via holds a routing method
|
||||
// instead of a manager, per isRouting.
|
||||
//
|
||||
// The test is applied in Go rather than in SQL because it has to hold the same
|
||||
// vocabulary as the import and the QSL panel — one list of accepted spellings,
|
||||
// in internal/adif, not a LIKE pattern drifting apart from it here. Only the
|
||||
// distinct values are examined, so the log is scanned once whatever its size.
|
||||
func (r *Repo) CountQSLViaRouting(ctx context.Context, isRouting func(string) bool) (int, error) {
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT qsl_via, COUNT(*) FROM qso
|
||||
WHERE qsl_via IS NOT NULL AND qsl_via != ''
|
||||
AND (qsl_sent_via IS NULL OR qsl_sent_via = '')
|
||||
GROUP BY qsl_via`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
n := 0
|
||||
for rows.Next() {
|
||||
var via string
|
||||
var c int
|
||||
if err := rows.Scan(&via, &c); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if isRouting(via) {
|
||||
n += c
|
||||
}
|
||||
}
|
||||
return n, rows.Err()
|
||||
}
|
||||
|
||||
// RepairQSLViaRouting moves routing words out of qsl_via into qsl_sent_via,
|
||||
// returning how many QSOs were changed.
|
||||
//
|
||||
// One UPDATE per distinct spelling, not per QSO: a log holds a handful of them
|
||||
// ("Bureau", "E", "Direct"…), and a remote MySQL logbook must not be made to
|
||||
// carry one round trip per contact for a tidy-up. Rows that already have a
|
||||
// sent-via are left alone by the same condition the count uses, so running this
|
||||
// twice cannot undo a later import.
|
||||
func (r *Repo) RepairQSLViaRouting(ctx context.Context, normalise func(string) string) (int, error) {
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT DISTINCT qsl_via FROM qso
|
||||
WHERE qsl_via IS NOT NULL AND qsl_via != ''
|
||||
AND (qsl_sent_via IS NULL OR qsl_sent_via = '')`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
type move struct{ from, to string }
|
||||
var moves []move
|
||||
for rows.Next() {
|
||||
var via string
|
||||
if err := rows.Scan(&via); err != nil {
|
||||
rows.Close()
|
||||
return 0, err
|
||||
}
|
||||
if to := normalise(via); to != "" {
|
||||
moves = append(moves, move{from: via, to: to})
|
||||
}
|
||||
}
|
||||
rows.Close()
|
||||
if err := rows.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
total := 0
|
||||
for _, m := range moves {
|
||||
res, err := r.db.ExecContext(ctx,
|
||||
`UPDATE qso SET qsl_sent_via = ?, qsl_via = ''
|
||||
WHERE qsl_via = ? AND (qsl_sent_via IS NULL OR qsl_sent_via = '')`,
|
||||
m.to, m.from)
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
if n, err := res.RowsAffected(); err == nil {
|
||||
total += int(n)
|
||||
}
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// CallCounties returns callsign → "STATE,County" for every US station already
|
||||
// logged with a county, newest QSO winning.
|
||||
//
|
||||
@@ -2889,7 +2973,8 @@ func scanQSO(s scanner) (QSO, error) {
|
||||
rig, ant sql.NullString
|
||||
qslSent, qslRcvd sql.NullString
|
||||
qslSentDate, qslRcvdDate sql.NullString
|
||||
qslVia, qslMsg, qslMsgRcvd sql.NullString
|
||||
qslVia, qslSentVia, qslRcvdVia sql.NullString
|
||||
qslMsg, qslMsgRcvd sql.NullString
|
||||
lotwSent, lotwRcvd sql.NullString
|
||||
lotwSentDate, lotwRcvdDate sql.NullString
|
||||
eqslSent, eqslRcvd sql.NullString
|
||||
@@ -2934,7 +3019,7 @@ func scanQSO(s scanner) (QSO, error) {
|
||||
&grid, &gridExt, &vucc,
|
||||
&country, &state, &cnty, &dxcc, &cont, &cqz, &ituz, &iota, &sota, &pota,
|
||||
&age, &lat, &lon, &rig, &ant,
|
||||
&qslSent, &qslRcvd, &qslSentDate, &qslRcvdDate, &qslVia, &qslMsg, &qslMsgRcvd,
|
||||
&qslSent, &qslRcvd, &qslSentDate, &qslRcvdDate, &qslVia, &qslSentVia, &qslRcvdVia, &qslMsg, &qslMsgRcvd,
|
||||
&lotwSent, &lotwRcvd, &lotwSentDate, &lotwRcvdDate,
|
||||
&eqslSent, &eqslRcvd, &eqslSentDate, &eqslRcvdDate,
|
||||
&clublogDate, &clublogStatus,
|
||||
@@ -3018,6 +3103,8 @@ func scanQSO(s scanner) (QSO, error) {
|
||||
q.QSLSentDate = qslSentDate.String
|
||||
q.QSLRcvdDate = qslRcvdDate.String
|
||||
q.QSLVia = qslVia.String
|
||||
q.QSLSentVia = qslSentVia.String
|
||||
q.QSLRcvdVia = qslRcvdVia.String
|
||||
q.QSLMsg = qslMsg.String
|
||||
q.QSLMsgRcvd = qslMsgRcvd.String
|
||||
q.LOTWSent = lotwSent.String
|
||||
|
||||
Reference in New Issue
Block a user