create-devoplus-one-app 0.0.0-stage → 0.1.0-next.158

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.
Files changed (122) hide show
  1. package/README.md +62 -2
  2. package/dist/cli.mjs +17 -0
  3. package/package.json +35 -3
  4. package/templates/0001_init.sql.tpl +10 -0
  5. package/templates/AGENTS.md.tpl +286 -0
  6. package/templates/AGENT_GUIDE.md.tpl +36 -0
  7. package/templates/README.md.tpl +58 -0
  8. package/templates/_shared/AGENTS.writes.md.tpl +38 -0
  9. package/templates/_shared/README.tail.md.tpl +20 -0
  10. package/templates/_shared/files/src/app.test.ts.tpl +36 -0
  11. package/templates/_shared/files/src/index.ts.tpl +8 -0
  12. package/templates/_shared/files/src/lib/client.ts.tpl +213 -0
  13. package/templates/_shared/files/src/lib/files.ts.tpl +37 -0
  14. package/templates/_shared/files/src/lib/http.ts.tpl +101 -0
  15. package/templates/_shared/files/src/lib/mail.ts.tpl +26 -0
  16. package/templates/_shared/files/src/lib/model.ts.tpl +31 -0
  17. package/templates/_shared/files/src/lib/page.ts.tpl +71 -0
  18. package/templates/_shared/files/src/lib/record.ts.tpl +246 -0
  19. package/templates/_shared/files/src/lib/server.ts.tpl +132 -0
  20. package/templates/_shared/files/src/shell.test.ts.tpl +56 -0
  21. package/templates/agent.ts.tpl +23 -0
  22. package/templates/bakim-takvimi/AGENTS.template.md.tpl +40 -0
  23. package/templates/bakim-takvimi/README.md.tpl +45 -0
  24. package/templates/bakim-takvimi/files/AGENT_GUIDE.md.tpl +52 -0
  25. package/templates/bakim-takvimi/files/one.data.json +45 -0
  26. package/templates/bakim-takvimi/files/src/agent.ts.tpl +22 -0
  27. package/templates/bakim-takvimi/files/src/app.ts.tpl +259 -0
  28. package/templates/bakim-takvimi/files/src/jobs.ts.tpl +170 -0
  29. package/templates/bakim-takvimi/files/src/names.ts.tpl +5 -0
  30. package/templates/bakim-takvimi/files/src/queries.ts.tpl +71 -0
  31. package/templates/bakim-takvimi/files/src/schedule.test.ts.tpl +58 -0
  32. package/templates/bakim-takvimi/files/src/schedule.ts.tpl +106 -0
  33. package/templates/bakim-takvimi/files/src/shell.ts.tpl +299 -0
  34. package/templates/bakim-takvimi/manifest.json.tpl +64 -0
  35. package/templates/egitim-sertifika/AGENTS.template.md.tpl +41 -0
  36. package/templates/egitim-sertifika/README.md.tpl +46 -0
  37. package/templates/egitim-sertifika/files/AGENT_GUIDE.md.tpl +53 -0
  38. package/templates/egitim-sertifika/files/one.data.json +43 -0
  39. package/templates/egitim-sertifika/files/src/agent.ts.tpl +22 -0
  40. package/templates/egitim-sertifika/files/src/app.ts.tpl +312 -0
  41. package/templates/egitim-sertifika/files/src/certificate.test.ts.tpl +83 -0
  42. package/templates/egitim-sertifika/files/src/certificate.ts.tpl +163 -0
  43. package/templates/egitim-sertifika/files/src/jobs.ts.tpl +219 -0
  44. package/templates/egitim-sertifika/files/src/names.ts.tpl +5 -0
  45. package/templates/egitim-sertifika/files/src/queries.ts.tpl +115 -0
  46. package/templates/egitim-sertifika/files/src/shell.ts.tpl +345 -0
  47. package/templates/egitim-sertifika/manifest.json.tpl +57 -0
  48. package/templates/envanter/AGENTS.template.md.tpl +28 -0
  49. package/templates/envanter/README.md.tpl +40 -0
  50. package/templates/envanter/files/AGENT_GUIDE.md.tpl +47 -0
  51. package/templates/envanter/files/one.data.json +62 -0
  52. package/templates/envanter/files/src/agent.ts.tpl +20 -0
  53. package/templates/envanter/files/src/app.ts.tpl +204 -0
  54. package/templates/envanter/files/src/jobs.ts.tpl +30 -0
  55. package/templates/envanter/files/src/queries.ts.tpl +53 -0
  56. package/templates/envanter/files/src/shell.ts.tpl +265 -0
  57. package/templates/envanter/files/src/stock.test.ts.tpl +31 -0
  58. package/templates/envanter/files/src/stock.ts.tpl +33 -0
  59. package/templates/envanter/manifest.json.tpl +56 -0
  60. package/templates/hedef-takibi/AGENTS.template.md.tpl +37 -0
  61. package/templates/hedef-takibi/README.md.tpl +52 -0
  62. package/templates/hedef-takibi/files/AGENT_GUIDE.md.tpl +48 -0
  63. package/templates/hedef-takibi/files/one.data.json +76 -0
  64. package/templates/hedef-takibi/files/src/agent.ts.tpl +26 -0
  65. package/templates/hedef-takibi/files/src/app.ts.tpl +740 -0
  66. package/templates/hedef-takibi/files/src/progress.test.ts.tpl +53 -0
  67. package/templates/hedef-takibi/files/src/progress.ts.tpl +81 -0
  68. package/templates/hedef-takibi/files/src/queries.ts.tpl +83 -0
  69. package/templates/hedef-takibi/files/src/shell.ts.tpl +455 -0
  70. package/templates/hedef-takibi/manifest.json.tpl +38 -0
  71. package/templates/index.ts.tpl +262 -0
  72. package/templates/markdown.d.ts.tpl +6 -0
  73. package/templates/musteri-ziyaret/AGENTS.template.md.tpl +47 -0
  74. package/templates/musteri-ziyaret/README.md.tpl +48 -0
  75. package/templates/musteri-ziyaret/files/AGENT_GUIDE.md.tpl +44 -0
  76. package/templates/musteri-ziyaret/files/one.data.json +53 -0
  77. package/templates/musteri-ziyaret/files/src/agent.ts.tpl +21 -0
  78. package/templates/musteri-ziyaret/files/src/app.ts.tpl +340 -0
  79. package/templates/musteri-ziyaret/files/src/customers.ts.tpl +85 -0
  80. package/templates/musteri-ziyaret/files/src/queries.ts.tpl +67 -0
  81. package/templates/musteri-ziyaret/files/src/shell.ts.tpl +272 -0
  82. package/templates/musteri-ziyaret/files/src/visit.test.ts.tpl +65 -0
  83. package/templates/musteri-ziyaret/files/src/visit.ts.tpl +98 -0
  84. package/templates/musteri-ziyaret/manifest.json.tpl +37 -0
  85. package/templates/saha-denetimi/AGENTS.template.md.tpl +32 -0
  86. package/templates/saha-denetimi/README.md.tpl +44 -0
  87. package/templates/saha-denetimi/files/AGENT_GUIDE.md.tpl +55 -0
  88. package/templates/saha-denetimi/files/one.data.json +87 -0
  89. package/templates/saha-denetimi/files/src/agent.ts.tpl +41 -0
  90. package/templates/saha-denetimi/files/src/app.ts.tpl +444 -0
  91. package/templates/saha-denetimi/files/src/queries.ts.tpl +143 -0
  92. package/templates/saha-denetimi/files/src/score.test.ts.tpl +48 -0
  93. package/templates/saha-denetimi/files/src/score.ts.tpl +102 -0
  94. package/templates/saha-denetimi/files/src/shell.ts.tpl +585 -0
  95. package/templates/saha-denetimi/manifest.json.tpl +49 -0
  96. package/templates/shell.test.ts.tpl +30 -0
  97. package/templates/shell.ts.tpl +201 -0
  98. package/templates/uygunsuzluk-dof/AGENTS.template.md.tpl +40 -0
  99. package/templates/uygunsuzluk-dof/README.md.tpl +46 -0
  100. package/templates/uygunsuzluk-dof/files/AGENT_GUIDE.md.tpl +56 -0
  101. package/templates/uygunsuzluk-dof/files/one.data.json +99 -0
  102. package/templates/uygunsuzluk-dof/files/src/agent.ts.tpl +45 -0
  103. package/templates/uygunsuzluk-dof/files/src/app.ts.tpl +431 -0
  104. package/templates/uygunsuzluk-dof/files/src/capa.test.ts.tpl +53 -0
  105. package/templates/uygunsuzluk-dof/files/src/capa.ts.tpl +114 -0
  106. package/templates/uygunsuzluk-dof/files/src/jobs.ts.tpl +127 -0
  107. package/templates/uygunsuzluk-dof/files/src/queries.ts.tpl +102 -0
  108. package/templates/uygunsuzluk-dof/files/src/shell.ts.tpl +441 -0
  109. package/templates/uygunsuzluk-dof/manifest.json.tpl +56 -0
  110. package/templates/ziyaretci-kaydi/AGENTS.template.md.tpl +35 -0
  111. package/templates/ziyaretci-kaydi/README.md.tpl +46 -0
  112. package/templates/ziyaretci-kaydi/files/AGENT_GUIDE.md.tpl +39 -0
  113. package/templates/ziyaretci-kaydi/files/one.data.json +42 -0
  114. package/templates/ziyaretci-kaydi/files/src/agent.ts.tpl +13 -0
  115. package/templates/ziyaretci-kaydi/files/src/app.ts.tpl +293 -0
  116. package/templates/ziyaretci-kaydi/files/src/jobs.ts.tpl +49 -0
  117. package/templates/ziyaretci-kaydi/files/src/notice.ts.tpl +48 -0
  118. package/templates/ziyaretci-kaydi/files/src/queries.ts.tpl +45 -0
  119. package/templates/ziyaretci-kaydi/files/src/shell.ts.tpl +252 -0
  120. package/templates/ziyaretci-kaydi/files/src/visitor.test.ts.tpl +34 -0
  121. package/templates/ziyaretci-kaydi/files/src/visitor.ts.tpl +57 -0
  122. package/templates/ziyaretci-kaydi/manifest.json.tpl +38 -0
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { STRINGS, normalizeLang, normalizeTheme, renderShell, renderShellScript } from './shell.ts'
3
+
4
+ describe('kabuk', () => {
5
+ it('iki dilin anahtar kümeleri aynıdır (yeni metin iki dile birden eklenir)', () => {
6
+ expect(Object.keys(STRINGS.en).sort()).toEqual(Object.keys(STRINGS.tr).sort())
7
+ })
8
+
9
+ it('sayfada satır içi betik yok; köprü ve sayfa betiği ayrı dosyadan yüklenir (platform satır içi betiği engeller)', () => {
10
+ const html = renderShell('tr', 'light')
11
+ expect(html).not.toMatch(/<script(?![^>]*\bsrc=)[^>]*>/)
12
+ expect(html).toContain('<script src="/one-bridge.js"></script>')
13
+ expect(html).toContain('<script src="/shell.js"></script>')
14
+ })
15
+
16
+ it('sayfa betiği Portal ile yalnız köprüden konuşur ve menüyü Portal\'a bildirir', () => {
17
+ const script = renderShellScript([{ path: '/', label: { tr: 'Ana sayfa', en: 'Home' } }])
18
+ expect(script).not.toMatch(/postMessage/)
19
+ expect(script).toContain('one.start({ navigation: NAVIGATION })')
20
+ expect(script).toContain('"path":"/"')
21
+ })
22
+
23
+ it('ilk çizim dil/tema sorgusunu uygular; bilinmeyen değer varsayılana düşer', () => {
24
+ expect(normalizeLang('en-US')).toBe('en')
25
+ expect(normalizeLang(null)).toBe('tr')
26
+ expect(normalizeTheme('DARK')).toBe('dark')
27
+ expect(normalizeTheme('x')).toBe('light')
28
+ expect(renderShell('en', 'dark')).toContain('<html lang="en" data-theme="dark">')
29
+ })
30
+ })
@@ -0,0 +1,201 @@
1
+ // Kabuk: HTML sayfası + ayrı betik dosyası (bundler yok). Satır içi betik platformda KAPALIDIR (CSP `script-src 'self'`):
2
+ // sayfa Devoplus One köprüsünü `/one-bridge.js`, kendi betiğini `/shell.js` adresinden yükler, görsel dili `/one-ui.css`'ten
3
+ // alır. Portal ile her konuşma `window.one` köprüsünden geçer (sözleşme: AGENTS.md "Portal köprüsü").
4
+ // src sorgusu parent=<portal origin[,…]> lang=<tr-TR|en-US|…> theme=<light|dark> (ilk yükleme)
5
+ // Portal → app belirteç · bağlam (tema, dil, Vekalet/Yerine Görüntüle, tasarım) · sekme gezinmesi · görünen alan
6
+ // app → Portal hazır · menü · yol · yükseklik · alt başlık · bildirim · onay · indirme · dış bağlantı · kaydedilmemiş değişiklik
7
+ // Menüyü (sekmeleri) ve kullanıcı/portal üst bilgisini Portal çizer: bu sayfa sol menü ya da kendi üst bilgisini ÇİZMEZ.
8
+ // Bildirim ve onay penceresi de Portal'dadır (`one.toast`, `one.confirm`): sayfa uzunsa `position: fixed` ekranın dışında kalır.
9
+ // ⛔ Kök öğelerde 100vh / height:100% kullanmayın: içerik iframe boyuna bağlanırsa yükseklik hiç küçülmez.
10
+ // Belirteç yalnız Portal'dan gelir; bu sayfa kendi başına oturum açamaz. Tema/dil değişince iframe YENİDEN YÜKLENMEZ;
11
+ // kabuk metinleri yerinde değiştirir.
12
+ import type { OneNavigationItem } from '@devoplus/one-sdk'
13
+
14
+ export type Lang = 'tr' | 'en'
15
+ export type Theme = 'light' | 'dark'
16
+
17
+ export function normalizeLang(raw: string | null | undefined): Lang {
18
+ return (raw ?? '').trim().toLowerCase().startsWith('en') ? 'en' : 'tr'
19
+ }
20
+
21
+ export function normalizeTheme(raw: string | null | undefined): Theme {
22
+ return (raw ?? '').trim().toLowerCase() === 'dark' ? 'dark' : 'light'
23
+ }
24
+
25
+ /** Kullanıcıya görünen metinler; iki dil de sayfaya gömülür ve one:context ile yerinde değişir. Yeni metin İKİ dile birden eklenir (test anahtar kümelerini karşılaştırır). */
26
+ export const STRINGS: Record<Lang, Record<string, string>> = {
27
+ tr: {
28
+ title: {{nameTrJs}},
29
+ waiting: "Portal'dan oturum bekleniyor…",
30
+ ready: 'Oturum alındı.',
31
+ networkError: 'İstek gönderilemedi (ağ hatası).',
32
+ me: 'Kullanıcı bilgim',
33
+ notes: 'Notlarım (bu kurulumun veritabanı)',
34
+ notePlaceholder: 'Yeni not…',
35
+ add: 'Ekle',
36
+ refresh: 'Yenile',
37
+ remove: 'Sil',
38
+ empty: 'Henüz not yok.',
39
+ added: 'Not eklendi.',
40
+ removed: 'Not silindi.',
41
+ removeTitle: 'Notu sil',
42
+ removeConfirm: 'Bu not silinsin mi? Bu işlem geri alınamaz.',
43
+ readOnly: 'Vekalet ya da Yerine Görüntüle oturumundasınız: bu uygulamada yalnız görüntüleme yapabilirsiniz.',
44
+ output: 'Son yanıt',
45
+ },
46
+ en: {
47
+ title: {{nameEnJs}},
48
+ waiting: 'Waiting for the session from the Portal…',
49
+ ready: 'Session received.',
50
+ networkError: 'Request could not be sent (network error).',
51
+ me: 'My user info',
52
+ notes: 'My notes (this installation’s database)',
53
+ notePlaceholder: 'New note…',
54
+ add: 'Add',
55
+ refresh: 'Refresh',
56
+ remove: 'Delete',
57
+ empty: 'No notes yet.',
58
+ added: 'Note added.',
59
+ removed: 'Note deleted.',
60
+ removeTitle: 'Delete note',
61
+ removeConfirm: 'Delete this note? This cannot be undone.',
62
+ readOnly: 'You are in a Delegation or View as User session: you can only view this app.',
63
+ output: 'Last response',
64
+ },
65
+ }
66
+
67
+ function escapeHtml(value: string): string {
68
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
69
+ }
70
+
71
+ /** İlk çizim `lang`/`theme` sorgusuyla yapılır; sonraki değişimler `/shell.js`'te one:context ile uygulanır. */
72
+ export function renderShell(lang: Lang, theme: Theme): string {
73
+ const s = STRINGS[lang]
74
+ const tx = (key: string): string => s[key] ?? STRINGS.tr[key] ?? key
75
+
76
+ return `<!doctype html>
77
+ <html lang="${lang}" data-theme="${theme}"><head><meta charset="utf-8"><title>${escapeHtml(tx('title'))}</title>
78
+ <meta name="viewport" content="width=device-width, initial-scale=1">
79
+ <link rel="stylesheet" href="/one-ui.css">
80
+ <style>
81
+ #noteText{flex:1 1 16rem;width:auto}
82
+ .notes{list-style:none;padding:0;margin:8px 0}.notes li{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--one-border)}
83
+ pre{background:var(--one-surface);border:1px solid var(--one-border);padding:12px;border-radius:var(--one-radius);overflow:auto;max-height:24rem;white-space:pre-wrap;word-break:break-word}
84
+ </style></head><body>
85
+ <p id="status" class="one-muted">${escapeHtml(tx('waiting'))}</p>
86
+ <div id="readOnly" class="one-message one-message-info" data-i18n="readOnly" hidden>${escapeHtml(tx('readOnly'))}</div>
87
+ <div class="one-row"><button type="button" class="one-btn" data-act="me" data-i18n="me">${escapeHtml(tx('me'))}</button></div>
88
+ <h2 data-i18n="notes">${escapeHtml(tx('notes'))}</h2>
89
+ <div class="one-row"><input type="text" class="one-input" id="noteText" maxlength="500" data-i18n-ph="notePlaceholder" placeholder="${escapeHtml(tx('notePlaceholder'))}"><button type="button" class="one-btn one-btn-primary" data-act="add" data-i18n="add">${escapeHtml(tx('add'))}</button><button type="button" class="one-btn" data-act="refresh" data-i18n="refresh">${escapeHtml(tx('refresh'))}</button></div>
90
+ <ul id="notes" class="notes"></ul>
91
+ <h2 data-i18n="output">${escapeHtml(tx('output'))}</h2>
92
+ <pre id="out"></pre>
93
+ <script src="/one-bridge.js"></script>
94
+ <script src="/shell.js"></script>
95
+ </body></html>`
96
+ }
97
+
98
+ /**
99
+ * `/shell.js` — sayfanın betiği. Metinler ve menü sunucuda gömülür (JSON); ilk dil/tema `<html>` niteliklerinden okunur.
100
+ * Yeni bir görünüm (ör. `/reports`) eklerken: manifest `navigation`'a yolu yazın, `one.onNavigate` ile görünümü değiştirin,
101
+ * kendi geçişlerinizde `one.setRoute(yol)` çağırın. Sol menü ya da üst bilgi ÇİZMEYİN — sekmeleri Portal çizer.
102
+ */
103
+ export function renderShellScript(
104
+ navigation: readonly OneNavigationItem[],
105
+ options: { actingWrites?: boolean } = {},
106
+ ): string {
107
+ // JSON bir betik dosyasına gömülüyor; "</" dizisi kaçırılır.
108
+ const json = (value: unknown): string => JSON.stringify(value).replace(/<\//g, '<\\/')
109
+ return `(function () {
110
+ var one = window.one;
111
+ var S = ${json(STRINGS)};
112
+ var NAVIGATION = ${json(navigation)};
113
+ // manifest actingSessions: "write" ise Vekalet / Yerine Görüntüle oturumunda da yazılır → salt-okunur uyarısı gösterilmez.
114
+ var ACTING_WRITES = ${json(options.actingWrites === true)};
115
+ var root = document.documentElement;
116
+ var lang = root.lang === 'en' ? 'en' : 'tr', theme = root.getAttribute('data-theme') === 'dark' ? 'dark' : 'light';
117
+ // Durum satırı bir metin ANAHTARI taşır ve one:context gelince o anahtar yeni dile çevrilir. Metni doğrudan
118
+ // textContent'e yazmayın: dil değişiminde eski dilde kalır ya da başka bir durumla (ör. ağ hatası → "hazır") ezilir.
119
+ var statusKey = 'waiting', hadToken = false;
120
+ function $(id) { return document.getElementById(id); }
121
+ function t(k) { return (S[lang] && S[lang][k]) || S.tr[k] || k; }
122
+ function showStatus(key) { statusKey = key; $('status').textContent = t(key); }
123
+ function apply(nextLang, nextTheme) {
124
+ lang = String(nextLang || '').toLowerCase().indexOf('en') === 0 ? 'en' : 'tr';
125
+ theme = nextTheme === 'dark' ? 'dark' : 'light';
126
+ document.documentElement.lang = lang; document.documentElement.setAttribute('data-theme', theme);
127
+ var nodes = document.querySelectorAll('[data-i18n]');
128
+ for (var i = 0; i < nodes.length; i++) nodes[i].textContent = t(nodes[i].getAttribute('data-i18n'));
129
+ var phs = document.querySelectorAll('[data-i18n-ph]');
130
+ for (var j = 0; j < phs.length; j++) phs[j].setAttribute('placeholder', t(phs[j].getAttribute('data-i18n-ph')));
131
+ showStatus(statusKey);
132
+ }
133
+ // Her istek Portal'dan gelen belirteci taşır (one.fetch); belirteç henüz yoksa istek yine gider, uygulama 401 döner.
134
+ function call(method, path, body) {
135
+ var headers = {};
136
+ if (body !== undefined) headers['content-type'] = 'application/json';
137
+ return one.fetch(path, { method: method, headers: headers, body: body === undefined ? undefined : JSON.stringify(body) })
138
+ .then(function (r) {
139
+ return r.text().then(function (text) {
140
+ var data = text;
141
+ try { data = JSON.parse(text); } catch (e) {}
142
+ $('out').textContent = JSON.stringify({ status: r.status, body: data }, null, 2);
143
+ // Yanıt geldiyse önceki ağ hatası artık geçerli değildir; durum satırı oturum durumuna döner.
144
+ if (statusKey === 'networkError') showStatus(one.token ? 'ready' : 'waiting');
145
+ return { ok: r.ok, body: data };
146
+ });
147
+ })
148
+ .catch(function (e) { $('out').textContent = String(e); showStatus('networkError'); return { ok: false, body: null }; });
149
+ }
150
+ function renderNotes(notes) {
151
+ var ul = $('notes'); ul.textContent = '';
152
+ // Sonradan eklenen metinler de data-i18n taşır ki one:context gelince yeni dile çevrilsin.
153
+ if (!notes || !notes.length) { var empty = document.createElement('li'); empty.setAttribute('data-i18n', 'empty'); empty.textContent = t('empty'); ul.appendChild(empty); return; }
154
+ for (var i = 0; i < notes.length; i++) (function (n) {
155
+ // Metin textContent ile yazılır: not içeriği kullanıcı girdisidir, HTML olarak YORUMLANMAZ.
156
+ var li = document.createElement('li'); var span = document.createElement('span'); span.textContent = n.text;
157
+ var del = document.createElement('button'); del.type = 'button'; del.setAttribute('class', 'one-btn'); del.setAttribute('data-i18n', 'remove'); del.textContent = t('remove');
158
+ del.addEventListener('click', function () { remove(n.id); });
159
+ li.appendChild(span); li.appendChild(del); ul.appendChild(li);
160
+ })(notes[i]);
161
+ }
162
+ function refresh() { return call('GET', '/api/notes').then(function (r) { if (r.ok) renderNotes(r.body && r.body.notes); }); }
163
+ // Onay penceresini Portal çizer: sayfa uzunsa sayfanın kendi penceresi ekranın dışında kalırdı.
164
+ function remove(id) {
165
+ return one.confirm({ title: t('removeTitle'), message: t('removeConfirm'), severity: 'danger', acceptLabel: t('remove') }).then(function (ok) {
166
+ if (!ok) return;
167
+ return call('DELETE', '/api/notes/' + encodeURIComponent(id)).then(function (r) {
168
+ if (r.ok) one.toast({ severity: 'success', summary: t('removed') });
169
+ return refresh();
170
+ });
171
+ });
172
+ }
173
+ var ACTIONS = {
174
+ me: function () { return call('GET', '/api/me'); },
175
+ refresh: refresh,
176
+ add: function () {
177
+ var text = ($('noteText').value || '').trim();
178
+ if (!text) return;
179
+ return call('POST', '/api/notes', { text: text }).then(function (r) {
180
+ if (!r.ok) return;
181
+ $('noteText').value = ''; one.setDirty(false);
182
+ one.toast({ severity: 'success', summary: t('added') });
183
+ return refresh();
184
+ });
185
+ }
186
+ };
187
+ var buttons = document.querySelectorAll('[data-act]');
188
+ for (var b = 0; b < buttons.length; b++) (function (btn) { btn.addEventListener('click', function () { ACTIONS[btn.getAttribute('data-act')](); }); })(buttons[b]);
189
+ // Yazılmış ama eklenmemiş not "kaydedilmemiş değişiklik"tir: Portal kullanıcı ayrılmadan önce sorar.
190
+ $('noteText').addEventListener('input', function () { one.setDirty(($('noteText').value || '').trim() !== ''); });
191
+ one.onToken(function () { var first = !hadToken; hadToken = true; showStatus('ready'); if (first) refresh(); });
192
+ one.onContext(function (c) {
193
+ apply(c.lang, c.theme);
194
+ // Vekalet / Yerine Görüntüle oturumunda platform yazma isteklerini reddeder; kullanıcıya baştan söylenir.
195
+ if (c.actor && !ACTING_WRITES) $('readOnly').removeAttribute('hidden'); else $('readOnly').setAttribute('hidden', '');
196
+ });
197
+ apply(lang, theme);
198
+ one.start({ navigation: NAVIGATION });
199
+ })();
200
+ `
201
+ }
@@ -0,0 +1,40 @@
1
+ ## Bu uygulamanın şablonu: uygunsuzluk ve düzeltici faaliyet (`uygunsuzluk-dof`)
2
+
3
+ Uygulama `create-devoplus-one-app --template uygunsuzluk-dof` ile üretildi. Kullanıcının ihtiyacını bu şablona göre
4
+ DEĞİŞTİRİN; baştan yazmayın. Dosya haritası:
5
+
6
+ | Dosya | Ne |
7
+ |---|---|
8
+ | `one.data.json` | Veri modeli: `nonconformities` (kayıt) + `actions` (düzeltici faaliyet) + `reminder_runs` (hatırlatma turu). Değişince `pnpm data:generate` |
9
+ | `src/app.ts` | Uçlar: bildirim, kayıt düzenleme, durum akışı, faaliyet ekle/değiştir/sil/tamamla, faaliyetlerim, özet |
10
+ | `src/capa.ts` | Saf kurallar: geçiş tablosu ve koşulları, termin doğrulama, gecikme, hatırlatma metni + `capa.test.ts` |
11
+ | `src/jobs.ts` | Sabah işi `overdue-reminders` (geciken faaliyet e-postası; uygulama kimliğiyle, günde bir kez) |
12
+ | `src/queries.ts` | Ekran, iş ve yapay zekâ araçlarının ortak sorguları |
13
+ | `src/shell.ts` | Sayfa: Faaliyetlerim · Uygunsuzluk bildir · Kayıtlar + kayıt paneli |
14
+ | `src/agent.ts` · `AGENT_GUIDE.md` | Devoplus yapay zekâsının araçları ve rehberi |
15
+ | `src/lib/*` | Şablonların ortak parçaları — gerekmedikçe değiştirmeyin |
16
+
17
+ Değişmezler (bozmayın):
18
+
19
+ - **Durum YALNIZ `/status` ucundan ve yalnız `capa.ts` geçiş tablosuyla değişir.** Bildirim ve düzenleme uçları
20
+ `status`, `closed_*` alanlarını istemciden ALMAZ (400). Geçişin koşulu (kök neden + faaliyet, bütün faaliyetler
21
+ tamamlandı) güncelleme ifadesinin İÇİNDEDİR; "önce say, sonra yaz"a çevirmeyin — araya giren bir faaliyet eklemesi
22
+ kapanışı geçersiz kılamazdı. Doğrulamada etkisiz bulunan kayıt (`verification_failed_at`) yeniden doğrulamaya ancak o
23
+ andan SONRA eklenmiş bir faaliyetle geçer; bu koşul da aynı ifadededir.
24
+ - **Kapanmış kayıt değişmez:** düzenleme hazır kayıt ucuna iletilmez; `lib/record.ts` ile doğrulanıp
25
+ `… WHERE status <> 'closed'` koşullu tek ifadeyle yazılır (eşleşmezse 409). Durumu önce okuyup sonra yazmayın.
26
+ - **Bütün geçişler ve faaliyet yönetimi yöneticinindir** (`…Manage`, 403). Faaliyeti sorumlusu ya da yönetici
27
+ tamamlar; başkasının faaliyeti "bulunamadı" (404) döner — yönetici olmayan kişi için "hâlâ sorumlusu" koşulu tamamlama
28
+ ifadesindedir. Faaliyet yalnız "açık" ve "faaliyet planlandı" durumundaki kayıtta eklenir, değişir, silinir ve
29
+ tamamlanır (409). Faaliyet kaydın başlığının kopyasını taşır: başlık düzenlemesi kopyaları aynı toplu yazımda günceller.
30
+ - **Görünürlük:** bütün kayıt ve faaliyet listeleri yöneticiye açıktır; diğer kişi yalnız kendi bildirdiği kaydı,
31
+ sorumlusu olduğu faaliyeti ve o faaliyetin kaydını görür. "Faaliyetlerim" listesinde sorumlu süzgecini SUNUCU yazar.
32
+ - **Kişi kimlikleri:** faaliyet sorumlusu `owner_org_id` (Organization Schema kimliği → `toOrgUserIds`). Oturum
33
+ kimliği (`sub`) ile karıştırmayın; "benim faaliyetlerim" `getMyOrgContext` ile çözülür.
34
+ - **Hatırlatma günde bir kez:** faaliyet o günün anahtarıyla koşullu damgalanır (`reminded_on`); e-posta yalnız
35
+ damgalananlar için gider, gönderim başarısızsa damga önceki değerine döner. Sorumlu ya da termin değişince damga
36
+ sıfırlanır. İş uygulama kimliğiyle gönderir (manifest `jobs.services: ["mail.execute"]` + kurulumda yönetici onayı);
37
+ onaysız servis 403 döner ve tur "onay gerekiyor" olarak kaydedilir. Bir turda en çok `REMINDER_OWNERS_PER_RUN` kişi;
38
+ hatırlatması ulaşmayan sorumlu (`reminder_failed_on`) sıranın sonuna geçer, ötekileri hatırlatmasız bırakmaz.
39
+ - Bu bir onay akışı DEĞİLDİR (kurumun İş Akışı modülü vardır); onaylayan zinciri eklemeyin. Telefon numarası alanı
40
+ EKLEMEYİN.
@@ -0,0 +1,46 @@
1
+ # {{nameTr}}
2
+
3
+ Kalite ve iş güvenliği uygunsuzluklarını ve onlar için yapılan düzeltici faaliyetleri (DÖF) izleyen bir kayıt
4
+ uygulaması. Devoplus Portal'ın içinde, Portal menüsünden açılır. Bu uygulama Devoplus One'ın **uygunsuzluk ve
5
+ düzeltici faaliyet** şablonundan üretildi; ihtiyacınıza göre değiştirilecek bir başlangıç noktasıdır.
6
+
7
+ ## Ne yapar
8
+
9
+ 1. **Uygunsuzluk bildir:** herkes gördüğü bir uygunsuzluğu kaynağı (denetim, müşteri şikâyeti, iç tespit, iş
10
+ güvenliği), önemi, açıklaması ve isterse bir dosya ekiyle bildirir. Bildirdiği kayıtları aynı sekmede görür.
11
+ 2. **Kayıtlar:** kalite ekibi bütün kayıtları görür, kök nedeni yazar ve sorumlusu ile termini olan düzeltici
12
+ faaliyetler açar. Sorumluya e-posta gider. Kayıtlar ve faaliyetler Excel'e aktarılabilir.
13
+ 3. **Durum akışı:** kayıt "açık → faaliyet planlandı → doğrulama → kapandı" sırasıyla ilerler. Faaliyet planlamak için
14
+ kök neden ve en az bir faaliyet gerekir; doğrulamaya geçmek ve kapatmak için bütün faaliyetler tamamlanmış olmalıdır.
15
+ Doğrulamada faaliyet yetersiz bulunursa kayıt yeniden "faaliyet planlandı"ya alınır; tekrar doğrulamaya geçmek için
16
+ yeni bir faaliyet eklenip tamamlanmalıdır.
17
+ 4. **Faaliyetlerim:** faaliyetin sorumlusu kendi faaliyetlerini görür ve ne yaptığını yazarak tamamlar.
18
+ 5. **Hatırlatma:** termini geçen faaliyet her sabah 08:00'de sorumlusuna e-postayla hatırlatılır; aynı gün ikinci kez
19
+ gönderilmez. Bir sabahta en çok 8 kişiye gider, kalanlar ertesi sabah gelir.
20
+ 6. **Devoplus asistanı:** "Geciken faaliyetler hangileri?" gibi sorular Devoplus'un yapay zekâ asistanına
21
+ sorulduğunda cevap bu uygulamadan gelir.
22
+
23
+ Bu uygulama bir onay akışı değildir: onay gereken işler kurumunuzun İş Akışı modülünde kalır.
24
+
25
+ ## Kimler ne görür
26
+
27
+ | Kim | Ne yapabilir |
28
+ |---|---|
29
+ | Uygulamayı kullanma izni olan herkes ({{permissionCode}}) | Uygunsuzluk bildirir; kendi bildirdiklerini ve sorumlusu olduğu faaliyetleri görür, faaliyetini tamamlar |
30
+ | Kalite ekibi ({{permissionPrefix}}Manage) | Bütün kayıtları görür, kök neden yazar, faaliyet açar ve değiştirir, durumu ilerletir ve kaydı kapatır |
31
+
32
+ İzinleri portal yöneticisi **Yetki Yönetimi** ekranından verir.
33
+
34
+ ## Neleri değiştirebilirsiniz
35
+
36
+ Yapay zekâ aracınıza iş diliyle söylemeniz yeterlidir:
37
+
38
+ - "Kaynaklara *tedarikçi* de eklensin" — yeni seçenek.
39
+ - "Kritik uygunsuzluk bildirilince kalite müdürüne hemen e-posta gitsin" — yeni bildirim.
40
+ - "Faaliyet tamamlanırken kanıt fotoğrafı istensin" — yeni alan.
41
+ - "Hatırlatma termin gününden iki gün önce de gelsin" — hatırlatma kuralı.
42
+
43
+ Bilinmesi gerekenler: faaliyet açıldığında sorumluya giden e-posta, işlemi yapan kişinin Portal'daki **"İzinleri
44
+ incele"** düğmesiyle e-posta izni vermiş olmasını gerektirir. Sabah hatırlatması kişi adına değil uygulama adına gider;
45
+ bunun için portal yöneticisinin uygulama kurulumunda e-posta servisini onaylaması gerekir. Onay yoksa durum Kayıtlar
46
+ sekmesinde yazar.
@@ -0,0 +1,56 @@
1
+ # {{nameTr}} — Devoplus yapay zekâsı için rehber
2
+
3
+ Bu dosyayı **Devoplus yapay zekâsı** okur (uygulama adresinde `/.well-known/one-agent.md`). Bir aracı eklerken,
4
+ değiştirirken ya da kaldırırken bu dosyayı AYNI değişiklikte güncelleyin; yayın kapısı dört başlığı ve her aracın adını
5
+ arar.
6
+
7
+ ## Uygulama ne yapar
8
+
9
+ {{nameTr}}, kalite ve iş güvenliği uygunsuzluklarını ve onlar için açılan düzeltici faaliyetleri kaydeder. Herkes bir
10
+ uygunsuzluk bildirebilir (denetim, müşteri şikâyeti, iç tespit ya da iş güvenliği). Kalite ekibi kök nedeni yazar,
11
+ sorumlusu ve termini olan düzeltici faaliyetler açar, kaydı "açık → faaliyet planlandı → doğrulama → kapandı" sırasıyla
12
+ ilerletir. Kayıt yalnız bütün faaliyetler tamamlanınca kapanır. Faaliyetin sorumlusu ne yaptığını yazarak faaliyeti
13
+ tamamlar. Termini geçen faaliyet her sabah sorumlusuna e-postayla hatırlatılır (günde en çok bir kez). Kullanıcı
14
+ "geciken faaliyetler hangileri", "kaç açık uygunsuzluk var", "Ahmet'in geciken işi var mı" gibi şeyler sorduğunda bu
15
+ uygulamaya başvurun. Bu bir onay akışı değildir; kayıt açan, durum değiştiren ya da faaliyet tamamlayan araç yoktur.
16
+
17
+ ## Veri
18
+
19
+ - **Uygunsuzluk** (`nonconformities`): konu, kaynak (`audit` denetim · `customer_complaint` müşteri şikâyeti ·
20
+ `internal` iç tespit · `safety` iş güvenliği), önem (`low` · `medium` · `high` · `critical`), açıklama, yer, tespit
21
+ tarihi, bildiren, kök neden, durum (`open` açık · `planned` faaliyet planlandı · `verification` doğrulama · `closed`
22
+ kapandı), kapanış zamanı ve notu.
23
+ - **Düzeltici faaliyet** (`actions`): bağlı uygunsuzluk, faaliyet metni, sorumlu kişinin adı, termin (gün), durum
24
+ (`open` açık · `done` tamamlandı), ne yapıldığı.
25
+ - Faaliyet, termininden SONRAKİ gün "gecikmiş" sayılır. Kapanmış kayıtlardaki faaliyetler gecikmiş sayılmaz.
26
+ - **Hatırlatma turu** (`reminder_runs`): her sabahki e-posta turunun özeti; e-posta servisi kurulumda onaylanmamışsa
27
+ "onay gerekiyor" olarak kaydedilir.
28
+
29
+ ## Araçlar
30
+
31
+ ### `overdue-actions` — geciken faaliyetler (salt okunur)
32
+
33
+ - **Ne zaman:** kullanıcı termini geçmiş faaliyetleri, kimin işinin geciktiğini ya da neyin acil olduğunu sorduğunda.
34
+ - **Girdi:** `owner` (isteğe bağlı) — sorumlunun adında geçen ifade; `limit` (1-50, varsayılan 20).
35
+ - **Çıktı:** `{ "total": 7, "actions": [{ "action", "owner", "dueDate", "daysOverdue", "nonconformity", "severity" }] }`
36
+ — en çok geciken önce; `total` süzgeçsiz toplam sayıdır.
37
+ - **Dikkat:** adda büyük/küçük harf eşleşmesi yalnız Latin harflerde yok sayılır; Türkçe harfli bir aramada sonuç
38
+ çıkmazsa ifadeyi küçük harfle yeniden deneyin. Yalnız yöneticiler kullanabilir.
39
+
40
+ ### `nonconformity-summary` — uygunsuzluk özeti (salt okunur)
41
+
42
+ - **Ne zaman:** kullanıcı kaç kaydın açık olduğunu, önem ya da kaynağa göre dağılımı veya hatırlatmaların gidip
43
+ gitmediğini sorduğunda.
44
+ - **Girdi:** yok.
45
+ - **Çıktı:** `{ "byStatus": { "open": 3, … }, "openBySeverity": { "high": 1, … }, "openBySource": { "safety": 2, … },
46
+ "overdueActions": 4, "lastReminderRun": { "day", "ownersReminded", "actionsReminded", "failed", "deferred",
47
+ "approvalNeeded" } }` — hiç tur yapılmadıysa `lastReminderRun` `null`. `approvalNeeded: true` ise e-posta
48
+ gönderilemedi: portal yöneticisinin kurulumda arka plan işi e-posta onayını vermesi gerekir.
49
+
50
+ ## Örnek istekler
51
+
52
+ - "Geciken düzeltici faaliyetler hangileri?" → `overdue-actions`
53
+ - "Ahmet'in geciken işi var mı?" → `overdue-actions` (`owner: "ahmet"`)
54
+ - "Kaç açık uygunsuzluk var, kritik olan var mı?" → `nonconformity-summary` → `byStatus`, `openBySeverity`
55
+ - "Hatırlatma e-postaları gidiyor mu?" → `nonconformity-summary` → `lastReminderRun`
56
+ - "Depodaki yangın tüpü için kayıt aç." → araç yok; kullanıcıyı uygulamanın Uygunsuzluk bildir sekmesine yönlendirin.
@@ -0,0 +1,99 @@
1
+ {
2
+ "version": 1,
3
+ "entities": {
4
+ "nonconformities": {
5
+ "title": { "tr": "Uygunsuzluklar", "en": "Nonconformities" },
6
+ "fields": {
7
+ "title": { "type": "text", "title": { "tr": "Konu", "en": "Subject" }, "required": true, "maxLength": 120 },
8
+ "source": {
9
+ "type": "choice",
10
+ "title": { "tr": "Kaynak", "en": "Source" },
11
+ "required": true,
12
+ "default": "internal",
13
+ "index": true,
14
+ "choices": [
15
+ { "value": "audit", "title": { "tr": "Denetim", "en": "Audit" } },
16
+ { "value": "customer_complaint", "title": { "tr": "Müşteri şikâyeti", "en": "Customer complaint" } },
17
+ { "value": "internal", "title": { "tr": "İç tespit", "en": "Internal finding" } },
18
+ { "value": "safety", "title": { "tr": "İş güvenliği", "en": "Occupational safety" } }
19
+ ]
20
+ },
21
+ "severity": {
22
+ "type": "choice",
23
+ "title": { "tr": "Önem", "en": "Severity" },
24
+ "required": true,
25
+ "default": "medium",
26
+ "index": true,
27
+ "choices": [
28
+ { "value": "low", "title": { "tr": "Düşük", "en": "Low" } },
29
+ { "value": "medium", "title": { "tr": "Orta", "en": "Medium" } },
30
+ { "value": "high", "title": { "tr": "Yüksek", "en": "High" } },
31
+ { "value": "critical", "title": { "tr": "Kritik", "en": "Critical" } }
32
+ ]
33
+ },
34
+ "description": { "type": "longText", "title": { "tr": "Açıklama", "en": "Description" }, "required": true, "maxLength": 4000 },
35
+ "location": { "type": "text", "title": { "tr": "Yer", "en": "Location" }, "maxLength": 120 },
36
+ "detected_on": { "type": "date", "title": { "tr": "Tespit tarihi", "en": "Detected on" }, "index": true },
37
+ "reporter_name": { "type": "text", "title": { "tr": "Bildiren", "en": "Reported by" }, "maxLength": 120 },
38
+ "root_cause": { "type": "longText", "title": { "tr": "Kök neden", "en": "Root cause" }, "maxLength": 4000 },
39
+ "status": {
40
+ "type": "choice",
41
+ "title": { "tr": "Durum", "en": "Status" },
42
+ "required": true,
43
+ "default": "open",
44
+ "index": true,
45
+ "choices": [
46
+ { "value": "open", "title": { "tr": "Açık", "en": "Open" } },
47
+ { "value": "planned", "title": { "tr": "Faaliyet planlandı", "en": "Actions planned" } },
48
+ { "value": "verification", "title": { "tr": "Doğrulama", "en": "Verification" } },
49
+ { "value": "closed", "title": { "tr": "Kapandı", "en": "Closed" } }
50
+ ]
51
+ },
52
+ "attachment": { "type": "file", "title": { "tr": "Dosya eki", "en": "Attachment" } },
53
+ "attachment_name": { "type": "text", "title": { "tr": "Ek dosya adı", "en": "Attachment name" }, "maxLength": 200 },
54
+ "closed_at": { "type": "datetime", "title": { "tr": "Kapanma zamanı", "en": "Closed at" } },
55
+ "closed_by_name": { "type": "text", "title": { "tr": "Kapatan", "en": "Closed by" }, "maxLength": 120 },
56
+ "closure_note": { "type": "longText", "title": { "tr": "Kapanış notu", "en": "Closing note" }, "maxLength": 2000 },
57
+ "verification_failed_at": { "type": "datetime", "title": { "tr": "Doğrulamada etkisiz bulundu", "en": "Found ineffective at" } }
58
+ }
59
+ },
60
+ "actions": {
61
+ "title": { "tr": "Düzeltici faaliyetler", "en": "Corrective actions" },
62
+ "fields": {
63
+ "nonconformity_id": { "type": "text", "title": { "tr": "Uygunsuzluk (kimlik)", "en": "Nonconformity (id)" }, "required": true, "maxLength": 36, "index": true },
64
+ "nonconformity_title": { "type": "text", "title": { "tr": "Uygunsuzluk", "en": "Nonconformity" }, "maxLength": 120 },
65
+ "description": { "type": "longText", "title": { "tr": "Faaliyet", "en": "Action" }, "required": true, "maxLength": 2000 },
66
+ "owner_org_id": { "type": "text", "title": { "tr": "Sorumlu (kimlik)", "en": "Owner (id)" }, "required": true, "maxLength": 24, "index": true },
67
+ "owner_name": { "type": "text", "title": { "tr": "Sorumlu", "en": "Owner" }, "maxLength": 120 },
68
+ "due_date": { "type": "date", "title": { "tr": "Termin", "en": "Due date" }, "required": true, "index": true },
69
+ "status": {
70
+ "type": "choice",
71
+ "title": { "tr": "Durum", "en": "Status" },
72
+ "required": true,
73
+ "default": "open",
74
+ "index": true,
75
+ "choices": [
76
+ { "value": "open", "title": { "tr": "Açık", "en": "Open" } },
77
+ { "value": "done", "title": { "tr": "Tamamlandı", "en": "Done" } }
78
+ ]
79
+ },
80
+ "done_at": { "type": "datetime", "title": { "tr": "Tamamlanma", "en": "Done at" } },
81
+ "done_note": { "type": "text", "title": { "tr": "Yapılan", "en": "What was done" }, "maxLength": 500 },
82
+ "done_by_name": { "type": "text", "title": { "tr": "Tamamlayan", "en": "Done by" }, "maxLength": 120 },
83
+ "reminded_on": { "type": "text", "title": { "tr": "Son hatırlatma günü", "en": "Last reminder day" }, "maxLength": 10 },
84
+ "reminder_failed_on": { "type": "date", "title": { "tr": "Hatırlatmanın ulaşmadığı son gün", "en": "Last undelivered reminder" } }
85
+ }
86
+ },
87
+ "reminder_runs": {
88
+ "title": { "tr": "Geciken faaliyet hatırlatmaları", "en": "Overdue action reminders" },
89
+ "fields": {
90
+ "run_key": { "type": "text", "title": { "tr": "Gün", "en": "Day" }, "required": true, "unique": true, "maxLength": 20 },
91
+ "reminded_owners": { "type": "number", "title": { "tr": "Hatırlatılan kişi", "en": "Owners reminded" }, "required": true, "default": 0, "min": 0 },
92
+ "reminded_actions": { "type": "number", "title": { "tr": "Hatırlatılan faaliyet", "en": "Actions reminded" }, "required": true, "default": 0, "min": 0 },
93
+ "failed": { "type": "number", "title": { "tr": "Gönderilemeyen", "en": "Failed" }, "required": true, "default": 0, "min": 0 },
94
+ "deferred_owners": { "type": "number", "title": { "tr": "Sonraki güne kalan", "en": "Deferred" }, "required": true, "default": 0, "min": 0 },
95
+ "consent_needed": { "type": "boolean", "title": { "tr": "Yönetici onayı gerekiyor", "en": "Approval needed" }, "required": true, "default": false }
96
+ }
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,45 @@
1
+ // Devoplus yapay zekâsının bu uygulamada çağırabileceği araçların İŞLEYİCİLERİ. Adı, açıklaması, girdi şeması,
2
+ // salt-okunurluğu ve izni `one.manifest.json` → `agent.tools`'tadır; rehber `AGENT_GUIDE.md`'dir. Yeni araç = üçü birlikte.
3
+ // Araçlar yalnız uygulamanın KENDİ veritabanını okur ve yalnız yöneticiye açıktır (manifest `permission`). Kayıt açan,
4
+ // durum değiştiren ya da faaliyet tamamlayan araç bilinçli olarak yoktur.
5
+ import { defineAgentTools } from '@devoplus/one-sdk'
6
+ import type { Env } from './app.ts'
7
+ import { daysOverdue, istanbulDay } from './capa.ts'
8
+ import { counts, lastReminderRun, overdueActions, overdueCount } from './queries.ts'
9
+
10
+ export const agentTools = defineAgentTools<Env>({
11
+ 'overdue-actions': {
12
+ handler: async ({ input, env }) => {
13
+ const today = istanbulDay(new Date())
14
+ const limit = typeof input.limit === 'number' ? input.limit : 20
15
+ const owner = typeof input.owner === 'string' ? input.owner : undefined
16
+ const [rows, total] = await Promise.all([overdueActions(env, today, limit, owner), overdueCount(env, today, owner)])
17
+ return {
18
+ total,
19
+ actions: rows.map((r) => ({
20
+ action: r.description,
21
+ owner: r.owner_name,
22
+ dueDate: r.due_date,
23
+ daysOverdue: daysOverdue(r.due_date, today),
24
+ nonconformity: r.nonconformity_title,
25
+ severity: r.severity,
26
+ })),
27
+ }
28
+ },
29
+ },
30
+ 'nonconformity-summary': {
31
+ handler: async ({ env }) => {
32
+ const today = istanbulDay(new Date())
33
+ const [c, overdue, run] = await Promise.all([counts(env), overdueCount(env, today), lastReminderRun(env)])
34
+ return {
35
+ byStatus: c.byStatus,
36
+ openBySeverity: c.openBySeverity,
37
+ openBySource: c.openBySource,
38
+ overdueActions: overdue,
39
+ lastReminderRun: run
40
+ ? { day: run.run_key, ownersReminded: run.reminded_owners, actionsReminded: run.reminded_actions, failed: run.failed, deferred: run.deferred, approvalNeeded: run.consent_needed === 1 }
41
+ : null,
42
+ }
43
+ },
44
+ },
45
+ })