PageSourceSearch

https://smart-odpady.ai/src/data/dataBoxPlusTriage.js?v=0.1.821

js smart-odpady.ai collected 2026-10-05 14:39:40 UTC 15,333 bytes, 402 lines download raw bytes

1export const DATA_BOX_PLUS_TRIAGE_QUEUES = [
2  { id: "todo", label: "K vyřízení" },
3  { id: "handed", label: "Předané" },
4  { id: "done", label: "Hotové" }
5];
6
7export const DATA_BOX_PLUS_TRIAGE_MAILBOXES = [
8  { id: "dbp-kaiser-servis", name: "Kaiser servis", company: "Kaiser servis", slot: 1 },
9  { id: "dbp-kaiser-technology", name: "Kaiser technology", company: "Kaiser technology", slot: 2 },
10  { id: "dbp-nanolab-plus", name: "Nanolab plus", company: "Nanolab plus", slot: 3 },
11  { id: "dbp-nanolab-shop", name: "Nanolab shop", company: "Nanolab shop", slot: 4 },
12  { id: "dbp-lefleur", name: "LeFleur", company: "LeFleur", slot: 5 },
13  { id: "dbp-kaiserman-fond", name: "Kaisermanův nadační fond", company: "Kaisermanův nadační fond", slot: 6 },
14  { id: "dbp-kaiser-holding", name: "Kaiser holding", company: "Kaiser holding", slot: 7 }
15];
16
17export const DATA_BOX_PLUS_TRIAGE_DERIVATION_VERSION = "production-workflow-v2";
18
19export async function readDataBoxPlusTriageSnapshot(requestJson) {
20  if (typeof requestJson !== "function") throw new TypeError("requestJson must be a function");
21  const [statusPayload, messagesResult, draftsResult] = await Promise.all([
22    requestJson("/api/data-box-plus/status", { method: "GET" }),
23    requestJson("/api/data-box-plus/messages?limit=150", { method: "GET" }),
24    requestJson("/api/data-box-plus/drafts?status=all", { method: "GET" })
25  ]);
26  const mailboxes = Array.isArray(statusPayload?.mailboxes) && statusPayload.mailboxes.length
27    ? statusPayload.mailboxes
28    : DATA_BOX_PLUS_TRIAGE_MAILBOXES.map((mailbox) => ({ ...mailbox }));
29  const statusResult = {
30    ...statusPayload,
31    apiStatus: statusPayload?.apiStatus || messagesResult?.apiStatus || "ready",
32    mailboxes
33  };
34  return [
35    statusResult,
36    messagesResult,
37    { recommendations: [] },
38    { rules: [] },
39    { syncRuns: statusPayload?.latestSyncRun ? [statusPayload.latestSyncRun] : [] },
40    draftsResult
41  ];
42}
43
44export async function readDataBoxPlusTriageDetail(requestJson, messageId) {
45  if (typeof requestJson !== "function") throw new TypeError("requestJson must be a function");
46  const id = String(messageId || "").trim();
47  if (!id) throw new TypeError("messageId is required");
48  return requestJson(`/api/data-box-plus/messages/${encodeURIComponent(id)}`, { method: "GET" });
49}
50
51function normalizeText(value) {
52  return String(value || "")
53    .normalize("NFD")
54    .replace(/[\u0300-\u036f]/g, "")
55    .toLocaleLowerCase("cs-CZ")
56    .replace(/\s+/g, " ")
57    .trim();
58}
59
60function joinedMessageState(message = {}) {
61  return normalizeText([
62    message.status,
63    message.archiveStatus,
64    message.attachmentStatus,
65    message.riskLevel,
66    message.priority,
67    message.type,
68    message.messageType
69  ].filter(Boolean).join(" "));
70}
71
72function includesAny(value, needles) {
73  return needles.some((needle) => value.includes(needle));
74}
75
76function isoDay(value) {
77  const match = String(value || "").trim().match(/^(\d{4}-\d{2}-\d{2})/);
78  return match?.[1] || "";
79}
80
81function pragueDay(now = new Date()) {
82  const parts = new Intl.DateTimeFormat("en-CA", {
83    timeZone: "Europe/Prague",
84    year: "numeric",
85    month: "2-digit",
86    day: "2-digit"
87  }).formatToParts(now);
88  const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
89  return `${values.year}-${values.month}-${values.day}`;
90}
91
92function deliveredTime(message = {}) {
93  const value = Date.parse(message.deliveredAt || message.receivedAt || "");
94  return Number.isFinite(value) ? value : 0;
95}
96
97function blockerForMessage(message = {}) {
98  const status = normalizeText(message.status);
99  const attachment = normalizeText(message.attachmentStatus);
100
101  if (includesAny(attachment, ["nepodarilo", "chyba", "problem", "nedostupna", "nedostupne"]) || status.includes("chybi priloha")) {
102    return {
103      code: "attachment_problem",
104      label: "Příloha se nenačetla",
105      actionLabel: "Otevřít chybu přílohy",
106      tone: "problem"
107    };
108  }
109  if (status.includes("potrebuje adresata")) {
110    return {
111      code: "needs_recipient",
112      label: "Je potřeba vybrat adresáta",
113      actionLabel: "Vybrat adresáta",
114      tone: "waiting"
115    };
116  }
117  if (status.includes("chybi vozidlo")) {
118    return {
119      code: "needs_vehicle",
120      label: "Je potřeba vybrat vozidlo",
121      actionLabel: "Vybrat vozidlo",
122      tone: "waiting"
123    };
124  }
125  if (includesAny(status, ["potrebuje upresnit", "nejasne", "neurceno"])) {
126    return {
127      code: "needs_classification",
128      label: "Rozřazení není jisté",
129      actionLabel: "Zařadit ručně",
130      tone: "waiting"
131    };
132  }
133  if (includesAny(status, ["nelze provest", "nepodarilo", "problem", "chyba"])) {
134    return {
135      code: "action_problem",
136      label: "Akci se nepodařilo provést",
137      actionLabel: "Otevřít chybu akce",
138      tone: "problem"
139    };
140  }
141  return null;
142}
143
144function isDoneMessage(message = {}) {
145  const archiveStatus = normalizeText(message.archiveStatus);
146  const status = normalizeText(message.status);
147  if (archiveStatus === "archived" || archiveStatus.startsWith("archivovan")) return true;
148  if ([
149    "archivovano",
150    "archivovane",
151    "vyreseno",
152    "completed",
153    "closed"
154  ].includes(status)) return true;
155  return [
156    "odeslano e-mailem",
157    "odeslano datovou schrankou",
158    "odeslano sms"
159  ].some((prefix) => status === prefix || status.startsWith(`${prefix} `));
160}
161
162function isHandedMessage(message = {}) {
163  if (String(message.assignedTo || "").trim()) return true;
164  const status = normalizeText(message.status);
165  return ["predano", "handoff", "assigned"].some((prefix) => status === prefix || status.startsWith(`${prefix} `));
166}
167
168function isUrgentMessage(message = {}, today = pragueDay()) {
169  const state = joinedMessageState(message);
170  if (includesAny(state, ["vysoke", "kriticke", "urgent", "pravni", "legal"])) return true;
171  const dueDay = isoDay(message.dueDate);
172  return Boolean(dueDay && today && dueDay <= today);
173}
174
175function donePresentation(message = {}) {
176  const status = normalizeText(message.status);
177  if (status.includes("odeslano")) {
178    return { code: "sent", label: "Hotovo: zpráva byla odeslána", actionLabel: "Detail historie", tone: "resolved" };
179  }
180  if (normalizeText(message.archiveStatus).startsWith("archiv") || status.includes("archiv")) {
181    return { code: "archived", label: "Hotovo: zpráva je archivovaná", actionLabel: "Detail historie", tone: "archive" };
182  }
183  return { code: "resolved", label: "Hotovo: zpráva je vyřízená", actionLabel: "Detail historie", tone: "resolved" };
184}
185
186function handedPresentation(message = {}) {
187  const assignee = String(message.assignedTo || "").trim();
188  return {
189    code: "waiting_owner",
190    label: assignee ? `Předáno: ${assignee}` : "Předáno odpovědné osobě",
191    actionLabel: "Detail předání",
192    tone: "handoff"
193  };
194}
195
196function todoPresentation(message = {}, today = pragueDay()) {
197  const blocker = blockerForMessage(message);
198  if (blocker) return blocker;
199
200  if (isUrgentMessage(message, today)) {
201    const dueDay = isoDay(message.dueDate);
202    const pastDue = Boolean(dueDay && dueDay < today);
203    const dueToday = Boolean(dueDay && dueDay === today);
204    return {
205      code: pastDue ? "past_due" : dueToday ? "due_today" : "urgent",
206      label: pastDue ? "Po termínu – zkontrolovat nutný krok" : dueToday ? "Termín je dnes" : "Nutná kontrola před předáním",
207      actionLabel: "Zkontrolovat nutný krok",
208      tone: "problem"
209    };
210  }
211
212  const target = dataBoxPlusTriageTarget(message);
213  if (target) {
214    return {
215      code: "suggested_route",
216      label: `Doporučeno: ${target}`,
217      actionLabel: "Zkontrolovat předání",
218      tone: "prepared"
219    };
220  }
221
222  const status = normalizeText(message.status);
223  return {
224    code: status.includes("nov") ? "new" : "open",
225    label: status.includes("nov") ? "Nová zpráva" : "Čeká na zařazení",
226    actionLabel: "Otevřít a zařadit",
227    tone: "new"
228  };
229}
230
231export function dataBoxPlusTriageQueueId(message = {}) {
232  if (isDoneMessage(message)) return "done";
233  if (blockerForMessage(message)) return "todo";
234  if (isHandedMessage(message)) return "handed";
235  return "todo";
236}
237
238export function dataBoxPlusTriageTarget(message = {}) {
239  const assignedTo = String(message.assignedTo || "").trim();
240  if (assignedTo) return assignedTo;
241  const suggestion = String(message.recommendedAction || message.suggestedAction || "").trim();
242  const normalized = normalizeText(suggestion);
243  if (!suggestion || includesAny(normalized, ["chat s autopilotem", "vyresit v chatu", "otevrit zpravu"])) return "";
244  return suggestion.replace(/[.!]+$/, "");
245}
246
247export function dataBoxPlusTriagePresentation(message = {}, options = {}) {
248  const queueId = dataBoxPlusTriageQueueId(message);
249  const today = options.today || pragueDay(options.now);
250  if (queueId === "done") return donePresentation(message);
251  if (queueId === "handed") return handedPresentation(message);
252  return todoPresentation(message, today);
253}
254
255export function dataBoxPlusTriageItem(message = {}, options = {}) {
256  const mailbox = options.mailbox || null;
257  const sentHistoryOnly = normalizeText(message.direction) === "sent";
258  if (sentHistoryOnly) {
259    return {
260      id: String(message.id || ""),
261      mailboxId: String(message.mailboxId || ""),
262      mailboxLabel: String(mailbox?.name || mailbox?.company || "Schránka"),
263      senderName: String(message.senderName || "Datová schránka"),
264      recipientName: String(message.recipientName || message.recipientBoxId || "Příjemce"),
265      subject: String(message.subject || "Datová zpráva"),
266      deliveredAt: String(message.deliveredAt || message.receivedAt || ""),
267      dueDate: "",
268      riskLevel: "",
269      priority: "",
270      sourceStatus: String(message.status || "Odesláno"),
271      assignedTo: "",
272      attachmentStatus: String(message.attachmentStatus || ""),
273      attachmentCount: Number(message.attachmentCount || 0) || (Array.isArray(message.attachments) ? message.attachments.length : 0),
274      isUnread: false,
275      target: "",
276      queueId: "done",
277      laneLabel: "Historie",
278      microstate: "sent",
279      microstateLabel: "Odesláno",
280      actionLabel: "Otevřít",
281      tone: "quiet",
282      readOnly: true,
283      persisted: true,
284      derivationVersion: DATA_BOX_PLUS_TRIAGE_DERIVATION_VERSION,
285      message
286    };
287  }
288  const queueId = dataBoxPlusTriageQueueId(message);
289  const presentation = dataBoxPlusTriagePresentation(message, options);
290  return {
291    id: String(message.id || ""),
292    mailboxId: String(message.mailboxId || ""),
293    mailboxLabel: String(mailbox?.name || mailbox?.company || "Schránka"),
294    senderName: String(message.senderName || "Datová schránka"),
295    recipientName: String(message.recipientName || message.recipientBoxId || ""),
296    subject: String(message.subject || "Datová zpráva"),
297    deliveredAt: String(message.deliveredAt || message.receivedAt || ""),
298    dueDate: String(message.dueDate || ""),
299    riskLevel: String(message.riskLevel || ""),
300    priority: String(message.priority || ""),
301    sourceStatus: String(message.status || ""),
302    assignedTo: String(message.assignedTo || ""),
303    attachmentStatus: String(message.attachmentStatus || ""),
304    attachmentCount: Number(message.attachmentCount || 0) || (Array.isArray(message.attachments) ? message.attachments.length : 0),
305    isUnread: includesAny(normalizeText([message.readStatus, message.status].filter(Boolean).join(" ")), ["neprect", "nova", "new", "unread"]),
306    target: dataBoxPlusTriageTarget(message),
307    queueId,
308    laneLabel: DATA_BOX_PLUS_TRIAGE_QUEUES.find((queue) => queue.id === queueId)?.label || "K vyřízení",
309    microstate: presentation.code,
310    microstateLabel: presentation.label,
311    actionLabel: presentation.actionLabel,
312    tone: presentation.tone,
313    readOnly: false,
314    persisted: true,
315    derivationVersion: DATA_BOX_PLUS_TRIAGE_DERIVATION_VERSION,
316    message
317  };
318}
319
320export function dataBoxPlusTriageSort(items = [], queueId = "todo") {
321  return [...items].sort((left, right) => {
322    if (queueId === "done" || queueId === "handed") {
323      return deliveredTime(right.message) - deliveredTime(left.message) || left.id.localeCompare(right.id, "cs");
324    }
325
326    const blockerRank = (item) => ({
327      attachment_problem: 0,
328      action_problem: 0,
329      needs_recipient: 1,
330      needs_vehicle: 1,
331      needs_classification: 1,
332      past_due: 2,
333      due_today: 3,
334      urgent: 4,
335      new: 5,
336      suggested_route: 6,
337      open: 7
338    }[item.microstate] ?? 8);
339    const rankDifference = blockerRank(left) - blockerRank(right);
340    if (rankDifference) return rankDifference;
341
342    const leftDue = isoDay(left.dueDate) || "9999-12-31";
343    const rightDue = isoDay(right.dueDate) || "9999-12-31";
344    if (leftDue !== rightDue) return leftDue.localeCompare(rightDue);
345
346    const leftRisk = includesAny(normalizeText([left.riskLevel, left.priority].join(" ")), ["vysoke", "kriticke", "urgent", "pravni", "legal"]) ? 0 : 1;
347    const rightRisk = includesAny(normalizeText([right.riskLevel, right.priority].join(" ")), ["vysoke", "kriticke", "urgent", "pravni", "legal"]) ? 0 : 1;
348    if (leftRisk !== rightRisk) return leftRisk - rightRisk;
349
350    return deliveredTime(left.message) - deliveredTime(right.message) || left.id.localeCompare(right.id, "cs");
351  });
352}
353
354export function dataBoxPlusTriageItems(messages = [], mailboxes = [], options = {}) {
355  const mailboxMap = new Map(mailboxes.map((mailbox) =>
355 [String(mailbox.id || ""), mailbox]));
356  const mailboxId = String(options.mailboxId || "");
357  const queueId = String(options.queueId || "");
358  const query = normalizeText(options.query);
359  const folder = String(options.folder || "received");
360  const today = options.today || pragueDay(options.now);
361
362  const items = messages
363    .filter((message) => {
364      const direction = normalizeText(message?.direction);
365      if (folder === "sent" && direction !== "sent") return false;
366      if (folder === "archive" && (direction === "sent" || dataBoxPlusTriageQueueId(message) !== "done")) return false;
367      if (folder === "received" && direction && direction !== "received") return false;
368      if (mailboxId && String(message?.mailboxId || "") !== mailboxId) return false;
369      return true;
370    })
371    .map((message) => dataBoxPlusTriageItem(message, { mailbox: mailboxMap.get(String(message.mailboxId || "")), today }))
372    .filter((item) => !queueId || item.queueId === queueId)
373    .filter((item) => {
374      if (!query) return true;
375      return normalizeText([
376        item.senderName,
377        item.recipientName,
378        item.subject,
379        item.mailboxLabel,
380        item.microstateLabel,
381        item.target,
382        item.sourceStatus,
383        item.riskLevel,
384        item.priority
385      ].join(" ")).includes(query);
386    });
387
388  return dataBoxPlusTriageSort(items, queueId || "todo");
389}
390
391export function dataBoxPlusTriageCounts(messages = [], mailboxes = [], options = {}) {
392  const counts = { todo: 0, handed: 0, done: 0 };
393  for (const item of dataBoxPlusTriageItems(messages, mailboxes, {
394    mailboxId: options.mailboxId,
395    folder: options.folder || "received",
396    today: options.today,
397    now: options.now
398  })) {
399    counts[item.queueId] += 1;
400  }
401  return counts;
402}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.