fix(hamlog): a status AND a date, like every other service

Bulk edit offered two HAMLOG fields, both dates, and the first question they
drew was the right one: which of these is the status? Collapsing the flag into
the date was neat in the storage and a riddle in the dialog, where the eight
fields above it are four status/date pairs.

So four keys now — APP_OPSLOG_HAMLOG_SENT / _SENT_DATE and _QSL / _QSL_DATE —
and four fields, in the shape an operator already reads for eQSL, QRZ, Club Log
and HRDLog. The stamp written after a successful upload follows: Y in the
status, the day in the date.

Nothing else changes: the QSL Manager's backlog still asks whether the sent key
is present, and the award engine still treats any non-N value in the QSL key as
a confirmation.
This commit is contained in:
2026-08-23 13:14:05 +02:00
parent 3f82da3c18
commit f8fdfab031
6 changed files with 36 additions and 22 deletions
+11 -5
View File
@@ -12849,10 +12849,14 @@ func (a *App) extShouldUpload(svc extsvc.Service, id int64) bool {
// markExtUploaded stamps the per-service upload status on the QSO row and
// tells the frontend to refresh that row's confirmation columns.
// hamlogSentKey records the day a QSO went to HAMLOG.online. Shared with the
// row colours (lib/rowColors.ts) and the QSL Manager, so the three agree on
// what "already sent" means.
const hamlogSentKey = "APP_OPSLOG_HAMLOG_SENT"
// hamlogSentKey says a QSO has gone to HAMLOG.online; hamlogSentDateKey says
// when. Split the way every other service splits it — a status and a date —
// and shared with the row colours (lib/rowColors.ts), the QSL Manager's backlog
// and the bulk editor, so all four agree on what "already sent" means.
const (
hamlogSentKey = "APP_OPSLOG_HAMLOG_SENT"
hamlogSentDateKey = "APP_OPSLOG_HAMLOG_SENT_DATE"
)
func (a *App) markExtUploaded(svc extsvc.Service, id int64, logID string) {
date := time.Now().UTC().Format("20060102")
@@ -12896,7 +12900,9 @@ func (a *App) markExtUploaded(svc extsvc.Service, id int64, logID string) {
// a falsehood into every export. The date rather than a Y, so a log tells
// WHEN a contact went — and so the same shape serves the QSL Manager's
// backlog query, which asks "is this key present at all".
err = a.qso.SetExtra(ctx, id, hamlogSentKey, date)
if err = a.qso.SetExtra(ctx, id, hamlogSentKey, "Y"); err == nil {
err = a.qso.SetExtra(ctx, id, hamlogSentDateKey, date)
}
}
if err != nil {
applog.Printf("extsvc: mark %s uploaded %d failed: %v", svc, id, err)
+2 -2
View File
@@ -10,7 +10,7 @@
"The row colours in Appearance can be scoped to HAMLOG.online as well, alongside paper, LoTW, eQSL and QRZ.com.",
"HAMLOG.online appears in the QSL Manager too: pick it to see every QSO never sent there and upload the backlog in one go. A contact already sent carries the date it went, so a second sweep does not send it twice.",
"Right-clicking QSOs offers HAMLOG.online among the upload targets, and the toast names each service properly instead of echoing its internal id.",
"Bulk edit can set the HAMLOG.online sent and confirmed dates. A log uploaded to their site by hand had no way to be marked as sent, so OpsLog kept offering to upload every contact again."
"Bulk edit can set HAMLOG.online sent and received, each with its status and its date — the same four fields every other service has there. A log uploaded to their site by hand had no way to be marked as sent, so OpsLog kept offering to upload every contact again."
],
"fr": [
"Yaesu : une ligne ANT dans le panneau du poste sélectionne la prise d'antenne, et le choix est mémorisé pour la bande sur laquelle il a été fait — on change de bande, l'antenne suit. Aucune page à configurer : choisir l'antenne une fois sur une bande le dit très bien. Les postes à une seule prise n'affichent jamais la ligne.",
@@ -20,7 +20,7 @@
"Les couleurs de lignes dans Apparence peuvent aussi être limitées à HAMLOG.online, aux côtés du papier, de LoTW, d'eQSL et de QRZ.com.",
"HAMLOG.online figure aussi dans le gestionnaire QSL : on le choisit pour voir tous les QSO jamais envoyés là-bas et expédier l'arriéré d'un coup. Un contact déjà envoyé porte la date de son départ, donc un second passage ne le renvoie pas.",
"Le clic droit sur des QSO propose HAMLOG.online parmi les destinations d'envoi, et la notification nomme correctement chaque service au lieu d'afficher son identifiant interne.",
"L'édition en masse peut renseigner les dates d'envoi et de confirmation HAMLOG.online. Un journal téléversé à la main sur leur site n'avait aucun moyen d'être marqué comme envoyé, et OpsLog proposait donc de renvoyer tous les contacts."
"L'édition en masse peut renseigner HAMLOG.online envoyé et reçu, chacun avec son statut et sa date — les mêmes quatre champs que les autres services. Un journal téléversé à la main sur leur site n'avait aucun moyen d'être marqué comme envoyé, et OpsLog proposait donc de renvoyer tous les contacts."
]
},
{
+5 -5
View File
@@ -49,11 +49,11 @@ const FIELDS: FieldDef[] = [
{ id: 'hrdlog_sent', label: 'bulk.fHrdlogSent', group: 'QSL / upload', kind: 'status' },
{ id: 'hrdlog_sent_date', label: 'bulk.fHrdlogSentDate', group: 'QSL / upload', kind: 'date' },
// HAMLOG.online: no promoted column, written into extras_json (see
// qso.bulkEditableExtras). Free text rather than a Y/N status because the
// value carries the DATE it went — which is what the QSL Manager's backlog
// and the row colours both read.
{ id: 'hamlog_sent', label: 'bulk.fHamlogSent', group: 'QSL / upload', kind: 'date' },
{ id: 'hamlog_rcvd', label: 'bulk.fHamlogRcvd', group: 'QSL / upload', kind: 'date' },
// qso.bulkEditableExtras). Status and date, like every other service here.
{ id: 'hamlog_sent', label: 'bulk.fHamlogSent', group: 'QSL / upload', kind: 'status' },
{ id: 'hamlog_sent_date', label: 'bulk.fHamlogSentDate', group: 'QSL / upload', kind: 'date' },
{ id: 'hamlog_rcvd', label: 'bulk.fHamlogRcvd', group: 'QSL / upload', kind: 'status' },
{ id: 'hamlog_rcvd_date', label: 'bulk.fHamlogRcvdDate', group: 'QSL / upload', kind: 'date' },
// My station / operator
{ id: 'station_callsign', label: 'bulk.fStationCall', group: 'My station', kind: 'text', upper: true },
{ id: 'operator', label: 'bulk.fOperator', group: 'My station', kind: 'text', upper: true },
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -31,8 +31,10 @@ func TestBandIsNotBulkEditableAlone(t *testing.T) {
// so they must be reachable through the extras path, not the column one.
func TestHamlogFieldsAreBulkEditable(t *testing.T) {
for field, want := range map[string]string{
"hamlog_sent": "APP_OPSLOG_HAMLOG_SENT",
"hamlog_rcvd": "APP_OPSLOG_HAMLOG_QSL",
"hamlog_sent": "APP_OPSLOG_HAMLOG_SENT",
"hamlog_sent_date": "APP_OPSLOG_HAMLOG_SENT_DATE",
"hamlog_rcvd": "APP_OPSLOG_HAMLOG_QSL",
"hamlog_rcvd_date": "APP_OPSLOG_HAMLOG_QSL_DATE",
} {
if got := BulkExtraKey(field); got != want {
t.Errorf("BulkExtraKey(%q) = %q, want %q", field, got, want)
+12 -6
View File
@@ -910,12 +910,18 @@ func (r *Repo) BulkSetField(ctx context.Context, ids []int64, column, value stri
var bulkEditableExtras = map[string]string{
"owner_callsign": "OWNER_CALLSIGN",
// HAMLOG.online has no promoted column — the ADIF standard defines a field
// for hamlog.EU and none for hamlog.ONLINE. Both directions are editable in
// bulk because a log uploaded to their site BY HAND has to be markable
// afterwards: without it OpsLog would offer to send every one of those
// contacts again.
"hamlog_sent": "APP_OPSLOG_HAMLOG_SENT",
"hamlog_rcvd": "APP_OPSLOG_HAMLOG_QSL",
// for hamlog.EU and none for hamlog.ONLINE. Bulk-editable because a log
// uploaded to their site BY HAND has to be markable afterwards: without it
// OpsLog would offer to send every one of those contacts again.
//
// A STATUS and a DATE in each direction, like every other service: one field
// holding a date and standing in for the flag looked clever and read as a
// riddle — "which of these two is the status?" was the first thing an
// operator asked of it.
"hamlog_sent": "APP_OPSLOG_HAMLOG_SENT",
"hamlog_sent_date": "APP_OPSLOG_HAMLOG_SENT_DATE",
"hamlog_rcvd": "APP_OPSLOG_HAMLOG_QSL",
"hamlog_rcvd_date": "APP_OPSLOG_HAMLOG_QSL_DATE",
}
// BulkExtraKey maps a frontend field id to its ADIF key in extras_json, or "".