feat(confirmations): a HamQTH default, and the pending status stops meaning 'sent'
HamQTH joins the Confirmations page with a sent side only — the site publishes no confirmation feed, so there is nothing to receive — and it defaults to R, the same 'still to upload' the other online services get. That default could not have worked as written. HamQTH and HAMLOG.online keep their sent state in an ADIF extra, and eligibility blocked on the key being PRESENT — so an operator setting the natural R default would have stamped every new QSO 'already gone' and silently disabled the very auto-upload the default arms. Eligibility now reads the VALUE: the ADIF pending statuses mean pending, anything else means sent. Guard-tested both ways.
This commit is contained in:
@@ -361,6 +361,7 @@ const (
|
||||
keyQSLDefaultHRDLogStatus = "qsl.hrdlog_status"
|
||||
keyQSLDefaultQRZComStatus = "qsl.qrzcom_status"
|
||||
keyQSLDefaultQRZComCfm = "qsl.qrzcom_confirmed"
|
||||
keyQSLDefaultHamqthStatus = "qsl.hamqth_status"
|
||||
keyQSLDefaultHamlogStatus = "qsl.hamlog_status"
|
||||
keyQSLDefaultHamlogCfm = "qsl.hamlog_confirmed"
|
||||
|
||||
@@ -456,6 +457,9 @@ type QSLDefaults struct {
|
||||
// at "N" here exactly as they do for Club Log.
|
||||
HamlogStatus string `json:"hamlog_status"`
|
||||
HamlogCfm string `json:"hamlog_confirmed"`
|
||||
// HamQTH, same extras story — and SENT only: the site publishes no
|
||||
// confirmation feed, so there is no received side to default.
|
||||
HamqthStatus string `json:"hamqth_status"`
|
||||
}
|
||||
|
||||
// CATSettings is the user-tweakable rig-control configuration. Stored as
|
||||
@@ -10949,6 +10953,7 @@ func defaultQSLDefaults() QSLDefaults {
|
||||
EQSLSent: "R", EQSLRcvd: "N",
|
||||
LOTWSent: "R", LOTWRcvd: "N",
|
||||
ClublogStatus: "R", ClublogCfm: "N", HRDLogStatus: "R",
|
||||
HamqthStatus: "R",
|
||||
QRZComStatus: "R", QRZComCfm: "N",
|
||||
}
|
||||
}
|
||||
@@ -10970,6 +10975,7 @@ func (a *App) GetQSLDefaults() (QSLDefaults, error) {
|
||||
keyQSLDefaultClublogStatus, keyQSLDefaultClublogCfm, keyQSLDefaultHRDLogStatus,
|
||||
keyQSLDefaultQRZComStatus, keyQSLDefaultQRZComCfm,
|
||||
keyQSLDefaultHamlogStatus, keyQSLDefaultHamlogCfm,
|
||||
keyQSLDefaultHamqthStatus,
|
||||
)
|
||||
if err != nil {
|
||||
return out, err
|
||||
@@ -10986,6 +10992,7 @@ func (a *App) GetQSLDefaults() (QSLDefaults, error) {
|
||||
out.QRZComStatus = m[keyQSLDefaultQRZComStatus]
|
||||
out.QRZComCfm = m[keyQSLDefaultQRZComCfm]
|
||||
out.HamlogStatus = m[keyQSLDefaultHamlogStatus]
|
||||
out.HamqthStatus = m[keyQSLDefaultHamqthStatus]
|
||||
out.HamlogCfm = m[keyQSLDefaultHamlogCfm]
|
||||
return out, nil
|
||||
}
|
||||
@@ -11010,6 +11017,7 @@ func (a *App) SaveQSLDefaults(d QSLDefaults) error {
|
||||
keyQSLDefaultQRZComStatus: strings.ToUpper(strings.TrimSpace(d.QRZComStatus)),
|
||||
keyQSLDefaultQRZComCfm: strings.ToUpper(strings.TrimSpace(d.QRZComCfm)),
|
||||
keyQSLDefaultHamlogStatus: strings.ToUpper(strings.TrimSpace(d.HamlogStatus)),
|
||||
keyQSLDefaultHamqthStatus: strings.ToUpper(strings.TrimSpace(d.HamqthStatus)),
|
||||
keyQSLDefaultHamlogCfm: strings.ToUpper(strings.TrimSpace(d.HamlogCfm)),
|
||||
} {
|
||||
if err := a.settings.Set(a.ctx, scope+k, v); err != nil {
|
||||
@@ -11066,6 +11074,7 @@ func applyQSLDefaultsTo(q *qso.QSO, d QSLDefaults) {
|
||||
// string field, and these two are map entries. Only set when the QSO does not
|
||||
// already carry them, which is the same rule fill() applies.
|
||||
setExtraDefault(q, hamlogSentKey, d.HamlogStatus)
|
||||
setExtraDefault(q, hamqthSentKey, d.HamqthStatus)
|
||||
setExtraDefault(q, award.HamlogQSLKey, d.HamlogCfm)
|
||||
}
|
||||
|
||||
@@ -13569,17 +13578,17 @@ func (a *App) extShouldUpload(svc extsvc.Service, id int64) bool {
|
||||
// is not remembered — hence no on-close mode and no manual backlog.
|
||||
return true
|
||||
case extsvc.ServiceHamlog:
|
||||
// The stamp is an extra, not a column — see markExtUploaded. Present means
|
||||
// it has gone, which is what stops an on-demand re-upload of a whole log
|
||||
// from sending every contact twice.
|
||||
if q.Extras != nil && strings.TrimSpace(q.Extras[hamlogSentKey]) != "" {
|
||||
// The stamp is an extra, not a column — see markExtUploaded. A stamp that
|
||||
// MEANS SENT is what stops an on-demand re-upload of a whole log from
|
||||
// sending every contact twice.
|
||||
if q.Extras != nil && extrasSaysSent(q.Extras[hamlogSentKey]) {
|
||||
applog.Printf("extsvc: QSO %d not eligible for hamlog — already sent on %s", id, q.Extras[hamlogSentKey])
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case extsvc.ServiceHamQTH:
|
||||
// Same extras stamp as HAMLOG.online — ADIF names no HamQTH field.
|
||||
if q.Extras != nil && strings.TrimSpace(q.Extras[hamqthSentKey]) != "" {
|
||||
if q.Extras != nil && extrasSaysSent(q.Extras[hamqthSentKey]) {
|
||||
applog.Printf("extsvc: QSO %d not eligible for hamqth — already sent on %s", id, q.Extras[hamqthSentKey])
|
||||
return false
|
||||
}
|
||||
@@ -13608,6 +13617,22 @@ const (
|
||||
hamqthSentDateKey = "APP_OPSLOG_HAMQTH_SENT_DATE"
|
||||
)
|
||||
|
||||
// extrasSaysSent reads an extras upload stamp as "this QSO has GONE".
|
||||
//
|
||||
// Mere presence will not do. markExtUploaded writes "Y" (older builds wrote the
|
||||
// date), but the Confirmations page pre-fills the same key with a TO-DO status
|
||||
// — "R", requested, is the natural default for a service you intend to upload
|
||||
// to — and reading that as "already gone" would silently disable the very
|
||||
// auto-upload the default was set to arm. So the pending statuses mean pending,
|
||||
// and anything else means sent.
|
||||
func extrasSaysSent(v string) bool {
|
||||
switch strings.ToUpper(strings.TrimSpace(v)) {
|
||||
case "", "R", "N", "Q", "I":
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *App) markExtUploaded(svc extsvc.Service, id int64, logID string) {
|
||||
date := time.Now().UTC().Format("20060102")
|
||||
// Use a fresh background context, NOT a.ctx: this stamp often runs during
|
||||
|
||||
+4
-2
@@ -7,14 +7,16 @@
|
||||
"HamQTH upload: 8th external service — real-time QSO upload to the HamQTH online logbook with your callbook credentials (auto-upload on log, “Send to…” right-click, QSL Manager backlog upload, connection test).",
|
||||
"Fixed: the right-click “Send to HAMLOG.online” was uploading the selection to QRZ.com with the QRZ key — it now goes to HAMLOG.online.",
|
||||
"LoTW: TQSL no longer refuses non-US stations over MY_CNTY — the field is stripped before signing unless it is the US “XX,County” shape LoTW actually validates (a Canadian “ONTARIO,Kawartha” was rejecting the whole record). Exports also stop gluing a full state name onto the county.",
|
||||
"DX Cluster: two new chase switches — Chase US counties and Chase new prefixes — and unchecking Chase new grids now also withdraws the NEW GRID badge. Each switch removes its badge from the spots AND its chip from the status filters, like Chase POTA always did."
|
||||
"DX Cluster: two new chase switches — Chase US counties and Chase new prefixes — and unchecking Chase new grids now also withdraws the NEW GRID badge. Each switch removes its badge from the spots AND its chip from the status filters, like Chase POTA always did.",
|
||||
"Confirmations: a HamQTH row — sent only, defaulting to R (to upload), since HamQTH publishes no confirmations to receive. Setting such a default no longer disables the auto-upload it was meant to arm (it also affected HAMLOG.online)."
|
||||
],
|
||||
"fr": [
|
||||
"Changer de base de réglages n’affiche plus « OpsLog is already running » : la relance automatique attend désormais que l’instance qui se ferme libère son verrou au lieu de la prendre de vitesse.",
|
||||
"Upload HamQTH : 8e service externe — envoi des QSO en temps réel vers le logbook HamQTH avec vos identifiants du lookup (upload auto au log, « Envoyer vers… » au clic droit, rattrapage via le QSL Manager, test de connexion).",
|
||||
"Corrigé : le clic droit « Envoyer vers HAMLOG.online » envoyait la sélection à QRZ.com avec la clé QRZ — elle part maintenant vers HAMLOG.online.",
|
||||
"LoTW : TQSL ne refuse plus les stations hors US à cause de MY_CNTY — le champ est retiré avant signature sauf s’il a la forme US « XX,County » que LoTW valide réellement (un « ONTARIO,Kawartha » canadien rejetait tout l’enregistrement). L’export cesse aussi de coller un nom d’état complet devant le comté.",
|
||||
"DX Cluster : deux nouvelles cases — Chasser les comtés US et Chasser les nouveaux préfixes — et décocher Chasser les nouveaux locators retire désormais aussi le badge NEW GRID. Chaque case enlève son badge des spots ET sa puce des filtres de statut, comme Chase POTA le faisait déjà."
|
||||
"DX Cluster : deux nouvelles cases — Chasser les comtés US et Chasser les nouveaux préfixes — et décocher Chasser les nouveaux locators retire désormais aussi le badge NEW GRID. Chaque case enlève son badge des spots ET sa puce des filtres de statut, comme Chase POTA le faisait déjà.",
|
||||
"Confirmations : une ligne HamQTH — envoi seulement, à R (à envoyer) par défaut, HamQTH ne publiant aucune confirmation à recevoir. Définir un tel défaut ne désactive plus l’upload automatique qu’il était censé armer (cela touchait aussi HAMLOG.online)."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hamlog/internal/qso"
|
||||
)
|
||||
|
||||
// A Confirmations default of "R" means "I intend to upload this", not "it has
|
||||
// gone" — reading it as sent would silently disable the auto-upload the default
|
||||
// was set to arm. Only a real stamp blocks.
|
||||
func TestExtrasSaysSent(t *testing.T) {
|
||||
pending := []string{"", " ", "R", "r", "N", "Q", "I"}
|
||||
for _, v := range pending {
|
||||
if extrasSaysSent(v) {
|
||||
t.Errorf("extrasSaysSent(%q) = true, want false (still to upload)", v)
|
||||
}
|
||||
}
|
||||
sent := []string{"Y", "y", "20260831"} // "Y" today, a date in older builds
|
||||
for _, v := range sent {
|
||||
if !extrasSaysSent(v) {
|
||||
t.Errorf("extrasSaysSent(%q) = false, want true (already uploaded)", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The HamQTH default lands on the extras key the uploader reads, and must leave
|
||||
// the QSO eligible.
|
||||
func TestHamQTHDefaultStaysUploadable(t *testing.T) {
|
||||
q := &qso.QSO{Callsign: "F4BPO"}
|
||||
applyQSLDefaultsTo(q, defaultQSLDefaults())
|
||||
if got := q.Extras[hamqthSentKey]; got != "R" {
|
||||
t.Fatalf("HamQTH sent extra = %q, want %q", got, "R")
|
||||
}
|
||||
if extrasSaysSent(q.Extras[hamqthSentKey]) {
|
||||
t.Error("a freshly logged QSO reads as already uploaded to HamQTH")
|
||||
}
|
||||
}
|
||||
@@ -1825,6 +1825,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
clublog_status: string; clublog_confirmed: string; hrdlog_status: string; qrzcom_status: string;
|
||||
qrzcom_confirmed: string;
|
||||
hamlog_status: string; hamlog_confirmed: string;
|
||||
hamqth_status: string;
|
||||
};
|
||||
const [qslDefaults, setQslDefaults] = useState<QSLDefaults>({
|
||||
qsl_sent: '', qsl_rcvd: '',
|
||||
@@ -1832,7 +1833,7 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
eqsl_sent: '', eqsl_rcvd: '',
|
||||
clublog_status: '', clublog_confirmed: '', hrdlog_status: '', qrzcom_status: '',
|
||||
qrzcom_confirmed: '',
|
||||
hamlog_status: '', hamlog_confirmed: '',
|
||||
hamlog_status: '', hamlog_confirmed: '', hamqth_status: '',
|
||||
});
|
||||
|
||||
// External services (logbook upload). One block per service; only QRZ is
|
||||
@@ -5755,6 +5756,15 @@ function SettingsModalImpl({ onClose, onSaved, initialSection, onMainPaneChanged
|
||||
{renderSelect('qrzcom_confirmed', FULL_OPTIONS)}
|
||||
</div>
|
||||
</div>
|
||||
{/* HamQTH — no received side: the site has no confirmation feed. */}
|
||||
<div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
|
||||
<Label className="text-sm font-medium pb-1.5">HamQTH</Label>
|
||||
<div>
|
||||
<Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">{t('conf.sent')}</Label>
|
||||
{renderSelect('hamqth_status', FULL_OPTIONS)}
|
||||
</div>
|
||||
<div />
|
||||
</div>
|
||||
{/* HAMLOG.online */}
|
||||
<div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
|
||||
<Label className="text-sm font-medium pb-1.5">HAMLOG.online</Label>
|
||||
|
||||
@@ -3302,6 +3302,7 @@ export namespace main {
|
||||
qrzcom_confirmed: string;
|
||||
hamlog_status: string;
|
||||
hamlog_confirmed: string;
|
||||
hamqth_status: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new QSLDefaults(source);
|
||||
@@ -3322,6 +3323,7 @@ export namespace main {
|
||||
this.qrzcom_confirmed = source["qrzcom_confirmed"];
|
||||
this.hamlog_status = source["hamlog_status"];
|
||||
this.hamlog_confirmed = source["hamlog_confirmed"];
|
||||
this.hamqth_status = source["hamqth_status"];
|
||||
}
|
||||
}
|
||||
export class QSLEmailTemplates {
|
||||
|
||||
Reference in New Issue
Block a user