feat: skip QSOs with unchanged confirmation data

When syncing from LoTW/DCL, only update QSOs if confirmation data
has changed. This avoids unnecessary database updates and makes it
clear which QSOs actually changed.

- LoTW: Checks if lotwQslRstatus or lotwQslRdate changed
- DCL: Checks if dclQslRstatus, dclQslRdate, DOK, or grid changed
- Frontend: Shows skipped count in sync summary

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-17 19:49:56 +01:00
parent 3592dbb4fb
commit e09ab94e63
3 changed files with 74 additions and 38 deletions

View File

@@ -192,6 +192,7 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
let addedCount = 0;
let updatedCount = 0;
let skippedCount = 0;
const errors = [];
const addedQSOs = [];
const updatedQSOs = [];
@@ -219,26 +220,41 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
.limit(1);
if (existing.length > 0) {
// Update existing QSO with DCL confirmation and DOK data
await db
.update(qsos)
.set({
dclQslRdate: dbQSO.dclQslRdate,
dclQslRstatus: dbQSO.dclQslRstatus,
darcDok: dbQSO.darcDok || existing[0].darcDok,
myDarcDok: dbQSO.myDarcDok || existing[0].myDarcDok,
grid: dbQSO.grid || existing[0].grid,
gridSource: dbQSO.gridSource || existing[0].gridSource,
})
.where(eq(qsos.id, existing[0].id));
updatedCount++;
// Track updated QSO (CALL and DATE)
updatedQSOs.push({
callsign: dbQSO.callsign,
date: dbQSO.qsoDate,
band: dbQSO.band,
mode: dbQSO.mode,
});
const existingQSO = existing[0];
// Check if DCL confirmation or DOK data has changed
const dataChanged =
existingQSO.dclQslRstatus !== dbQSO.dclQslRstatus ||
existingQSO.dclQslRdate !== dbQSO.dclQslRdate ||
existingQSO.darcDok !== (dbQSO.darcDok || existingQSO.darcDok) ||
existingQSO.myDarcDok !== (dbQSO.myDarcDok || existingQSO.myDarcDok) ||
existingQSO.grid !== (dbQSO.grid || existingQSO.grid);
if (dataChanged) {
// Update existing QSO with changed DCL confirmation and DOK data
await db
.update(qsos)
.set({
dclQslRdate: dbQSO.dclQslRdate,
dclQslRstatus: dbQSO.dclQslRstatus,
darcDok: dbQSO.darcDok || existingQSO.darcDok,
myDarcDok: dbQSO.myDarcDok || existingQSO.myDarcDok,
grid: dbQSO.grid || existingQSO.grid,
gridSource: dbQSO.gridSource || existingQSO.gridSource,
})
.where(eq(qsos.id, existingQSO.id));
updatedCount++;
// Track updated QSO (CALL and DATE)
updatedQSOs.push({
callsign: dbQSO.callsign,
date: dbQSO.qsoDate,
band: dbQSO.band,
mode: dbQSO.mode,
});
} else {
// Skip - same data
skippedCount++;
}
} else {
// Insert new QSO
await db.insert(qsos).values(dbQSO);
@@ -274,6 +290,7 @@ export async function syncQSOs(userId, dclApiKey, sinceDate = null, jobId = null
total: adifQSOs.length,
added: addedCount,
updated: updatedCount,
skipped: skippedCount,
addedQSOs,
updatedQSOs,
confirmed: adifQSOs.filter(q => q.dcl_qsl_rcvd === 'Y').length,

View File

@@ -222,6 +222,7 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
let addedCount = 0;
let updatedCount = 0;
let skippedCount = 0;
const errors = [];
const addedQSOs = [];
const updatedQSOs = [];
@@ -247,22 +248,34 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
.limit(1);
if (existing.length > 0) {
await db
.update(qsos)
.set({
lotwQslRdate: dbQSO.lotwQslRdate,
lotwQslRstatus: dbQSO.lotwQslRstatus,
lotwSyncedAt: dbQSO.lotwSyncedAt,
})
.where(eq(qsos.id, existing[0].id));
updatedCount++;
// Track updated QSO (CALL and DATE)
updatedQSOs.push({
callsign: dbQSO.callsign,
date: dbQSO.qsoDate,
band: dbQSO.band,
mode: dbQSO.mode,
});
const existingQSO = existing[0];
// Check if LoTW confirmation data has changed
const confirmationChanged =
existingQSO.lotwQslRstatus !== dbQSO.lotwQslRstatus ||
existingQSO.lotwQslRdate !== dbQSO.lotwQslRdate;
if (confirmationChanged) {
await db
.update(qsos)
.set({
lotwQslRdate: dbQSO.lotwQslRdate,
lotwQslRstatus: dbQSO.lotwQslRstatus,
lotwSyncedAt: dbQSO.lotwSyncedAt,
})
.where(eq(qsos.id, existingQSO.id));
updatedCount++;
// Track updated QSO (CALL and DATE)
updatedQSOs.push({
callsign: dbQSO.callsign,
date: dbQSO.qsoDate,
band: dbQSO.band,
mode: dbQSO.mode,
});
} else {
// Skip - same data
skippedCount++;
}
} else {
await db.insert(qsos).values(dbQSO);
addedCount++;
@@ -288,13 +301,14 @@ export async function syncQSOs(userId, lotwUsername, lotwPassword, sinceDate = n
}
}
logger.info('LoTW sync completed', { total: adifQSOs.length, added: addedCount, updated: updatedCount, jobId });
logger.info('LoTW sync completed', { total: adifQSOs.length, added: addedCount, updated: updatedCount, skipped: skippedCount, jobId });
return {
success: true,
total: adifQSOs.length,
added: addedCount,
updated: updatedCount,
skipped: skippedCount,
addedQSOs,
updatedQSOs,
errors: errors.length > 0 ? errors : undefined,

View File

@@ -296,7 +296,12 @@
<div class="alert {syncResult.success ? 'alert-success' : 'alert-error'}">
{#if syncResult.success}
<h3>Sync Complete!</h3>
<p>Total: {syncResult.total}, Added: {syncResult.added}, Updated: {syncResult.updated}</p>
<p>
Total: {syncResult.total},
Added: {syncResult.added},
Updated: {syncResult.updated || 0},
Skipped: {syncResult.skipped || 0}
</p>
{#if syncResult.errors && syncResult.errors.length > 0}
<p class="text-small">Some QSOs had errors</p>
{/if}