{
  "info": {
    "_postman_id": "30785806-8118-4f9d-9871-fec555921d67",
    "name": "SILAPOR E-Ticketing API v1",
    "description": "REST API **SILAPOR E-Ticketing** — IT Service Desk FEB UNPAD (Laravel 13 + Passport 13).\n\n### Cara pakai\n1. Import koleksi ini **dan** salah satu environment (`Produksi` / `Lokal`), lalu pilih environment di pojok kanan atas.\n2. Isi variabel `email` & `password` (akun SIMANTAP yang terdaftar di divisi) serta `reporter_email` / `reporter_phone` untuk tujuan notifikasi uji.\n3. Jalankan folder **00 → 10 berurutan**, atau klik kanan koleksi → **Run collection**. Variabel ID (tiket, UUID, eskalasi, divisi, dsb.) terisi otomatis dari respons.\n\n### Catatan\n- Token Bearer disimpan di `access_token`; bila kosong, pre-request script login otomatis.\n- Email/WA/analisis AI diproses antrian: jalankan `php artisan queue:work` di server agar terkirim.\n- Folder 06 (AI) menerima 503 bila API key AI belum diisi — itu wajar.\n- Folder 08 memerlukan akun admin; user biasa akan mendapat 403 (diterima oleh test).\n- Uji ini **menulis data sungguhan** (tiket, penanganan, notifikasi). Gunakan akun uji dan `reporter_email` milik Anda sendiri.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{access_token}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Header Accept JSON untuk semua request (agar error Laravel berbentuk JSON)",
          "pm.request.headers.upsert({ key: 'Accept', value: 'application/json' });",
          "",
          "// Login otomatis bila access_token kosong",
          "const path = pm.request.url.getPath();",
          "const isPublic = path.includes('/auth/token') || path.includes('/public/') || (pm.request.auth && pm.request.auth.type === 'noauth');",
          "if (!isPublic && !pm.environment.get('access_token') && pm.environment.get('email')) {",
          "    pm.sendRequest({",
          "        url: pm.environment.get('base_url') + '/api/v1/auth/token',",
          "        method: 'POST',",
          "        header: { 'Content-Type': 'application/json', Accept: 'application/json' },",
          "        body: { mode: 'raw', raw: JSON.stringify({ email: pm.environment.get('email'), password: pm.environment.get('password'), device_name: 'postman-auto', scopes: ['tickets:read','tickets:write','handlings:write','reports:read','ai:use'] }) }",
          "    }, (err, res) => {",
          "        if (!err && res.code === 201) { pm.environment.set('access_token', res.json().data.access_token); console.log('Login otomatis berhasil'); }",
          "        else console.error('Login otomatis gagal', err || res.code, res && res.text());",
          "    });",
          "}",
          "",
          "// Tanggal periode default: 30 hari terakhir",
          "if (!pm.environment.get('date_from')) {",
          "    const pad = (n) => String(n).padStart(2, '0');",
          "    const f = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;",
          "    const to = new Date(); const from = new Date(); from.setDate(to.getDate() - 30);",
          "    pm.environment.set('date_from', f(from)); pm.environment.set('date_to', f(to));",
          "}"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "00. Autentikasi",
      "description": "Terbitkan Personal Access Token Passport. Token otomatis disimpan ke variabel `access_token` dan dipakai seluruh request (Auth: Bearer di level collection).",
      "item": [
        {
          "name": "Login – terbitkan token",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.access_token; if (v !== undefined && v !== null) { pm.environment.set('access_token', v); console.log('access_token =', v); } }",
                  "pm.test('Token tersedia', () => pm.expect(pm.response.json().data.access_token).to.be.a('string'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/auth/token",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "auth",
                "token"
              ]
            },
            "description": "Endpoint publik (10 req/menit). Kirim email & kata sandi akun SIMANTAP. Untuk akun yang hanya login via Google/PAuS, set kata sandi dulu melalui database/tinker.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{email}}\",\n  \"password\": \"{{password}}\",\n  \"device_name\": \"postman\",\n  \"scopes\": [\n    \"tickets:read\",\n    \"tickets:write\",\n    \"handlings:write\",\n    \"reports:read\",\n    \"ai:use\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Login – kredensial salah (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 422', () => pm.expect(pm.response.code).to.be.oneOf([422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Ada pesan error email', () => pm.expect(pm.response.json().errors).to.have.property('email'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/auth/token",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "auth",
                "token"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"{{email}}\",\n  \"password\": \"salah-password\",\n  \"device_name\": \"postman\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Tanpa token (401)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 401', () => pm.expect(pm.response.code).to.be.oneOf([401]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/me",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "me"
              ]
            },
            "description": "Membuktikan guard `auth:api` menolak request tanpa token.",
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Profil saya",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('user_id', v); console.log('user_id =', v); } }",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.devisi_id; if (v !== undefined && v !== null) { pm.environment.set('my_devisi_id', v); console.log('my_devisi_id =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/me",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "me"
              ]
            },
            "description": ""
          }
        }
      ]
    },
    {
      "name": "01. Referensi & Dashboard",
      "description": "Mengisi variabel ID otomatis dari data master (jenis, kategori, prioritas, eskalasi, divisi, gedung).",
      "item": [
        {
          "name": "Semua referensi (dropdown)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "const d = pm.response.json().data;",
                  "const first = (a) => (a && a.length ? a[0].id : null);",
                  "pm.environment.set('jenis_id', first(d.jenis_ticket));",
                  "pm.environment.set('kategori_id', first(d.kategori_ticket));",
                  "pm.environment.set('prioritas_id', first(d.prioritas));",
                  "pm.environment.set('prioritas_id_alt', d.prioritas && d.prioritas.length > 1 ? d.prioritas[1].id : first(d.prioritas));",
                  "pm.environment.set('gedung_id', first(d.gedung));",
                  "const myDiv = Number(pm.environment.get('my_devisi_id'));",
                  "const other = (d.devisi || []).find(x => x.id !== myDiv) || (d.devisi || [])[0];",
                  "if (other) pm.environment.set('devisi_tujuan_id', other.id);",
                  "const byKind = (k) => ((d.eskalasi || []).find(e => e.kind === k) || {}).id;",
                  "['handling','delegated','hold','resolved'].forEach(k => { const v = byKind(k); if (v) pm.environment.set('eskalasi_' + k + '_id', v); else console.warn('Eskalasi kind tidak ditemukan:', k, '— cek ET_ESKALASI_* di .env'); });",
                  "pm.test('Eskalasi Selesai terdeteksi', () => pm.expect(byKind('resolved'), 'set ET_ESKALASI_SELESAI_ID').to.exist);"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/references",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "references"
              ]
            },
            "description": "Di-cache 10 menit di server. Periksa field `kind` pada eskalasi: handling / delegated / hold / transferred / resolved."
          }
        },
        {
          "name": "Ruangan per gedung",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = (pm.response.json().data[0] || {}).id; if (v !== undefined && v !== null) { pm.environment.set('ruangan_id', v); console.log('ruangan_id =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/references/gedung/{{gedung_id}}/ruangan",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "references",
                "gedung",
                "{{gedung_id}}",
                "ruangan"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Daftar petugas",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/users?search=&devisi_id={{my_devisi_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "users"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "description": "Filter nama (opsional)",
                  "disabled": true
                },
                {
                  "key": "devisi_id",
                  "value": "{{my_devisi_id}}",
                  "description": "Filter divisi",
                  "disabled": true
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Statistik dashboard",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Ada KPI', () => pm.expect(pm.response.json().data).to.have.property('kpi'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/dashboard/stats?from={{date_from}}&to={{date_to}}&devisi_id=all",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "dashboard",
                "stats"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{date_from}}"
                },
                {
                  "key": "to",
                  "value": "{{date_to}}"
                },
                {
                  "key": "devisi_id",
                  "value": "all",
                  "description": "Hanya berlaku untuk admin",
                  "disabled": true
                }
              ]
            },
            "description": ""
          }
        }
      ]
    },
    {
      "name": "02. Tiket",
      "description": "CRUD tiket. Body memakai variabel dari folder 01 — jalankan folder 00 & 01 lebih dulu.",
      "item": [
        {
          "name": "Buat tiket (luar area FEB)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('ticket_id', v); console.log('ticket_id =', v); } }",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.uuid; if (v !== undefined && v !== null) { pm.environment.set('ticket_uuid', v); console.log('ticket_uuid =', v); } }",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.nomor_ticket; if (v !== undefined && v !== null) { pm.environment.set('ticket_nomor', v); console.log('ticket_nomor =', v); } }",
                  "pm.test('Nomor format YYYYMMDD.NNN', () => pm.expect(pm.response.json().data.nomor_ticket).to.match(/^\\d{8}\\.\\d{3,}$/));",
                  "pm.test('SLA terbentuk', () => pm.expect(pm.response.json().data.sla).to.not.be.null);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets"
              ]
            },
            "description": "Memicu: nomor tiket terkunci, SLA, notifikasi divisi (Pusher), email & WA ke pelapor (antrian — jalankan `php artisan queue:work`).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jenis_ticket_id\": \"{{jenis_id}}\",\n  \"kategori_ticket_id\": \"{{kategori_id}}\",\n  \"prioritas_id\": \"{{prioritas_id}}\",\n  \"nama_pelapor\": \"Pelapor Uji Postman\",\n  \"email_pelapor\": \"{{reporter_email}}\",\n  \"phone_pelapor\": \"{{reporter_phone}}\",\n  \"deskripsi_pengaduan\": \"Uji Postman: akses internet di ruang rapat lantai 2 terputus sejak pagi, perangkat AP tidak menyala.\",\n  \"nomor_surat\": null,\n  \"assignees\": [\n    \"{{user_id}}\"\n  ],\n  \"status_site\": \"outsite\",\n  \"event_location_outsite_name\": \"Gedung Rektorat Unpad Jatinangor\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Buat tiket (dalam area FEB)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201 / 422', () => pm.expect(pm.response.code).to.be.oneOf([201, 422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('ticket_id_inside', v); console.log('ticket_id_inside =', v); } }",
                  "if (pm.response.code === 422) console.warn('Tidak ada data ruangan — isi tabel tbaset_ruangan_gedung atau abaikan.');"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets"
              ]
            },
            "description": "Butuh `ruangan_id` dari request 'Ruangan per gedung'.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jenis_ticket_id\": \"{{jenis_id}}\",\n  \"kategori_ticket_id\": \"{{kategori_id}}\",\n  \"prioritas_id\": \"{{prioritas_id}}\",\n  \"nama_pelapor\": \"Pelapor Uji Postman\",\n  \"email_pelapor\": \"{{reporter_email}}\",\n  \"phone_pelapor\": \"{{reporter_phone}}\",\n  \"deskripsi_pengaduan\": \"Uji Postman: proyektor ruang kelas tidak menampilkan gambar dari HDMI.\",\n  \"nomor_surat\": null,\n  \"assignees\": [\n    \"{{user_id}}\"\n  ],\n  \"status_site\": \"inside\",\n  \"ruangan_gedung_id\": \"{{ruangan_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Buat tiket – validasi gagal (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 422', () => pm.expect(pm.response.code).to.be.oneOf([422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "const e = pm.response.json().errors;",
                  "pm.test('Field wajib dilaporkan', () => ['jenis_ticket_id','prioritas_id','nama_pelapor','deskripsi_pengaduan','assignees','ruangan_gedung_id'].forEach(f => pm.expect(e).to.have.property(f)));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status_site\": \"inside\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Daftar tiket",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Ada meta paginasi', () => pm.expect(pm.response.json().meta).to.have.property('total'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets?from={{date_from}}&to={{date_to}}&per_page=15&page=1&search=&mine=1&prioritas_id={{prioritas_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{date_from}}"
                },
                {
                  "key": "to",
                  "value": "{{date_to}}"
                },
                {
                  "key": "per_page",
                  "value": "15"
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Nomor / deskripsi / pelapor",
                  "disabled": true
                },
                {
                  "key": "mine",
                  "value": "1",
                  "description": "Hanya tugas saya",
                  "disabled": true
                },
                {
                  "key": "prioritas_id",
                  "value": "{{prioritas_id}}",
                  "description": "",
                  "disabled": true
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Daftar tiket – filter status",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets?from={{date_from}}&to={{date_to}}&status[]=0&status[]=2",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{date_from}}"
                },
                {
                  "key": "to",
                  "value": "{{date_to}}"
                },
                {
                  "key": "status[]",
                  "value": "0",
                  "description": "0 Open"
                },
                {
                  "key": "status[]",
                  "value": "2",
                  "description": "2 In Progress"
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Detail tiket",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Ada flag hak akses (can)', () => pm.expect(pm.response.json().data.can).to.be.an('object'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Perbarui tiket",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"deskripsi_pengaduan\": \"Uji Postman (diperbarui): akses internet ruang rapat lantai 2 terputus, AP mati total.\",\n  \"phone_pelapor\": \"{{reporter_phone}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Ubah prioritas",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/priority",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "priority"
              ]
            },
            "description": "Deadline SLA dihitung ulang dari waktu tiket dibuat.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"prioritas_id\": \"{{prioritas_id_alt}}\",\n  \"reason\": \"Penyesuaian dampak layanan (uji Postman)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Kelola pelaksana tugas",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/assignees",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "assignees"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"assignees\": [\n    \"{{user_id}}\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "03. Penanganan (alur ITSM)",
      "description": "Jalankan berurutan untuk menguji alur: first response → In Progress, Ditunda (SLA berhenti), dilanjutkan (deadline diperpanjang), Didelegasikan, lalu Selesai → CLOSED.",
      "item": [
        {
          "name": "1. Sedang ditangani (respons pertama)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('handling_id', v); console.log('handling_id =', v); } }",
                  "pm.sendRequest({ url: pm.environment.get('base_url') + '/api/v1/tickets/' + pm.environment.get('ticket_id'), header: { Accept: 'application/json', Authorization: 'Bearer ' + pm.environment.get('access_token') } }, (err, res) => { if (!err) pm.test('Status tiket sekarang: in_progress', () => pm.expect(res.json().data.status.key).to.eql('in_progress')); });"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_handling_id}}\",\n  \"deskripsi_penanganan\": \"Pengecekan awal: AP di ruang rapat tidak mendapat daya dari switch PoE.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Riwayat penanganan",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Perbarui tindakan",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/handlings/{{handling_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "handlings",
                "{{handling_id}}"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_handling_id}}\",\n  \"deskripsi_penanganan\": \"Pengecekan awal (revisi): port PoE switch lantai 2 mati, AP tidak mendapat daya.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "2. Ditunda (jam SLA berhenti)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.sendRequest({ url: pm.environment.get('base_url') + '/api/v1/tickets/' + pm.environment.get('ticket_id'), header: { Accept: 'application/json', Authorization: 'Bearer ' + pm.environment.get('access_token') } }, (err, res) => { if (!err) pm.test('SLA on_hold = true', () => pm.expect(res.json().data.sla.on_hold).to.eql(true)); });"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_hold_id}}\",\n  \"deskripsi_penanganan\": \"Menunggu pengadaan adaptor PoE injector dari bagian sarana.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "3. Dilanjutkan (SLA berjalan lagi)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_handling_id}}\",\n  \"deskripsi_penanganan\": \"Adaptor tersedia, pemasangan PoE injector dimulai.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "4. Delegasi tanpa divisi (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 422', () => pm.expect(pm.response.code).to.be.oneOf([422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('devisi_id & reason wajib', () => { const e = pm.response.json().errors; pm.expect(e).to.have.property('devisi_id'); pm.expect(e).to.have.property('reason'); });"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_delegated_id}}\",\n  \"deskripsi_penanganan\": \"Perlu bantuan unit lain.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "5. Didelegasikan ke divisi lain",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "Divisi tujuan menerima notifikasi realtime `ticket.assigned` (type: delegated).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_delegated_id}}\",\n  \"deskripsi_penanganan\": \"Pemasangan kabel baru ke plafon memerlukan tim sarana.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\",\n  \"devisi_id\": \"{{devisi_tujuan_id}}\",\n  \"reason\": \"Pekerjaan instalasi kabel di plafon (uji Postman)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Tindakan untuk dihapus",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('handling_id_delete', v); console.log('handling_id_delete =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_handling_id}}\",\n  \"deskripsi_penanganan\": \"Catatan uji yang akan dihapus.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Hapus tindakan",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/handlings/{{handling_id_delete}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "handlings",
                "{{handling_id_delete}}"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "6. Selesai (tiket CLOSED)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.sendRequest({ url: pm.environment.get('base_url') + '/api/v1/tickets/' + pm.environment.get('ticket_id'), header: { Accept: 'application/json', Authorization: 'Bearer ' + pm.environment.get('access_token') } }, (err, res) => { if (!err) pm.test('Status tiket sekarang: closed', () => pm.expect(res.json().data.status.key).to.eql('closed')); });"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/handlings",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "handlings"
              ]
            },
            "description": "Memicu email + WA ke pelapor dan analisis AI di antrian (bila ET_AI_AUTO_LEARN=true).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eskalasi_id\": \"{{eskalasi_resolved_id}}\",\n  \"deskripsi_penanganan\": \"AP kembali menyala, koneksi internet ruang rapat normal dan sudah diuji bersama pelapor.\",\n  \"url_google_drive_data_dukung\": \"https://drive.google.com/drive/folders/contoh-data-dukung\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "04. Status & pemberitahuan pelapor",
      "description": "Kirim ulang pemberitahuan (hanya tiket selesai) dan transisi status manual.",
      "item": [
        {
          "name": "Kirim ulang email ke pelapor",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/notify/email",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "notify",
                "email"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Kirim ulang WhatsApp ke pelapor",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 422', () => pm.expect(pm.response.code).to.be.oneOf([200, 422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/notify/whatsapp",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "notify",
                "whatsapp"
              ]
            },
            "description": "422 bila tiket tidak memiliki nomor WA."
          }
        },
        {
          "name": "Buka kembali (Closed → Open)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Status open', () => pm.expect(pm.response.json().data.status.key).to.eql('open'));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "status"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": 0,\n  \"reason\": \"Pelapor melaporkan gangguan muncul lagi (uji Postman)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Tandai Resolved",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "status"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": 3,\n  \"reason\": \"Solusi diberikan, menunggu konfirmasi\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Tutup kembali (Closed)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Status closed', () => pm.expect(pm.response.json().data.status.key).to.eql('closed'));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "status"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": 1,\n  \"reason\": \"Dikonfirmasi pelapor\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Status tidak valid (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 422', () => pm.expect(pm.response.code).to.be.oneOf([422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}",
                "status"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": 9\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "05. Notifikasi divisi",
      "description": "Bel notifikasi di header workdesk.",
      "item": [
        {
          "name": "Daftar notifikasi",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = (pm.response.json().data[0] || {}).id; if (v !== undefined && v !== null) { pm.environment.set('notification_id', v); console.log('notification_id =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/notifications",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "notifications"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Jumlah belum dibaca",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/notifications/unread-count",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "notifications",
                "unread-count"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Tandai satu dibaca",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 403 / 404', () => pm.expect(pm.response.code).to.be.oneOf([200, 403, 404]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/notifications/{{notification_id}}/read",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "notifications",
                "{{notification_id}}",
                "read"
              ]
            },
            "description": "403 bila notifikasi milik divisi lain; 404 bila belum ada notifikasi."
          }
        },
        {
          "name": "Tandai semua dibaca",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/notifications/read-all",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "notifications",
                "read-all"
              ]
            },
            "description": ""
          }
        }
      ]
    },
    {
      "name": "06. AI (Laravel AI SDK)",
      "description": "Butuh GEMINI_API_KEY (atau provider lain) di .env. 503 = layanan AI tidak tersedia/dinonaktifkan. Batas 10 aksi/menit/user.",
      "item": [
        {
          "name": "Rapikan teks (rewrite)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 503', () => pm.expect(pm.response.code).to.be.oneOf([200, 503]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code === 200) console.log(pm.response.json().data.text);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/rewrite",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "rewrite"
              ]
            },
            "description": "Butuh GEMINI_API_KEY (atau provider lain) di .env. 503 = layanan AI tidak tersedia/dinonaktifkan. Batas 10 aksi/menit/user.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"text\": \"internet diruang rapat lt 2 mati dr pagi, ap nya ga nyala, tolong dicek segera\",\n  \"mode\": \"ticket\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Snapshot metrik SLA (tanpa AI)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/snapshot",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "snapshot"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Prediksi risiko SLA",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201 / 503', () => pm.expect(pm.response.code).to.be.oneOf([201, 503]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/prediction",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "prediction"
              ]
            },
            "description": "Butuh GEMINI_API_KEY (atau provider lain) di .env. 503 = layanan AI tidak tersedia/dinonaktifkan. Batas 10 aksi/menit/user."
          }
        },
        {
          "name": "Riwayat prediksi",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/predictions",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "predictions"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Analisis efisiensi kerja (WES)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201 / 503', () => pm.expect(pm.response.code).to.be.oneOf([201, 503]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/efficiency",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "efficiency"
              ]
            },
            "description": "Butuh GEMINI_API_KEY (atau provider lain) di .env. 503 = layanan AI tidak tersedia/dinonaktifkan. Batas 10 aksi/menit/user."
          }
        },
        {
          "name": "Hasil efisiensi terakhir",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/efficiency",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "efficiency"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Susun knowledge base + RCA",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201 / 503', () => pm.expect(pm.response.code).to.be.oneOf([201, 503]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('kb_id', v); console.log('kb_id =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/knowledge-base",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "knowledge-base"
              ]
            },
            "description": "Butuh GEMINI_API_KEY (atau provider lain) di .env. 503 = layanan AI tidak tersedia/dinonaktifkan. Batas 10 aksi/menit/user."
          }
        },
        {
          "name": "Knowledge base tiket ini",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = (pm.response.json().data || {}).id; if (v !== undefined && v !== null) { pm.environment.set('kb_id', v); console.log('kb_id =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/tickets/{{ticket_id}}/knowledge-base",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "tickets",
                "{{ticket_id}}",
                "knowledge-base"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Daftar knowledge base",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/knowledge-base?search=&per_page=15",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "knowledge-base"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "per_page",
                  "value": "15"
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Detail knowledge base",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 404', () => pm.expect(pm.response.code).to.be.oneOf([200, 404]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/ai/knowledge-base/{{kb_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "ai",
                "knowledge-base",
                "{{kb_id}}"
              ]
            },
            "description": ""
          }
        }
      ]
    },
    {
      "name": "07. Laporan (unduh)",
      "description": "Gunakan tombol **Send ▾ → Send and Download** untuk menyimpan berkas.",
      "item": [
        {
          "name": "Excel rekap (3 sheet)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Berkas biner diterima', () => pm.expect(pm.response.headers.get('Content-Type')).to.match(/(pdf|spreadsheet|octet-stream)/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/reports/excel?from={{date_from}}&to={{date_to}}&devisi_id=all",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "reports",
                "excel"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{date_from}}"
                },
                {
                  "key": "to",
                  "value": "{{date_to}}"
                },
                {
                  "key": "devisi_id",
                  "value": "all",
                  "description": "Admin saja",
                  "disabled": true
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "PDF rekap (A4 landscape)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Berkas biner diterima', () => pm.expect(pm.response.headers.get('Content-Type')).to.match(/(pdf|spreadsheet|octet-stream)/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/reports/pdf?from={{date_from}}&to={{date_to}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "reports",
                "pdf"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{date_from}}"
                },
                {
                  "key": "to",
                  "value": "{{date_to}}"
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "PDF capaian kinerja harian",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "pm.test('Berkas biner diterima', () => pm.expect(pm.response.headers.get('Content-Type')).to.match(/(pdf|spreadsheet|octet-stream)/));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/reports/tickets/{{ticket_uuid}}/pdf",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "reports",
                "tickets",
                "{{ticket_uuid}}",
                "pdf"
              ]
            },
            "description": ""
          }
        }
      ]
    },
    {
      "name": "08. Master data (admin)",
      "description": "Hanya Admin / Admin Helpdesk (`is_admin` atau `is_admin_helpdesk` = 1). User biasa mendapat 403.",
      "item": [
        {
          "name": "Skema master data",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/master/schema",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "master",
                "schema"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Daftar kategori tiket",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 403', () => pm.expect(pm.response.code).to.be.oneOf([200, 403]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/master/kategori-ticket",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "master",
                "kategori-ticket"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Tambah kategori",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201 / 403', () => pm.expect(pm.response.code).to.be.oneOf([201, 403]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('master_id', v); console.log('master_id =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/master/kategori-ticket",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "master",
                "kategori-ticket"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"nama_kategori_ticket\": \"Uji Postman\",\n  \"kode\": \"UJI\",\n  \"deskripsi_kategori_ticket\": \"Dibuat dari koleksi Postman\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Ubah kategori",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 403 / 404', () => pm.expect(pm.response.code).to.be.oneOf([200, 403, 404]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/master/kategori-ticket/{{master_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "master",
                "kategori-ticket",
                "{{master_id}}"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"nama_kategori_ticket\": \"Uji Postman (ubah)\",\n  \"kode\": \"UJI2\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Tambah prioritas – validasi SLA (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 422 / 403', () => pm.expect(pm.response.code).to.be.oneOf([422, 403]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/master/prioritas",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "master",
                "prioritas"
              ]
            },
            "description": "resolution_time_minutes harus ≥ response_time_minutes.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"nama_prioritas\": \"UJI\",\n  \"response_time_minutes\": 120,\n  \"resolution_time_minutes\": 60\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Hapus kategori",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 / 403 / 404 / 409', () => pm.expect(pm.response.code).to.be.oneOf([200, 403, 404, 409]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/master/kategori-ticket/{{master_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "master",
                "kategori-ticket",
                "{{master_id}}"
              ]
            },
            "description": ""
          }
        }
      ]
    },
    {
      "name": "09. Publik (tanpa login)",
      "description": "Halaman pantau pelapor via tautan UUID. Batas 30 req/menit/IP.",
      "item": [
        {
          "name": "Pantau tiket",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "const em = pm.environment.get('reporter_email'); if (em) pm.test('Tidak membocorkan email pelapor', () => pm.expect(JSON.stringify(pm.response.json().data)).to.not.include(em));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/public/tickets/{{ticket_uuid}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "public",
                "tickets",
                "{{ticket_uuid}}"
              ]
            },
            "description": "",
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Kirim penilaian (rating 5)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200 (reCAPTCHA nonaktif) / 422 (reCAPTCHA aktif)', () => pm.expect(pm.response.code).to.be.oneOf([200, 422]));",
                  "if (pm.response.code === 422) pm.test('Ditolak karena reCAPTCHA', () => pm.expect(pm.response.json().errors).to.have.property('recaptcha_token'));",
                  "if (pm.response.code === 200) pm.test('Feedback tersimpan', () => pm.expect(pm.response.json().data.feedback.rating).to.eql(5));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/public/tickets/{{ticket_uuid}}/feedback",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "public",
                "tickets",
                "{{ticket_uuid}}",
                "feedback"
              ]
            },
            "description": "Dilindungi reCAPTCHA v3 (aksi `eticketing_feedback`). Dari Postman tidak ada token, sehingga respons 422 `recaptcha_token` adalah perilaku yang BENAR bila RECAPTCHA_ENABLED=true. Untuk menguji penyimpanan penilaian, set RECAPTCHA_ENABLED=false sementara di lingkungan lokal/staging.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"rating\": 5,\n  \"message\": \"Penanganan cepat dan jelas. (uji Postman)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Penilaian tidak valid (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 422', () => pm.expect(pm.response.code).to.be.oneOf([422]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/public/tickets/{{ticket_uuid}}/feedback",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "public",
                "tickets",
                "{{ticket_uuid}}",
                "feedback"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"rating\": 7\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "UUID tidak dikenal (404)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 404', () => pm.expect(pm.response.code).to.be.oneOf([404]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/public/tickets/00000000-0000-4000-8000-000000000000",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "public",
                "tickets",
                "00000000-0000-4000-8000-000000000000"
              ]
            },
            "description": "",
            "auth": {
              "type": "noauth"
            }
          }
        }
      ]
    },
    {
      "name": "10. Pembersihan",
      "description": "Hapus tiket hanya bisa oleh pembuat & saat status Open.",
      "item": [
        {
          "name": "Buat tiket untuk dihapus",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 201', () => pm.expect(pm.response.code).to.be.oneOf([201]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code < 300) { const v = pm.response.json().data.id; if (v !== undefined && v !== null) { pm.environment.set('ticket_id_delete', v); console.log('ticket_id_delete =', v); } }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"jenis_ticket_id\": \"{{jenis_id}}\",\n  \"kategori_ticket_id\": \"{{kategori_id}}\",\n  \"prioritas_id\": \"{{prioritas_id}}\",\n  \"nama_pelapor\": \"Pelapor Uji Postman\",\n  \"email_pelapor\": \"{{reporter_email}}\",\n  \"phone_pelapor\": \"{{reporter_phone}}\",\n  \"deskripsi_pengaduan\": \"Tiket uji Postman yang akan dihapus.\",\n  \"nomor_surat\": null,\n  \"assignees\": [\n    \"{{user_id}}\"\n  ],\n  \"status_site\": \"outsite\",\n  \"event_location_outsite_name\": \"Gedung Rektorat Unpad Jatinangor\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Hapus tiket",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id_delete}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id_delete}}"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Hapus tiket CLOSED ditolak (403)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 403', () => pm.expect(pm.response.code).to.be.oneOf([403]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/tickets/{{ticket_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "tickets",
                "{{ticket_id}}"
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Cabut token (logout)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Status 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "const isJson = (pm.response.headers.get('Content-Type') || '').includes('json');",
                  "if (isJson && pm.response.code < 400) pm.test('success = true', () => pm.expect(pm.response.json().success).to.eql(true));",
                  "if (pm.response.code === 200) pm.environment.unset('access_token');"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/auth/token",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "auth",
                "token"
              ]
            },
            "description": "Token dicabut; request berikutnya akan login ulang otomatis via pre-request script."
          }
        }
      ]
    }
  ]
}