hh-shipments 0.3.17 → 0.3.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/public/app.js +6 -2
- package/public/index.html +1 -0
- package/server.js +19 -3
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -56,7 +56,10 @@ function showApp() {
|
|
|
56
56
|
$('tabbar').hidden = false;
|
|
57
57
|
showView('view-dash');
|
|
58
58
|
loadDash();
|
|
59
|
-
api.get('/api/version').then((v) => {
|
|
59
|
+
api.get('/api/version').then((v) => {
|
|
60
|
+
const cv = (document.querySelector('meta[name="app-version"]') || {}).content || '?';
|
|
61
|
+
if (v && v.version) $('app-version').textContent = 'app v' + cv + ' · server v' + v.version;
|
|
62
|
+
}).catch(() => {});
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
/* ---------- auth ---------- */
|
|
@@ -119,6 +122,7 @@ async function runQuick(text, targetEl, preset) {
|
|
|
119
122
|
let html = '';
|
|
120
123
|
r.added.forEach((a) => { html += `<div class="review">✅ Added <b>${esc(a.number)}</b> (${esc(a.carrier.toUpperCase())})${r.simulated ? ' <span class="sim-badge">SIMULATED</span>' : ''}</div>`; });
|
|
121
124
|
r.duplicates.forEach((n) => { html += `<div class="review">⛔ <b>${esc(n)}</b> is already tracked — skipped.</div>`; });
|
|
125
|
+
(r.updated || []).forEach((u) => { html += `<div class="review">🔄 Updated <b>${esc(u.number)}</b> — seller: <b>${esc(u.seller_name || '—')}</b>${u.device ? ` · ${esc(u.device)}` : ''}</div>`; });
|
|
122
126
|
r.needs_review.forEach((c, i) => {
|
|
123
127
|
html += `<div class="review" id="rev-${i}"><span class="warn">Needs review</span> — <b>${esc(c.number)}</b> (${esc(c.carrier.toUpperCase())}, ${esc(c.confidence)} confidence)${c.error ? ' — ' + esc(c.error) : ''}
|
|
124
128
|
<div class="row"><button class="secondary" data-add="${i}">Add anyway</button></div></div>`;
|
|
@@ -126,7 +130,7 @@ async function runQuick(text, targetEl, preset) {
|
|
|
126
130
|
if (r.context && (r.context.seller_name || r.context.device)) {
|
|
127
131
|
html = `<div class="review">📝 Detected: ${esc(r.context.seller_name || '—')} · ${esc(r.context.device || '—')}</div>` + html;
|
|
128
132
|
}
|
|
129
|
-
if (!r.added.length && !r.needs_review.length && !r.duplicates.length) html = '<div class="review">No tracking numbers found in that text.</div>';
|
|
133
|
+
if (!r.added.length && !r.needs_review.length && !r.duplicates.length && !(r.updated || []).length) html = '<div class="review">No tracking numbers found in that text.</div>';
|
|
130
134
|
targetEl.innerHTML = html;
|
|
131
135
|
targetEl.querySelectorAll('[data-add]').forEach((btn) => {
|
|
132
136
|
btn.onclick = async () => {
|
package/public/index.html
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
6
|
<meta name="theme-color" content="#0a84ff">
|
|
7
|
+
<meta name="app-version" content="0.3.18">
|
|
7
8
|
<title>HH Shipments</title>
|
|
8
9
|
<link rel="manifest" href="/manifest.webmanifest">
|
|
9
10
|
<link rel="apple-touch-icon" href="/icon-192.png">
|
package/server.js
CHANGED
|
@@ -225,9 +225,25 @@ function quickAddText(text, opts = {}) {
|
|
|
225
225
|
// Typed text (paste box) gets the lenient name rule; OCR never does.
|
|
226
226
|
const autoCtx = extract.extractContext(label_text || text, { lenient: !!typed && !label_text });
|
|
227
227
|
const existing = new Set(db.prepare('SELECT tracking_number FROM shipments').all().map((r) => r.tracking_number));
|
|
228
|
-
const added = [], needs_review = [], duplicates = [];
|
|
228
|
+
const added = [], needs_review = [], duplicates = [], updated = [];
|
|
229
229
|
for (const c of candidates) {
|
|
230
|
-
if (existing.has(c.number)) {
|
|
230
|
+
if (existing.has(c.number)) {
|
|
231
|
+
// Already tracked — but a re-paste carrying a name (Hunter's
|
|
232
|
+
// "number + name" workflow) fills a blank seller/device on it.
|
|
233
|
+
const newSeller = seller_name || autoCtx.seller_name;
|
|
234
|
+
const newDevice = device || autoCtx.device;
|
|
235
|
+
if (newSeller || newDevice) {
|
|
236
|
+
const cur = db.prepare('SELECT id, seller_name, device FROM shipments WHERE tracking_number = ?').get(c.number);
|
|
237
|
+
if (cur && ((!cur.seller_name && newSeller) || (!cur.device && newDevice))) {
|
|
238
|
+
db.prepare('UPDATE shipments SET seller_name = COALESCE(seller_name, ?), device = COALESCE(device, ?) WHERE id = ?')
|
|
239
|
+
.run(newSeller || null, newDevice || null, cur.id);
|
|
240
|
+
updated.push({ id: cur.id, number: c.number, seller_name: newSeller || cur.seller_name, device: newDevice || cur.device });
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
duplicates.push(c.number);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
231
247
|
if (c.confidence === 'high') {
|
|
232
248
|
const r = addShipment({ number: c.number, carrier: c.carrier, seller_name: seller_name || autoCtx.seller_name, device: device || autoCtx.device, notes, source });
|
|
233
249
|
if (r.error) needs_review.push({ ...c, error: r.error });
|
|
@@ -237,7 +253,7 @@ function quickAddText(text, opts = {}) {
|
|
|
237
253
|
needs_review.push(c);
|
|
238
254
|
}
|
|
239
255
|
}
|
|
240
|
-
return { added, needs_review, duplicates, context: autoCtx, simulated: provider.simulated };
|
|
256
|
+
return { added, needs_review, duplicates, updated, context: autoCtx, simulated: provider.simulated };
|
|
241
257
|
}
|
|
242
258
|
|
|
243
259
|
app.post('/api/intake/quick', requireAuth, (req, res) => {
|