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.
- package/README.md +62 -2
- package/dist/cli.mjs +17 -0
- package/package.json +35 -3
- package/templates/0001_init.sql.tpl +10 -0
- package/templates/AGENTS.md.tpl +286 -0
- package/templates/AGENT_GUIDE.md.tpl +36 -0
- package/templates/README.md.tpl +58 -0
- package/templates/_shared/AGENTS.writes.md.tpl +38 -0
- package/templates/_shared/README.tail.md.tpl +20 -0
- package/templates/_shared/files/src/app.test.ts.tpl +36 -0
- package/templates/_shared/files/src/index.ts.tpl +8 -0
- package/templates/_shared/files/src/lib/client.ts.tpl +213 -0
- package/templates/_shared/files/src/lib/files.ts.tpl +37 -0
- package/templates/_shared/files/src/lib/http.ts.tpl +101 -0
- package/templates/_shared/files/src/lib/mail.ts.tpl +26 -0
- package/templates/_shared/files/src/lib/model.ts.tpl +31 -0
- package/templates/_shared/files/src/lib/page.ts.tpl +71 -0
- package/templates/_shared/files/src/lib/record.ts.tpl +246 -0
- package/templates/_shared/files/src/lib/server.ts.tpl +132 -0
- package/templates/_shared/files/src/shell.test.ts.tpl +56 -0
- package/templates/agent.ts.tpl +23 -0
- package/templates/bakim-takvimi/AGENTS.template.md.tpl +40 -0
- package/templates/bakim-takvimi/README.md.tpl +45 -0
- package/templates/bakim-takvimi/files/AGENT_GUIDE.md.tpl +52 -0
- package/templates/bakim-takvimi/files/one.data.json +45 -0
- package/templates/bakim-takvimi/files/src/agent.ts.tpl +22 -0
- package/templates/bakim-takvimi/files/src/app.ts.tpl +259 -0
- package/templates/bakim-takvimi/files/src/jobs.ts.tpl +170 -0
- package/templates/bakim-takvimi/files/src/names.ts.tpl +5 -0
- package/templates/bakim-takvimi/files/src/queries.ts.tpl +71 -0
- package/templates/bakim-takvimi/files/src/schedule.test.ts.tpl +58 -0
- package/templates/bakim-takvimi/files/src/schedule.ts.tpl +106 -0
- package/templates/bakim-takvimi/files/src/shell.ts.tpl +299 -0
- package/templates/bakim-takvimi/manifest.json.tpl +64 -0
- package/templates/egitim-sertifika/AGENTS.template.md.tpl +41 -0
- package/templates/egitim-sertifika/README.md.tpl +46 -0
- package/templates/egitim-sertifika/files/AGENT_GUIDE.md.tpl +53 -0
- package/templates/egitim-sertifika/files/one.data.json +43 -0
- package/templates/egitim-sertifika/files/src/agent.ts.tpl +22 -0
- package/templates/egitim-sertifika/files/src/app.ts.tpl +312 -0
- package/templates/egitim-sertifika/files/src/certificate.test.ts.tpl +83 -0
- package/templates/egitim-sertifika/files/src/certificate.ts.tpl +163 -0
- package/templates/egitim-sertifika/files/src/jobs.ts.tpl +219 -0
- package/templates/egitim-sertifika/files/src/names.ts.tpl +5 -0
- package/templates/egitim-sertifika/files/src/queries.ts.tpl +115 -0
- package/templates/egitim-sertifika/files/src/shell.ts.tpl +345 -0
- package/templates/egitim-sertifika/manifest.json.tpl +57 -0
- package/templates/envanter/AGENTS.template.md.tpl +28 -0
- package/templates/envanter/README.md.tpl +40 -0
- package/templates/envanter/files/AGENT_GUIDE.md.tpl +47 -0
- package/templates/envanter/files/one.data.json +62 -0
- package/templates/envanter/files/src/agent.ts.tpl +20 -0
- package/templates/envanter/files/src/app.ts.tpl +204 -0
- package/templates/envanter/files/src/jobs.ts.tpl +30 -0
- package/templates/envanter/files/src/queries.ts.tpl +53 -0
- package/templates/envanter/files/src/shell.ts.tpl +265 -0
- package/templates/envanter/files/src/stock.test.ts.tpl +31 -0
- package/templates/envanter/files/src/stock.ts.tpl +33 -0
- package/templates/envanter/manifest.json.tpl +56 -0
- package/templates/hedef-takibi/AGENTS.template.md.tpl +37 -0
- package/templates/hedef-takibi/README.md.tpl +52 -0
- package/templates/hedef-takibi/files/AGENT_GUIDE.md.tpl +48 -0
- package/templates/hedef-takibi/files/one.data.json +76 -0
- package/templates/hedef-takibi/files/src/agent.ts.tpl +26 -0
- package/templates/hedef-takibi/files/src/app.ts.tpl +740 -0
- package/templates/hedef-takibi/files/src/progress.test.ts.tpl +53 -0
- package/templates/hedef-takibi/files/src/progress.ts.tpl +81 -0
- package/templates/hedef-takibi/files/src/queries.ts.tpl +83 -0
- package/templates/hedef-takibi/files/src/shell.ts.tpl +455 -0
- package/templates/hedef-takibi/manifest.json.tpl +38 -0
- package/templates/index.ts.tpl +262 -0
- package/templates/markdown.d.ts.tpl +6 -0
- package/templates/musteri-ziyaret/AGENTS.template.md.tpl +47 -0
- package/templates/musteri-ziyaret/README.md.tpl +48 -0
- package/templates/musteri-ziyaret/files/AGENT_GUIDE.md.tpl +44 -0
- package/templates/musteri-ziyaret/files/one.data.json +53 -0
- package/templates/musteri-ziyaret/files/src/agent.ts.tpl +21 -0
- package/templates/musteri-ziyaret/files/src/app.ts.tpl +340 -0
- package/templates/musteri-ziyaret/files/src/customers.ts.tpl +85 -0
- package/templates/musteri-ziyaret/files/src/queries.ts.tpl +67 -0
- package/templates/musteri-ziyaret/files/src/shell.ts.tpl +272 -0
- package/templates/musteri-ziyaret/files/src/visit.test.ts.tpl +65 -0
- package/templates/musteri-ziyaret/files/src/visit.ts.tpl +98 -0
- package/templates/musteri-ziyaret/manifest.json.tpl +37 -0
- package/templates/saha-denetimi/AGENTS.template.md.tpl +32 -0
- package/templates/saha-denetimi/README.md.tpl +44 -0
- package/templates/saha-denetimi/files/AGENT_GUIDE.md.tpl +55 -0
- package/templates/saha-denetimi/files/one.data.json +87 -0
- package/templates/saha-denetimi/files/src/agent.ts.tpl +41 -0
- package/templates/saha-denetimi/files/src/app.ts.tpl +444 -0
- package/templates/saha-denetimi/files/src/queries.ts.tpl +143 -0
- package/templates/saha-denetimi/files/src/score.test.ts.tpl +48 -0
- package/templates/saha-denetimi/files/src/score.ts.tpl +102 -0
- package/templates/saha-denetimi/files/src/shell.ts.tpl +585 -0
- package/templates/saha-denetimi/manifest.json.tpl +49 -0
- package/templates/shell.test.ts.tpl +30 -0
- package/templates/shell.ts.tpl +201 -0
- package/templates/uygunsuzluk-dof/AGENTS.template.md.tpl +40 -0
- package/templates/uygunsuzluk-dof/README.md.tpl +46 -0
- package/templates/uygunsuzluk-dof/files/AGENT_GUIDE.md.tpl +56 -0
- package/templates/uygunsuzluk-dof/files/one.data.json +99 -0
- package/templates/uygunsuzluk-dof/files/src/agent.ts.tpl +45 -0
- package/templates/uygunsuzluk-dof/files/src/app.ts.tpl +431 -0
- package/templates/uygunsuzluk-dof/files/src/capa.test.ts.tpl +53 -0
- package/templates/uygunsuzluk-dof/files/src/capa.ts.tpl +114 -0
- package/templates/uygunsuzluk-dof/files/src/jobs.ts.tpl +127 -0
- package/templates/uygunsuzluk-dof/files/src/queries.ts.tpl +102 -0
- package/templates/uygunsuzluk-dof/files/src/shell.ts.tpl +441 -0
- package/templates/uygunsuzluk-dof/manifest.json.tpl +56 -0
- package/templates/ziyaretci-kaydi/AGENTS.template.md.tpl +35 -0
- package/templates/ziyaretci-kaydi/README.md.tpl +46 -0
- package/templates/ziyaretci-kaydi/files/AGENT_GUIDE.md.tpl +39 -0
- package/templates/ziyaretci-kaydi/files/one.data.json +42 -0
- package/templates/ziyaretci-kaydi/files/src/agent.ts.tpl +13 -0
- package/templates/ziyaretci-kaydi/files/src/app.ts.tpl +293 -0
- package/templates/ziyaretci-kaydi/files/src/jobs.ts.tpl +49 -0
- package/templates/ziyaretci-kaydi/files/src/notice.ts.tpl +48 -0
- package/templates/ziyaretci-kaydi/files/src/queries.ts.tpl +45 -0
- package/templates/ziyaretci-kaydi/files/src/shell.ts.tpl +252 -0
- package/templates/ziyaretci-kaydi/files/src/visitor.test.ts.tpl +34 -0
- package/templates/ziyaretci-kaydi/files/src/visitor.ts.tpl +57 -0
- package/templates/ziyaretci-kaydi/manifest.json.tpl +38 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// {{nameTr}} — sayfa (üç görünüm: Faaliyetlerim · Uygunsuzluk bildir · Kayıtlar) + her görünümden açılan kayıt paneli.
|
|
2
|
+
// Liste, süzgeç, form, kişi seçici ve dosya yükleme hazır bileşenlerdir; seçenekler veri modelinden gelir. Durum düğmeleri
|
|
3
|
+
// yalnız gidilebilecek durumları gösterir (kaynak `src/capa.ts`); asıl karar sunucudadır.
|
|
4
|
+
import type { OneNavigationItem } from '@devoplus/one-sdk'
|
|
5
|
+
import { ACTION_NOTE_MAX, STATUSES, nextStatuses } from './capa.ts'
|
|
6
|
+
import { dataModel } from './data/types.ts'
|
|
7
|
+
import { renderClientScript } from './lib/client.ts'
|
|
8
|
+
import { choices, label } from './lib/model.ts'
|
|
9
|
+
import { escapeHtml, i18n, renderPage, translator, view, type Lang, type Strings, type Theme } from './lib/page.ts'
|
|
10
|
+
|
|
11
|
+
export const STRINGS: Strings = {
|
|
12
|
+
tr: {
|
|
13
|
+
title: {{nameTrJs}},
|
|
14
|
+
waiting: "Portal'dan oturum bekleniyor…",
|
|
15
|
+
networkError: 'İstek gönderilemedi (ağ hatası).',
|
|
16
|
+
requestFailed: 'İşlem yapılamadı.',
|
|
17
|
+
readOnly: 'Vekalet ya da Yerine Görüntüle oturumundasınız: bu uygulamada yalnız görüntüleme yapabilirsiniz.',
|
|
18
|
+
myIntro: 'Sorumlusu olduğunuz düzeltici faaliyetler. Bir faaliyete tıklayıp ne yaptığınızı yazarak tamamlayın.',
|
|
19
|
+
myTable: 'Faaliyetlerim',
|
|
20
|
+
search: 'Ara',
|
|
21
|
+
overdue: 'Gecikti',
|
|
22
|
+
completeTitle: 'Faaliyeti tamamla',
|
|
23
|
+
completeSubmit: 'Tamamlandı olarak işaretle',
|
|
24
|
+
whatDone: 'Ne yapıldı',
|
|
25
|
+
completed: 'Faaliyet tamamlandı.',
|
|
26
|
+
openRecord: 'Kaydı görüntüle',
|
|
27
|
+
reportIntro: 'Bir kalite ya da iş güvenliği uygunsuzluğu gördüyseniz buradan bildirin. Kayıt kalite ekibine gider.',
|
|
28
|
+
reportSubmit: 'Bildir',
|
|
29
|
+
reported: 'Uygunsuzluk kaydedildi.',
|
|
30
|
+
myReports: 'Bildirdiklerim',
|
|
31
|
+
managersOnly: 'Bu bölüm yalnız yöneticilere açıktır.',
|
|
32
|
+
summary: 'Açık: {open} - Faaliyet planlandı: {planned} - Doğrulamada: {verification} - Kapandı: {closed}. Geciken faaliyet: {overdue}.',
|
|
33
|
+
lastRun: 'Son hatırlatma ({day}): {owners} kişiye {actions} faaliyet hatırlatıldı.',
|
|
34
|
+
lastRunConsent: 'Son hatırlatma ({day}) gönderilemedi: e-posta servisi bu kurulumda onaylanmamış. Portal yöneticisinden arka plan işi e-posta onayını isteyin.',
|
|
35
|
+
noRun: 'Henüz hatırlatma turu yapılmadı.',
|
|
36
|
+
recordsTable: 'Uygunsuzluk kayıtları',
|
|
37
|
+
actionsTable: 'Bütün düzeltici faaliyetler',
|
|
38
|
+
recordTitle: 'Kayıt: {title}',
|
|
39
|
+
download: 'Eki indir',
|
|
40
|
+
editRecord: 'Kayıt bilgileri ve kök neden',
|
|
41
|
+
recordSaved: 'Kayıt kaydedildi.',
|
|
42
|
+
moveTo: 'Durumu ilerlet:',
|
|
43
|
+
statusConfirm: 'Kayıt "{status}" durumuna geçirilsin mi?',
|
|
44
|
+
statusChanged: 'Durum değişti.',
|
|
45
|
+
closeNote: 'Kapanış notu (isteğe bağlı)',
|
|
46
|
+
actionsTitle: 'Düzeltici faaliyetler',
|
|
47
|
+
actionForm: 'Faaliyet',
|
|
48
|
+
newAction: 'Yeni faaliyet',
|
|
49
|
+
actionSaved: 'Faaliyet kaydedildi.',
|
|
50
|
+
deleteAction: 'Faaliyeti sil',
|
|
51
|
+
deleteActionConfirm: 'Bu faaliyet silinsin mi?',
|
|
52
|
+
actionDeleted: 'Faaliyet silindi.',
|
|
53
|
+
owner: 'Sorumlu (e-posta alır)',
|
|
54
|
+
consentNeeded: 'Kaydedildi ama sorumluya e-posta gönderilemedi: portaldaki "İzinleri incele" düğmesiyle e-posta izni verin.',
|
|
55
|
+
close: 'Kapat',
|
|
56
|
+
downloadFailed: 'Dosya indirilemedi.',
|
|
57
|
+
},
|
|
58
|
+
en: {
|
|
59
|
+
title: {{nameEnJs}},
|
|
60
|
+
waiting: 'Waiting for the session from the Portal…',
|
|
61
|
+
networkError: 'Request could not be sent (network error).',
|
|
62
|
+
requestFailed: 'The action could not be completed.',
|
|
63
|
+
readOnly: 'You are in a Delegation or View as User session: you can only view this app.',
|
|
64
|
+
myIntro: 'Corrective actions you own. Click an action and describe what you did to complete it.',
|
|
65
|
+
myTable: 'My actions',
|
|
66
|
+
search: 'Search',
|
|
67
|
+
overdue: 'Overdue',
|
|
68
|
+
completeTitle: 'Complete the action',
|
|
69
|
+
completeSubmit: 'Mark as done',
|
|
70
|
+
whatDone: 'What was done',
|
|
71
|
+
completed: 'Action completed.',
|
|
72
|
+
openRecord: 'View record',
|
|
73
|
+
reportIntro: 'If you have seen a quality or safety nonconformity, report it here. The record goes to the quality team.',
|
|
74
|
+
reportSubmit: 'Report',
|
|
75
|
+
reported: 'Nonconformity saved.',
|
|
76
|
+
myReports: 'My reports',
|
|
77
|
+
managersOnly: 'Only managers can see this section.',
|
|
78
|
+
summary: 'Open: {open} - Actions planned: {planned} - In verification: {verification} - Closed: {closed}. Overdue actions: {overdue}.',
|
|
79
|
+
lastRun: 'Latest reminder ({day}): {actions} actions reminded to {owners} people.',
|
|
80
|
+
lastRunConsent: 'Latest reminder ({day}) could not be sent: the e-mail service is not approved for this installation. Ask the portal administrator to approve background e-mail.',
|
|
81
|
+
noRun: 'No reminder run yet.',
|
|
82
|
+
recordsTable: 'Nonconformity records',
|
|
83
|
+
actionsTable: 'All corrective actions',
|
|
84
|
+
recordTitle: 'Record: {title}',
|
|
85
|
+
download: 'Download attachment',
|
|
86
|
+
editRecord: 'Record details and root cause',
|
|
87
|
+
recordSaved: 'Record saved.',
|
|
88
|
+
moveTo: 'Move status to:',
|
|
89
|
+
statusConfirm: 'Move the record to "{status}"?',
|
|
90
|
+
statusChanged: 'Status changed.',
|
|
91
|
+
closeNote: 'Closing note (optional)',
|
|
92
|
+
actionsTitle: 'Corrective actions',
|
|
93
|
+
actionForm: 'Action',
|
|
94
|
+
newAction: 'New action',
|
|
95
|
+
actionSaved: 'Action saved.',
|
|
96
|
+
deleteAction: 'Delete action',
|
|
97
|
+
deleteActionConfirm: 'Delete this action?',
|
|
98
|
+
actionDeleted: 'Action deleted.',
|
|
99
|
+
owner: 'Owner (gets e-mail)',
|
|
100
|
+
consentNeeded: 'Saved, but no e-mail was sent to the owner: allow e-mail with the "Review permissions" button in the Portal.',
|
|
101
|
+
close: 'Close',
|
|
102
|
+
downloadFailed: 'The file could not be downloaded.',
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const MODEL = {
|
|
107
|
+
manage: '{{permissionPrefix}}Manage',
|
|
108
|
+
noteMax: ACTION_NOTE_MAX,
|
|
109
|
+
next: Object.fromEntries(STATUSES.map((s) => [s, nextStatuses(s)])),
|
|
110
|
+
status: choices(dataModel, 'nonconformities', 'status'),
|
|
111
|
+
source: choices(dataModel, 'nonconformities', 'source'),
|
|
112
|
+
severity: choices(dataModel, 'nonconformities', 'severity'),
|
|
113
|
+
actionStatus: choices(dataModel, 'actions', 'status'),
|
|
114
|
+
labels: {
|
|
115
|
+
title: label(dataModel, 'nonconformities', 'title'),
|
|
116
|
+
source: label(dataModel, 'nonconformities', 'source'),
|
|
117
|
+
severity: label(dataModel, 'nonconformities', 'severity'),
|
|
118
|
+
description: label(dataModel, 'nonconformities', 'description'),
|
|
119
|
+
location: label(dataModel, 'nonconformities', 'location'),
|
|
120
|
+
detectedOn: label(dataModel, 'nonconformities', 'detected_on'),
|
|
121
|
+
reporter: label(dataModel, 'nonconformities', 'reporter_name'),
|
|
122
|
+
rootCause: label(dataModel, 'nonconformities', 'root_cause'),
|
|
123
|
+
status: label(dataModel, 'nonconformities', 'status'),
|
|
124
|
+
attachment: label(dataModel, 'nonconformities', 'attachment'),
|
|
125
|
+
closedAt: label(dataModel, 'nonconformities', 'closed_at'),
|
|
126
|
+
closedBy: label(dataModel, 'nonconformities', 'closed_by_name'),
|
|
127
|
+
closureNote: label(dataModel, 'nonconformities', 'closure_note'),
|
|
128
|
+
ncTitle: label(dataModel, 'actions', 'nonconformity_title'),
|
|
129
|
+
action: label(dataModel, 'actions', 'description'),
|
|
130
|
+
ownerName: label(dataModel, 'actions', 'owner_name'),
|
|
131
|
+
dueDate: label(dataModel, 'actions', 'due_date'),
|
|
132
|
+
actionStatus: label(dataModel, 'actions', 'status'),
|
|
133
|
+
doneAt: label(dataModel, 'actions', 'done_at'),
|
|
134
|
+
doneNote: label(dataModel, 'actions', 'done_note'),
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function renderShell(lang: Lang, theme: Theme, path: string): string {
|
|
139
|
+
const tx = translator(STRINGS, lang)
|
|
140
|
+
const btn = (id: string, key: string, cls = 'one-btn', hidden = false) =>
|
|
141
|
+
`<button type="button" class="${cls}" id="${id}" data-i18n="${key}"${hidden ? ' hidden' : ''}>${escapeHtml(tx(key))}</button>`
|
|
142
|
+
const body = [
|
|
143
|
+
view(
|
|
144
|
+
'/',
|
|
145
|
+
path,
|
|
146
|
+
`${i18n(tx, 'myIntro', 'p', 'class="one-muted"')}
|
|
147
|
+
<one-filter id="myFilter" for="myTable"></one-filter>
|
|
148
|
+
<one-table id="myTable" src="/api/my/actions" paging="page" export="xlsx csv" clickable caption="${escapeHtml(tx('myTable'))}"></one-table>
|
|
149
|
+
<section id="completeBox" class="one-card app-detail" hidden>
|
|
150
|
+
<h2 data-i18n="completeTitle">${escapeHtml(tx('completeTitle'))}</h2>
|
|
151
|
+
<dl id="completeFields"></dl>
|
|
152
|
+
<one-form id="completeForm" no-cancel></one-form>
|
|
153
|
+
<div class="app-toolbar">${btn('openFromAction', 'openRecord')}</div>
|
|
154
|
+
</section>`,
|
|
155
|
+
),
|
|
156
|
+
view(
|
|
157
|
+
'/report',
|
|
158
|
+
path,
|
|
159
|
+
`${i18n(tx, 'reportIntro', 'p', 'class="one-muted"')}
|
|
160
|
+
<one-form id="reportForm" action="/api/nonconformities" method="POST"></one-form>
|
|
161
|
+
<h2 data-i18n="myReports">${escapeHtml(tx('myReports'))}</h2>
|
|
162
|
+
<one-table id="reportsTable" src="/api/my/nonconformities" paging="page" clickable caption="${escapeHtml(tx('myReports'))}"></one-table>`,
|
|
163
|
+
),
|
|
164
|
+
view(
|
|
165
|
+
'/records',
|
|
166
|
+
path,
|
|
167
|
+
`${i18n(tx, 'managersOnly', 'p', 'class="one-muted" id="recordsDenied"')}
|
|
168
|
+
<div id="recordsArea" hidden>
|
|
169
|
+
<p><strong id="summaryLine"></strong></p>
|
|
170
|
+
<p class="one-muted" id="runLine"></p>
|
|
171
|
+
<one-filter id="recordsFilter" for="recordsTable"></one-filter>
|
|
172
|
+
<one-table id="recordsTable" src="/api/nonconformities" paging="page" export="xlsx csv" clickable caption="${escapeHtml(tx('recordsTable'))}"></one-table>
|
|
173
|
+
<h2 data-i18n="actionsTable">${escapeHtml(tx('actionsTable'))}</h2>
|
|
174
|
+
<one-filter id="actionsFilter" for="actionsTable"></one-filter>
|
|
175
|
+
<one-table id="actionsTable" src="/api/actions" paging="page" export="xlsx csv" clickable caption="${escapeHtml(tx('actionsTable'))}"></one-table>
|
|
176
|
+
</div>`,
|
|
177
|
+
),
|
|
178
|
+
`<section id="recordPanel" class="one-card app-detail" hidden aria-labelledby="recordTitle">
|
|
179
|
+
<h2 id="recordTitle"></h2>
|
|
180
|
+
<dl id="recordFields"></dl>
|
|
181
|
+
<div class="app-toolbar">${btn('downloadBtn', 'download', 'one-btn', true)}</div>
|
|
182
|
+
<div id="manageBox" hidden>
|
|
183
|
+
<h3 data-i18n="editRecord">${escapeHtml(tx('editRecord'))}</h3>
|
|
184
|
+
<one-form id="recordForm" method="PATCH" no-cancel></one-form>
|
|
185
|
+
<div id="statusBox" class="app-toolbar">${i18n(tx, 'moveTo', 'span', 'class="one-muted"')}<span id="statusButtons" class="app-toolbar"></span></div>
|
|
186
|
+
<div id="closeNoteBox" class="one-field" hidden><label class="one-label" for="closeNote" data-i18n="closeNote">${escapeHtml(tx('closeNote'))}</label><textarea id="closeNote" class="one-textarea" maxlength="2000" rows="2"></textarea></div>
|
|
187
|
+
</div>
|
|
188
|
+
<h3 data-i18n="actionsTitle">${escapeHtml(tx('actionsTitle'))}</h3>
|
|
189
|
+
<one-table id="recordActions" no-search clickable caption="${escapeHtml(tx('actionsTitle'))}"></one-table>
|
|
190
|
+
<div id="actionBox" hidden>
|
|
191
|
+
<div class="app-toolbar">${btn('newAction', 'newAction')}${btn('deleteAction', 'deleteAction', 'one-btn one-btn-danger', true)}</div>
|
|
192
|
+
<one-form id="actionForm" no-cancel></one-form>
|
|
193
|
+
</div>
|
|
194
|
+
<div class="app-toolbar">${btn('closeRecord', 'close', 'one-btn one-btn-text')}</div>
|
|
195
|
+
</section>`,
|
|
196
|
+
].join('\n')
|
|
197
|
+
return renderPage({ lang, theme, title: tx('title'), body })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function renderShellScript(navigation: readonly OneNavigationItem[], options: { actingWrites?: boolean } = {}): string {
|
|
201
|
+
return renderClientScript({ strings: STRINGS, navigation, ...(options.actingWrites !== undefined ? { actingWrites: options.actingWrites } : {}), body: pageScript() })
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function pageScript(): string {
|
|
205
|
+
return `
|
|
206
|
+
var M = ${JSON.stringify(MODEL).replace(/<\//g, '<\\/')};
|
|
207
|
+
var myTable = $('myTable'), completeForm = $('completeForm'), reportForm = $('reportForm'), reportsTable = $('reportsTable');
|
|
208
|
+
var recordsTable = $('recordsTable'), actionsTable = $('actionsTable'), recordForm = $('recordForm'), recordActions = $('recordActions'), actionForm = $('actionForm');
|
|
209
|
+
var currentAction = null, current = null, editingAction = null, isManager = false;
|
|
210
|
+
|
|
211
|
+
function both(key) { return { tr: S.tr[key], en: S.en[key] }; }
|
|
212
|
+
function choiceText(options, value) { for (var i = 0; i < options.length; i++) if (options[i].value === value) return L(options[i].label); return value || ''; }
|
|
213
|
+
function fail(r) { one.toast({ severity: 'error', summary: t('requestFailed'), detail: problem(r) }); }
|
|
214
|
+
function mailNote(r) { if (r.ok && r.body && r.body.notification === 'consent-needed') one.toast({ severity: 'warn', summary: t('consentNeeded') }); }
|
|
215
|
+
function row(dl, labelText, value) { if (value == null || value === '') return; dl.appendChild(el('dt', { text: labelText })); dl.appendChild(el('dd', { text: String(value) })); }
|
|
216
|
+
function todayKey() { var d = new Date(Date.now() + 3 * 3600000); return d.toISOString().slice(0, 10); }
|
|
217
|
+
function late(r) { return r.status === 'open' && r.due_date && r.due_date < todayKey(); }
|
|
218
|
+
|
|
219
|
+
var actionColumns = [
|
|
220
|
+
{ key: 'due_date', label: M.labels.dueDate, type: 'date' },
|
|
221
|
+
{ key: 'description', label: M.labels.action },
|
|
222
|
+
{ key: 'nonconformity_title', label: M.labels.ncTitle },
|
|
223
|
+
{ key: 'owner_name', label: M.labels.ownerName },
|
|
224
|
+
{ key: 'status', label: M.labels.actionStatus, type: 'choice', options: M.actionStatus },
|
|
225
|
+
{ key: 'late', label: both('overdue'), sortable: false, format: function (v, r) { return late(r) ? t('overdue') : ''; } },
|
|
226
|
+
{ key: 'done_note', label: M.labels.doneNote }
|
|
227
|
+
];
|
|
228
|
+
myTable.columns = actionColumns;
|
|
229
|
+
actionsTable.columns = actionColumns;
|
|
230
|
+
recordActions.columns = actionColumns.filter(function (c) { return c.key !== 'nonconformity_title'; });
|
|
231
|
+
var actionFilter = [
|
|
232
|
+
{ key: 'q', label: both('search'), type: 'text' },
|
|
233
|
+
{ key: 'status', label: M.labels.actionStatus, type: 'choice', options: M.actionStatus },
|
|
234
|
+
{ key: 'due_date', label: M.labels.dueDate, type: 'dateRange' }
|
|
235
|
+
];
|
|
236
|
+
$('myFilter').fields = actionFilter;
|
|
237
|
+
$('actionsFilter').fields = actionFilter;
|
|
238
|
+
var recordColumns = [
|
|
239
|
+
{ key: 'created_at', label: { tr: 'Bildirim', en: 'Reported' }, type: 'datetime' },
|
|
240
|
+
{ key: 'title', label: M.labels.title },
|
|
241
|
+
{ key: 'source', label: M.labels.source, type: 'choice', options: M.source },
|
|
242
|
+
{ key: 'severity', label: M.labels.severity, type: 'choice', options: M.severity },
|
|
243
|
+
{ key: 'status', label: M.labels.status, type: 'choice', options: M.status },
|
|
244
|
+
{ key: 'location', label: M.labels.location }
|
|
245
|
+
];
|
|
246
|
+
recordsTable.columns = recordColumns.concat([{ key: 'reporter_name', label: M.labels.reporter }]);
|
|
247
|
+
reportsTable.columns = recordColumns;
|
|
248
|
+
$('recordsFilter').fields = [
|
|
249
|
+
{ key: 'q', label: both('search'), type: 'text' },
|
|
250
|
+
{ key: 'status', label: M.labels.status, type: 'choice', options: M.status },
|
|
251
|
+
{ key: 'severity', label: M.labels.severity, type: 'choice', options: M.severity },
|
|
252
|
+
{ key: 'source', label: M.labels.source, type: 'choice', options: M.source },
|
|
253
|
+
{ key: 'created_at', label: { tr: 'Bildirim', en: 'Reported' }, type: 'dateRange' }
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
// ── Bildir ──
|
|
257
|
+
reportForm.fields = [
|
|
258
|
+
{ key: 'title', label: M.labels.title, required: true, maxLength: 120 },
|
|
259
|
+
{ key: 'source', label: M.labels.source, type: 'choice', options: M.source, required: true },
|
|
260
|
+
{ key: 'severity', label: M.labels.severity, type: 'choice', options: M.severity, required: true },
|
|
261
|
+
{ key: 'description', label: M.labels.description, type: 'longText', required: true, maxLength: 4000 },
|
|
262
|
+
{ key: 'location', label: M.labels.location, maxLength: 120 },
|
|
263
|
+
{ key: 'detected_on', label: M.labels.detectedOn, type: 'date' },
|
|
264
|
+
{ key: 'attachment', label: M.labels.attachment, type: 'file', accept: 'image/*,.pdf,.docx,.xlsx', maxSize: 5242880, action: '/api/files' }
|
|
265
|
+
];
|
|
266
|
+
reportForm.submitLabel = both('reportSubmit');
|
|
267
|
+
var EMPTY = { source: 'internal', severity: 'medium' };
|
|
268
|
+
reportForm.value = EMPTY;
|
|
269
|
+
reportForm.addEventListener('saved', function (e) {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
one.toast({ severity: 'success', summary: t('reported') });
|
|
272
|
+
reportForm.value = EMPTY; reportsTable.reload();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ── Faaliyetlerim ──
|
|
276
|
+
completeForm.fields = [{ key: 'note', label: both('whatDone'), required: true, maxLength: M.noteMax }];
|
|
277
|
+
completeForm.submitLabel = both('completeSubmit');
|
|
278
|
+
function showComplete(a) {
|
|
279
|
+
currentAction = a;
|
|
280
|
+
var dl = $('completeFields'); dl.textContent = '';
|
|
281
|
+
row(dl, L(M.labels.action), a.description);
|
|
282
|
+
row(dl, L(M.labels.ncTitle), a.nonconformity_title);
|
|
283
|
+
row(dl, L(M.labels.dueDate), fmtDate(a.due_date) + (late(a) ? ' (' + t('overdue') + ')' : ''));
|
|
284
|
+
row(dl, L(M.labels.actionStatus), choiceText(M.actionStatus, a.status));
|
|
285
|
+
row(dl, L(M.labels.doneNote), a.done_note);
|
|
286
|
+
completeForm.toggleAttribute('hidden', a.status !== 'open' || !canWrite());
|
|
287
|
+
completeForm.value = {};
|
|
288
|
+
$('completeBox').removeAttribute('hidden');
|
|
289
|
+
}
|
|
290
|
+
myTable.addEventListener('rowclick', function (e) { showComplete(e.detail.row); });
|
|
291
|
+
completeForm.addEventListener('save', function (e) {
|
|
292
|
+
e.preventDefault();
|
|
293
|
+
if (!currentAction) return;
|
|
294
|
+
api('POST', '/api/actions/' + encodeURIComponent(currentAction.id) + '/complete', { note: e.detail.value.note || '' }).then(function (r) {
|
|
295
|
+
if (!r.ok) { if (r.body && r.body.errors) completeForm.setErrors(r.body.errors, problem(r)); else fail(r); return; }
|
|
296
|
+
one.toast({ severity: 'success', summary: t('completed') });
|
|
297
|
+
showComplete(r.body.action); myTable.reload();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
$('openFromAction').addEventListener('click', function () { if (currentAction) openRecord(currentAction.nonconformity_id); });
|
|
301
|
+
|
|
302
|
+
// ── Kayıt paneli ──
|
|
303
|
+
function base() { return '/api/nonconformities/' + encodeURIComponent(current.id); }
|
|
304
|
+
recordForm.fields = [
|
|
305
|
+
{ key: 'title', label: M.labels.title, required: true, maxLength: 120 },
|
|
306
|
+
{ key: 'source', label: M.labels.source, type: 'choice', options: M.source, required: true },
|
|
307
|
+
{ key: 'severity', label: M.labels.severity, type: 'choice', options: M.severity, required: true },
|
|
308
|
+
{ key: 'description', label: M.labels.description, type: 'longText', required: true, maxLength: 4000 },
|
|
309
|
+
{ key: 'location', label: M.labels.location, maxLength: 120 },
|
|
310
|
+
{ key: 'detected_on', label: M.labels.detectedOn, type: 'date' },
|
|
311
|
+
{ key: 'root_cause', label: M.labels.rootCause, type: 'longText', maxLength: 4000 }
|
|
312
|
+
];
|
|
313
|
+
actionForm.fields = [
|
|
314
|
+
{ key: 'description', label: M.labels.action, type: 'longText', required: true, maxLength: 2000 },
|
|
315
|
+
{ key: 'owner', label: both('owner'), type: 'person', required: true },
|
|
316
|
+
{ key: 'due_date', label: M.labels.dueDate, type: 'date', required: true }
|
|
317
|
+
];
|
|
318
|
+
function editableActions(rec) { return rec.status === 'open' || rec.status === 'planned'; }
|
|
319
|
+
function newAction() { editingAction = null; actionForm.value = {}; $('deleteAction').setAttribute('hidden', ''); }
|
|
320
|
+
function showRecord(rec, actions, manager) {
|
|
321
|
+
current = rec;
|
|
322
|
+
$('recordTitle').textContent = t('recordTitle', { title: rec.title });
|
|
323
|
+
var dl = $('recordFields'); dl.textContent = '';
|
|
324
|
+
row(dl, L(M.labels.status), choiceText(M.status, rec.status));
|
|
325
|
+
row(dl, L(M.labels.source), choiceText(M.source, rec.source));
|
|
326
|
+
row(dl, L(M.labels.severity), choiceText(M.severity, rec.severity));
|
|
327
|
+
row(dl, L(M.labels.reporter), rec.reporter_name);
|
|
328
|
+
row(dl, L(M.labels.detectedOn), fmtDate(rec.detected_on));
|
|
329
|
+
row(dl, L(M.labels.location), rec.location);
|
|
330
|
+
row(dl, L(M.labels.description), rec.description);
|
|
331
|
+
row(dl, L(M.labels.rootCause), rec.root_cause);
|
|
332
|
+
row(dl, L(M.labels.closedAt), fmtDateTime(rec.closed_at));
|
|
333
|
+
row(dl, L(M.labels.closedBy), rec.closed_by_name);
|
|
334
|
+
row(dl, L(M.labels.closureNote), rec.closure_note);
|
|
335
|
+
$('downloadBtn').toggleAttribute('hidden', !rec.attachment);
|
|
336
|
+
var writable = manager && canWrite();
|
|
337
|
+
$('manageBox').toggleAttribute('hidden', !writable || rec.status === 'closed');
|
|
338
|
+
if (writable && rec.status !== 'closed') {
|
|
339
|
+
recordForm.setAttribute('action', base());
|
|
340
|
+
recordForm.value = { title: rec.title, source: rec.source, severity: rec.severity, description: rec.description, location: rec.location || '', detected_on: rec.detected_on || '', root_cause: rec.root_cause || '' };
|
|
341
|
+
var buttons = $('statusButtons'); buttons.textContent = '';
|
|
342
|
+
(M.next[rec.status] || []).forEach(function (s) {
|
|
343
|
+
var b = el('button', { type: 'button', 'class': 'one-btn', text: choiceText(M.status, s) });
|
|
344
|
+
b.addEventListener('click', function () { changeStatus(s); });
|
|
345
|
+
buttons.appendChild(b);
|
|
346
|
+
});
|
|
347
|
+
$('closeNoteBox').toggleAttribute('hidden', (M.next[rec.status] || []).indexOf('closed') < 0);
|
|
348
|
+
$('closeNote').value = '';
|
|
349
|
+
}
|
|
350
|
+
recordActions.rows = actions;
|
|
351
|
+
$('actionBox').toggleAttribute('hidden', !(writable && editableActions(rec)));
|
|
352
|
+
newAction();
|
|
353
|
+
$('recordPanel').removeAttribute('hidden');
|
|
354
|
+
one.setTitle(rec.title);
|
|
355
|
+
}
|
|
356
|
+
function hideRecord() { $('recordPanel').setAttribute('hidden', ''); current = null; one.setTitle(null); }
|
|
357
|
+
function openRecord(id) {
|
|
358
|
+
api('GET', '/api/nonconformities/' + encodeURIComponent(id) + '/detail').then(function (r) {
|
|
359
|
+
if (!r.ok) { fail(r); return; }
|
|
360
|
+
showRecord(r.body.record, r.body.actions, r.body.manager === true);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function reloadLists() { if (isManager) { recordsTable.reload(); actionsTable.reload(); loadSummary(); } myTable.reload(); reportsTable.reload(); }
|
|
364
|
+
function changeStatus(s) {
|
|
365
|
+
if (!current) return;
|
|
366
|
+
one.confirm({ title: choiceText(M.status, s), message: t('statusConfirm', { status: choiceText(M.status, s) }), acceptLabel: choiceText(M.status, s) }).then(function (ok) {
|
|
367
|
+
if (!ok) return;
|
|
368
|
+
var body = { status: s };
|
|
369
|
+
var note = ($('closeNote').value || '').trim();
|
|
370
|
+
if (s === 'closed' && note) body.note = note;
|
|
371
|
+
return api('POST', base() + '/status', body).then(function (r) {
|
|
372
|
+
if (!toastResult(r, 'statusChanged')) return;
|
|
373
|
+
openRecord(current.id); reloadLists();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
recordForm.addEventListener('saved', function (e) {
|
|
378
|
+
e.preventDefault();
|
|
379
|
+
one.toast({ severity: 'success', summary: t('recordSaved') });
|
|
380
|
+
openRecord(current.id); reloadLists();
|
|
381
|
+
});
|
|
382
|
+
recordActions.addEventListener('rowclick', function (e) {
|
|
383
|
+
var a = e.detail.row;
|
|
384
|
+
if ($('actionBox').hasAttribute('hidden') || a.status !== 'open') return;
|
|
385
|
+
editingAction = a;
|
|
386
|
+
actionForm.value = { description: a.description, owner: { id: a.owner_org_id, name: a.owner_name || '' }, due_date: a.due_date };
|
|
387
|
+
$('deleteAction').removeAttribute('hidden');
|
|
388
|
+
});
|
|
389
|
+
$('newAction').addEventListener('click', newAction);
|
|
390
|
+
actionForm.addEventListener('save', function (e) {
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
if (!current) return;
|
|
393
|
+
var v = e.detail.value, body = { description: v.description, owner: v.owner, due_date: v.due_date };
|
|
394
|
+
var req = editingAction ? api('PATCH', '/api/actions/' + encodeURIComponent(editingAction.id), body) : api('POST', base() + '/actions', body);
|
|
395
|
+
req.then(function (r) {
|
|
396
|
+
if (!r.ok) { if (r.body && r.body.errors) actionForm.setErrors(r.body.errors, problem(r)); else fail(r); return; }
|
|
397
|
+
one.toast({ severity: 'success', summary: t('actionSaved') });
|
|
398
|
+
mailNote(r); openRecord(current.id); reloadLists();
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
$('deleteAction').addEventListener('click', function () {
|
|
402
|
+
if (!editingAction) return;
|
|
403
|
+
one.confirm({ title: t('deleteAction'), message: t('deleteActionConfirm'), severity: 'danger', acceptLabel: t('deleteAction') }).then(function (ok) {
|
|
404
|
+
if (!ok) return;
|
|
405
|
+
return api('DELETE', '/api/actions/' + encodeURIComponent(editingAction.id)).then(function (r) { if (toastResult(r, 'actionDeleted')) { openRecord(current.id); reloadLists(); } });
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
$('downloadBtn').addEventListener('click', function () {
|
|
409
|
+
if (!current) return;
|
|
410
|
+
var rec = current;
|
|
411
|
+
one.fetch(base() + '/attachment').then(function (res) {
|
|
412
|
+
if (!res.ok) { one.toast({ severity: 'error', summary: t('downloadFailed') }); return; }
|
|
413
|
+
return res.arrayBuffer().then(function (data) { return one.download({ name: rec.attachment_name || 'ek', mime: (res.headers.get('content-type') || 'application/octet-stream').split(';')[0], data: data }); });
|
|
414
|
+
}, function () { one.toast({ severity: 'error', summary: t('downloadFailed') }); });
|
|
415
|
+
});
|
|
416
|
+
$('closeRecord').addEventListener('click', hideRecord);
|
|
417
|
+
reportsTable.addEventListener('rowclick', function (e) { openRecord(e.detail.row.id); });
|
|
418
|
+
recordsTable.addEventListener('rowclick', function (e) { openRecord(e.detail.row.id); });
|
|
419
|
+
actionsTable.addEventListener('rowclick', function (e) { openRecord(e.detail.row.nonconformity_id); });
|
|
420
|
+
|
|
421
|
+
// ── Kayıtlar özeti ──
|
|
422
|
+
function loadSummary() {
|
|
423
|
+
if (!isManager) return;
|
|
424
|
+
api('GET', '/api/summary').then(function (r) {
|
|
425
|
+
if (!r.ok) { fail(r); return; }
|
|
426
|
+
var s = r.body.byStatus || {};
|
|
427
|
+
$('summaryLine').textContent = t('summary', { open: fmtNumber(s.open || 0), planned: fmtNumber(s.planned || 0), verification: fmtNumber(s.verification || 0), closed: fmtNumber(s.closed || 0), overdue: fmtNumber(r.body.overdue) });
|
|
428
|
+
var run = r.body.lastReminderRun;
|
|
429
|
+
$('runLine').textContent = !run ? t('noRun') : run.consent_needed ? t('lastRunConsent', { day: fmtDate(run.run_key) }) : t('lastRun', { day: fmtDate(run.run_key), owners: fmtNumber(run.reminded_owners), actions: fmtNumber(run.reminded_actions) });
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
onSession(function () {
|
|
434
|
+
isManager = can(M.manage);
|
|
435
|
+
$('recordsArea').toggleAttribute('hidden', !isManager); $('recordsDenied').toggleAttribute('hidden', isManager);
|
|
436
|
+
reportForm.toggleAttribute('hidden', !canWrite());
|
|
437
|
+
if (isManager) loadSummary();
|
|
438
|
+
});
|
|
439
|
+
onView(function (p) { hideRecord(); $('completeBox').setAttribute('hidden', ''); currentAction = null; if (p === '/records') loadSummary(); });
|
|
440
|
+
`
|
|
441
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scopes": ["orgschema.read", "mail.execute"],
|
|
3
|
+
"services": { "database": true, "files": true, "cache": false },
|
|
4
|
+
"menuIcon": "pi pi-exclamation-triangle",
|
|
5
|
+
"navigation": [
|
|
6
|
+
{ "path": "/", "label": { "tr": "Faaliyetlerim", "en": "My actions" } },
|
|
7
|
+
{ "path": "/report", "label": { "tr": "Uygunsuzluk bildir", "en": "Report a nonconformity" } },
|
|
8
|
+
{ "path": "/records", "label": { "tr": "Kayıtlar", "en": "Records" }, "permission": "{{permissionPrefix}}Manage" }
|
|
9
|
+
],
|
|
10
|
+
"permissions": [
|
|
11
|
+
{ "code": "{{permissionPrefix}}Manage", "name": { "tr": "Uygunsuzluk kayıtlarını ve düzeltici faaliyetleri yönet", "en": "Manage nonconformities and corrective actions" } }
|
|
12
|
+
],
|
|
13
|
+
"agent": {
|
|
14
|
+
"summary": {
|
|
15
|
+
"tr": "Uygunsuzluk ve düzeltici faaliyet uygulaması: kalite ve iş güvenliği uygunsuzlukları, kök nedenleri, sorumlulu ve terminli düzeltici faaliyetler.",
|
|
16
|
+
"en": "Nonconformity and corrective action app: quality and safety nonconformities, their root causes, corrective actions with owner and due date."
|
|
17
|
+
},
|
|
18
|
+
"tools": [
|
|
19
|
+
{
|
|
20
|
+
"name": "overdue-actions",
|
|
21
|
+
"title": { "tr": "Geciken faaliyetler", "en": "Overdue actions" },
|
|
22
|
+
"description": {
|
|
23
|
+
"tr": "Termini geçmiş ve tamamlanmamış düzeltici faaliyetleri sorumlu, termin, gecikme günü ve bağlı uygunsuzlukla döndürür; en çok geciken önce. Salt okunur.",
|
|
24
|
+
"en": "Returns open corrective actions past their due date with owner, due date, days overdue and the related nonconformity; most overdue first. Read-only."
|
|
25
|
+
},
|
|
26
|
+
"readOnly": true,
|
|
27
|
+
"permission": "{{permissionPrefix}}Manage",
|
|
28
|
+
"input": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"owner": { "type": "string", "minLength": 1, "maxLength": 80, "description": "Sorumlunun adında geçen ifade" },
|
|
32
|
+
"limit": { "type": "integer", "minimum": 1, "maximum": 50, "description": "En çok kaç faaliyet (varsayılan 20)" }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"name": "nonconformity-summary",
|
|
38
|
+
"title": { "tr": "Uygunsuzluk özeti", "en": "Nonconformity summary" },
|
|
39
|
+
"description": {
|
|
40
|
+
"tr": "Kayıtların duruma göre sayısını, açık kayıtların önem ve kaynağa göre dağılımını, geciken faaliyet sayısını ve son hatırlatma turunun sonucunu döndürür. Salt okunur.",
|
|
41
|
+
"en": "Returns record counts by status, open records by severity and source, the number of overdue actions and the result of the latest reminder run. Read-only."
|
|
42
|
+
},
|
|
43
|
+
"readOnly": true,
|
|
44
|
+
"permission": "{{permissionPrefix}}Manage",
|
|
45
|
+
"input": { "type": "object", "properties": {} }
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"jobs": {
|
|
50
|
+
"schedules": [
|
|
51
|
+
{ "name": "overdue-reminders", "cron": "0 8 * * *", "timeZone": "Europe/Istanbul", "description": "Termini geçmiş düzeltici faaliyetleri sorumlusuna günde en çok bir kez e-postayla hatırlatır" }
|
|
52
|
+
],
|
|
53
|
+
"queues": [],
|
|
54
|
+
"services": ["mail.execute"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Bu uygulamanın şablonu: ziyaretçi kaydı (`ziyaretci-kaydi`)
|
|
2
|
+
|
|
3
|
+
Uygulama `create-devoplus-one-app --template ziyaretci-kaydi` 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: `visitors` (ziyaret) + `retention_runs` (gece temizliği kaydı). Değişince `pnpm data:generate` |
|
|
9
|
+
| `src/app.ts` | Uçlar: ziyaretçi kaydı, giriş/çıkış/iptal eylemleri, bugünkü liste, ziyaretçilerim, özet |
|
|
10
|
+
| `src/visitor.ts` | Saf kurallar: **saklama süresi `RETENTION_DAYS` (90 gün)**, ziyaret günü, planlanan zaman, durum geçişleri + `visitor.test.ts` |
|
|
11
|
+
| `src/notice.ts` | Ziyaret edilen çalışana bildirim: e-posta + mobil bildirim (kanallar bağımsız) |
|
|
12
|
+
| `src/jobs.ts` | Gece işi `visitor-data-retention` (02:30): saklama süresi dolan ziyaretin kişisel bilgilerini siler |
|
|
13
|
+
| `src/shell.ts` | Sayfa: ziyaretçilerim, resepsiyon (form + bugünkü liste + eylemler), tüm kayıtlar |
|
|
14
|
+
| `src/agent.ts` · `src/queries.ts` · `AGENT_GUIDE.md` | Devoplus yapay zekâsının aracı (yalnız sayı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
|
+
- **Giriş ve çıkış zamanını SUNUCU yazar.** Kayıt uçları `status`, `checked_in_at`, `checked_out_at`, `visit_day`,
|
|
20
|
+
`anonymized_at` alanlarını istemciden ALMAZ (400); eylem uçları (`/check-in`, `/check-out`, `/cancel`) gövde almaz,
|
|
21
|
+
gövdede alan gelirse 400 döner. Geçiş tek koşullu güncellemedir (`WHERE status = <beklenen>`): ikinci giriş 409.
|
|
22
|
+
- **Ziyaretçilerim yalnız ziyaret edilen kişiye açıktır:** liste süzgeci (`host_org_id = ben`) sunucuda zorlanır,
|
|
23
|
+
tek kayıt okumada başkasının ziyaretçisi 404 alır. Kişi kimliği Organization Schema kimliğidir (`Users/me` →
|
|
24
|
+
`orgUserId`), oturum kimliği (`sub`) DEĞİL.
|
|
25
|
+
- **⛔ KVKK saklama süresi tek sabittir:** `RETENTION_DAYS` (`src/visitor.ts`). Gece işi son olayı bu süreden eski
|
|
26
|
+
ziyaretin kişisel alanlarını **ve arama kolonlarını** (`<alan>_fold`) boşaltır (`PERSONAL_FIELDS_SQL`, `src/jobs.ts`).
|
|
27
|
+
Ziyaretçiye ait yeni bir kişisel alan eklerseniz o listeye de ekleyin ve testini yazın; yalnız asıl alanı boşaltmak
|
|
28
|
+
bilgiyi arama kolonunda bırakır. Süreyi değiştirirseniz README'deki süreyi de değiştirin.
|
|
29
|
+
- **Telefon numarası ve kimlik numarası (TC kimlik) TOPLANMAZ.** Kullanıcı isterse önce kişisel veri sorumlusuna
|
|
30
|
+
danışmasını söyleyin; telefon eklenecekse Devoplus kuralı geçerlidir (doğrulama ve E.164 saklama).
|
|
31
|
+
- **Ziyaret silinmez** (405): gelmeyecek ziyaret iptal edilir; kişisel bilgiler saklama süresi sonunda silinir.
|
|
32
|
+
- **Yapay zekâ aracı kişisel bilgi döndürmez** (`count-visitors-today` yalnız sayılar). Ad döndüren bir araç eklemeyin.
|
|
33
|
+
- **Zamanlanmış iş kullanıcısızdır** ve yalnız uygulamanın veritabanını değiştirir; e-posta göndermez.
|
|
34
|
+
- Bildirim e-postası ve mobil bildirim kullanıcının Portal'daki süreli onayına bağlıdır (`mail.execute`,
|
|
35
|
+
`push.execute`); mobil bildirim platformda kapalıysa sonuç `unavailable` olur ve giriş yine kaydedilir.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# {{nameTr}}
|
|
2
|
+
|
|
3
|
+
Kuruma gelen ziyaretçilerin resepsiyonda kaydını tutan bir uygulama. Devoplus Portal'ın içinde, Portal menüsünden
|
|
4
|
+
açılır. Bu uygulama Devoplus One'ın **ziyaretçi kaydı** şablonundan üretildi; ihtiyacınıza göre değiştirilecek bir
|
|
5
|
+
başlangıç noktasıdır.
|
|
6
|
+
|
|
7
|
+
## Ne yapar
|
|
8
|
+
|
|
9
|
+
1. **Resepsiyon:** görevli ziyaretçiyi kaydeder (ad soyad, firma, ziyaret amacı, ziyaret edilen çalışan, planlanan
|
|
10
|
+
zaman). Ziyaretçi geldiğinde **Giriş yap**, ayrıldığında **Çıkış yap** düğmesine basılır; giriş ve çıkış zamanını
|
|
11
|
+
sistem yazar, elle girilemez. Gelmeyecek ziyaret iptal edilir. Ekranın üstünde bugün kaç ziyaretçinin beklendiği,
|
|
12
|
+
kaçının geldiği ve şu an kaç kişinin içeride olduğu yazar. Günün listesi Excel'e ya da CSV'ye aktarılabilir.
|
|
13
|
+
2. **Ziyaretçilerim:** her çalışan yalnız kendisini ziyarete gelen ve gelecek olan ziyaretçileri görür.
|
|
14
|
+
3. **Bildirim:** ziyaretçi giriş yaptığında ziyaret edilen çalışana e-posta gider; Portal'ın mobil bildirimi açıksa
|
|
15
|
+
telefonuna da bildirim düşer.
|
|
16
|
+
4. **Tüm kayıtlar:** resepsiyon bütün ziyaretleri arar, durumuna göre süzer ve Excel'e aktarır.
|
|
17
|
+
5. **Kişisel verilerin silinmesi (KVKK):** ziyaretin son olayından (çıkış, yoksa giriş, yoksa planlanan zaman)
|
|
18
|
+
**90 gün** sonra ziyaretçinin adı, firması ve ziyaret amacı her gece 02:30'da kendiliğinden silinir. Ziyaret sayıları
|
|
19
|
+
korunur ama kişi artık tanınamaz. Telefon numarası ve kimlik numarası hiç sorulmaz.
|
|
20
|
+
6. **Devoplus asistanı:** "Şu an binada kaç ziyaretçi var?" gibi sorular Devoplus'un yapay zekâ asistanına
|
|
21
|
+
sorulduğunda cevap bu uygulamadan gelir. Asistan ziyaretçilerin adlarını vermez, yalnız sayıları söyler.
|
|
22
|
+
|
|
23
|
+
## Kimler ne görür
|
|
24
|
+
|
|
25
|
+
| Kim | Ne yapabilir |
|
|
26
|
+
|---|---|
|
|
27
|
+
| Uygulamayı kullanma izni olan herkes ({{permissionCode}}) | Kendi ziyaretçilerini görür, Excel'e aktarır |
|
|
28
|
+
| Resepsiyon ({{permissionPrefix}}Manage) | Ziyaretçi kaydeder, giriş ve çıkış yapar, iptal eder, bütün kayıtları görür |
|
|
29
|
+
|
|
30
|
+
İzinleri portal yöneticisi **Yetki Yönetimi** ekranından verir. Resepsiyon ve Tüm kayıtlar sekmeleri yalnız resepsiyon
|
|
31
|
+
iznine sahip kişilere görünür.
|
|
32
|
+
|
|
33
|
+
## Neleri değiştirebilirsiniz
|
|
34
|
+
|
|
35
|
+
Yapay zekâ aracınıza iş diliyle söylemeniz yeterlidir:
|
|
36
|
+
|
|
37
|
+
- "Kişisel bilgiler 30 gün sonra silinsin" — saklama süresi tek bir yerde durur; kurumunuzun kişisel veri saklama
|
|
38
|
+
politikasına göre seçin.
|
|
39
|
+
- "Ziyaretçiye verilen kart numarası da yazılsın" — yeni alan.
|
|
40
|
+
- "Ziyaretçi kaydedildiğinde de çalışana haber verilsin" — yeni bildirim.
|
|
41
|
+
- "Giriş yapan ziyaretçi için yazdırılabilir bir ziyaretçi kartı olsun" — yeni rapor.
|
|
42
|
+
|
|
43
|
+
Bilinmesi gerekenler: bildirim, girişi yapan resepsiyon görevlisinin Portal'daki **"İzinleri incele"** düğmesiyle
|
|
44
|
+
e-posta (ve mobil bildirim) izni vermiş olmasını gerektirir; izin yoksa giriş yine kaydedilir ve ekran bildirimin
|
|
45
|
+
gitmediğini söyler. Mobil bildirim her portalda açık olmayabilir; kapalıysa yalnız e-posta gider. Telefon ya da kimlik
|
|
46
|
+
numarası eklemek isterseniz önce kurumunuzun kişisel veri sorumlusuna danışın.
|
|
@@ -0,0 +1,39 @@
|
|
|
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}}, kuruma gelen ziyaretçilerin resepsiyonda kaydını tutar: ziyaretçinin adı, firması, ziyaret amacı, ziyaret
|
|
10
|
+
edilen çalışan ve planlanan zaman. Resepsiyon görevlisi ziyaretçi geldiğinde giriş, ayrıldığında çıkış yapar; zamanları
|
|
11
|
+
sistem yazar. Giriş yapıldığında ziyaret edilen çalışana bildirim gider. Ziyaretçinin kişisel bilgileri saklama süresi
|
|
12
|
+
dolunca kendiliğinden silinir. Kullanıcı "bugün kaç ziyaretçi var", "şu an binada kaç ziyaretçi var" gibi şeyler
|
|
13
|
+
sorduğunda bu uygulamaya başvurun. Ziyaretçi kaydeden, giriş ya da çıkış yapan araç yoktur.
|
|
14
|
+
|
|
15
|
+
## Veri
|
|
16
|
+
|
|
17
|
+
- **Ziyaret** (`visitors`): ad soyad, firma, ziyaret amacı, ziyaret edilen çalışan, planlanan zaman, ziyaret günü,
|
|
18
|
+
durum (`expected` bekleniyor · `inside` içeride · `left` ayrıldı · `cancelled` iptal edildi), giriş ve çıkış zamanı.
|
|
19
|
+
- Telefon numarası ve kimlik numarası tutulmaz.
|
|
20
|
+
- Saklama süresi dolan ziyaretin adı, firması ve amacı silinir; sayılarda yer almaya devam eder.
|
|
21
|
+
- ⛔ Araçlar kişisel bilgi döndürmez: ziyaretçinin kim olduğunu soran kullanıcıyı uygulamanın ekranına yönlendirin.
|
|
22
|
+
|
|
23
|
+
## Araçlar
|
|
24
|
+
|
|
25
|
+
### `count-visitors-today` — bugünkü ziyaretçi sayıları (salt okunur)
|
|
26
|
+
|
|
27
|
+
- **Ne zaman:** kullanıcı bugün kaç ziyaretçi beklendiğini, kaçının geldiğini ya da şu an binada kaç ziyaretçi olduğunu
|
|
28
|
+
sorduğunda.
|
|
29
|
+
- **Girdi:** yok.
|
|
30
|
+
- **Çıktı:** `{ "day": "2026-10-20", "expected": 3, "checkedIn": 5, "left": 2, "insideNow": 4 }` — `expected` bugün
|
|
31
|
+
beklenip henüz gelmeyenler, `checkedIn` bugün giriş yapanlar, `left` bugün ayrılanlar, `insideNow` şu an içeride
|
|
32
|
+
olanlar (önceki günlerden çıkışı yapılmamış olanlar dahil). Gün Türkiye saatiyledir.
|
|
33
|
+
|
|
34
|
+
## Örnek istekler
|
|
35
|
+
|
|
36
|
+
- "Bugün kaç ziyaretçimiz var?" → `count-visitors-today` → `expected` + `checkedIn`
|
|
37
|
+
- "Şu an binada kaç ziyaretçi var?" → `count-visitors-today` → `insideNow`
|
|
38
|
+
- "Bugün gelen ziyaretçilerin adları neler?" → araç ad vermez; kullanıcıyı uygulamanın Resepsiyon sekmesine yönlendirin.
|
|
39
|
+
- "Ziyaretçimin girişini yap." → araç yok; resepsiyonun uygulamadan yapması gerekir.
|