Anda dapat melihat riwayat servis, mengunduh invoice lama, dan mengecek saldo referral dengan memasukkan Nomor HP Anda.
AC BDG Express
Rekam Medis & Riwayat Perawatan AC
AC BDG Express
Pilih jamnya, teknisi kami datang.
Booking servis AC di Bandung. Isi tiga langkah di bawah, konfirmasinya kami kirim lewat WhatsApp.
Pilih jam sendiri
Konfirmasi lewat WhatsApp
Jadwal bisa diubah
Booking terkirim
Layanan
-
Tanggal
-
Jam
-
WhatsApp
-
Manajemen Jadwal & Booking
Info Integrasi: Jadwal yang dibuat di sini akan otomatis terhubung dengan database Invoice / Penawaran dan akan muncul di HP Teknisi. Tips: di tampilan Minggu / Hari, tarik jadwal ke atas / bawah untuk ubah jam, tarik pinggir bawahnya untuk ubah durasi, atau tarik ke kolom hari lain untuk pindah tanggal. (HP: tahan sebentar lalu geser)
Dashboard Ringkasan
Waktu Sistem (Admin)
00:00:00
Memuat tanggal...
Terminal Absensi Harian
Sisa Kasbon Pribadi
Rp 0
Karyawan Aktif
0
Piutang Kasbon
Rp 0
Estimasi Beban Gaji
Rp 0
Saldo Cash Saat Ini
Rp 0
Total Jadwal (Bulan Ini)
0
Selesai (Bulan Ini)
0
Jadwal Hari Ini
0
Pemasukan Hari Ini
Rp 0
Pengeluaran Hari Ini
Rp 0
Total Piutang (Belum Lunas)
Rp 0
Produk & Jasa Terlaris
Berdasarkan qty pada Invoice Lunas (Bulan Ini)
Pengeluaran Terbanyak
Rincian kategori pengeluaran (Bulan Ini)
Daftar Tugas Saya
Tanggal
Pelanggan / Alamat
Status Tagihan
Aksi
Kalender Jadwal Tugas (Semua Teknisi)
Tracking Map Teknisi
Pantauan Live Lokasi Karyawan (Hari Ini)
Estimasi BBM & Jarak Tempuh Teknisi
Dihitung otomatis dari jejak GPS live yang terekam hari ini
Teknisi
Jarak Hari Ini
Estimasi BBM
Estimasi Biaya
Rute
Memuat data jejak GPS...
Produktivitas Teknisi
Berdasarkan total revenue (Rp) Invoice Lunas bulan ini
Produktivitas Marketing & Order
Jumlah order/jadwal yang masuk per hari & per bulan
Order Hari Ini
0
Order Bulan Ini
0
Rata-rata / Hari
0
Nilai Order Bulan Ini
Rp 0
Marketing / CS
Hari Ini
Bulan Ini
Selesai/Lunas
Nilai Order
Revenue Harian Teknisi
Berdasarkan Invoice Lunas hari ini
Total Revenue Hari Ini (Semua Teknisi)Rp 0
Arus Kas Tahunan ()
CRM Auto-Reminder (>90 Hari)
0 Pelanggan
Pelanggan di bawah ini sudah lebih dari 3 bulan belum melakukan servis AC sejak transaksi terakhir mereka. Klik Auto Blast untuk mengirim WA otomatis ke semua pelanggan, atau klik tombol WA manual. Sistem juga otomatis mengirim reminder ini 1x/hari secara diam-diam saat Admin/Owner/Marketing membuka Dashboard, tanpa perlu klik tombol.
Pelanggan / WA
Servis Terakhir
Jeda Waktu
Aksi Follow-up
Papan Peringkat (Bulan Ini)
Penilaian berdasarkan performa bulan berjalan (Hadir Tepat Waktu = 5 Pts | eReport = 10 Pts | Revenue Rp 100rb = 1 Pts). Poin dihitung per bulan dan reset tiap bulan; capai 1000 Pts dalam 1 bulan untuk kenaikan gaji otomatis.
Peringkat
Nama Teknisi
Hadir Tepat
eReport
Revenue
Total Poin
Status Kehadiran Harian
Tanggal
Nama Karyawan
Jam Masuk
Jam Keluar
Lembur
Status Indikator
Nama Karyawan
KARYAWAN
Info Radius: Absen wajib dilakukan dalam jarak 200 Meter dari Kantor.
Waktu Saat Ini
00:00:00
Memuat tanggal...
Live Tracking
Mati (Hanya saat absen)
Gaji Terkumpul
Rp 0
Sisa Kasbon
Rp 0
Terminal Absensi
Kalender Jadwal Saya
Peringkat Bulan Ini
Bonus Juara 1: Bertahan di peringkat pertama hingga akhir bulan untuk mendapatkan Ekstra Bonus Rp 250.000 ke Slip Gaji!
Rank
Teknisi
Hadir Tepat
eReport
Revenue
Total Poin
Tugas Saya
Tanggal
Pelanggan
Status
Aksi
Live Ops Map
Semua teknisi yang sedang bertugas hari ini, dipantau real-time dalam satu peta besar. Enak dipantau dari HP Owner.
Auto-update 15 detik
Memuat...
Status: Menuju Lokasi Sedang Kerja Selesai Standby
Status dihitung otomatis dari GPS live teknisi, jadwal tugas hari ini, dan eReport yang sudah diisi.
Heatmap Sebaran Pelanggan
Area mana paling padat order? Berguna untuk strategi marketing atau menentukan lokasi cabang baru.
Belum dimuat.
Titik merah/kuning menunjukkan area dengan kepadatan pelanggan tertinggi. Alamat pelanggan digeocode otomatis lalu disimpan (cache) agar tidak diproses ulang tiap dibuka.
Zona Wilayah Teknisi
Bagi kota jadi beberapa zona & tetapkan "wilayah utama" tiap teknisi. Order baru otomatis disarankan ke teknisi yang wilayahnya cocok.
Klik di peta kosong untuk menambah zona baru. Untuk zona yang sudah ada, langsung geser pin-nya di peta untuk memindahkan lokasinya — bisa berapa pun jumlah zonanya sekaligus.
Tambah Zona Baru
Belum dipilih — klik di peta atau cari alamat.
Nama Zona
Radius
Teknisi Wilayah
Aksi
Tetapkan "Zona Wilayah" tiap teknisi di menu Pegawai. Saat membuat jadwal baru di Jadwal & Booking, sistem otomatis mendeteksi zona dari alamat pelanggan dan menyarankan teknisi wilayah tersebut.
Inventory & Stok
Kode
Nama Barang
Kategori
Stok
Aksi
Peralatan Teknisi
Kode Aset
Nama Tool
Peminjam
Status
Aksi
Peralatan Anda
Kode
Peralatan
Kondisi
Tgl Pinjam
Direktori Pegawai
Karyawan
Akun
Gaji/Lembur
Aksi
Produk & Jasa
Kode
Item
Kategori
Harga
Aksi
Kontak Pelanggan
Pelanggan
No. WhatsApp
Alamat
Info Servis
Kode Referral
Saldo
Aksi
Invoice & Penawaran
Tanggal
Pelanggan
Status
Total
Aksi
Buku Kas
Total Kas: Rp 0
Pemasukan (Bulan Ini)
Rp 0
Pengeluaran (Bulan Ini)
Rp 0
Tanggal
Tipe
Keterangan
Nominal & Saldo
Aksi
Kasbon Karyawan
Pegawai
Total Pinjam
Total Dibayar
Sisa
Payroll
Pegawai
Pendapatan
Potongan
THP
Rekap eReport
Tanggal
Pelanggan
Teknisi
Pekerjaan
Aksi
Rekap Rating & Ulasan Pelanggan
Teknisi
Rata-rata Rating
Jumlah Ulasan
Distribusi
Lacak Komplain
0 Komplain
Rekap mengikuti bulan yang sedang dipilih di halaman ini (lihat pemilih bulan di Dashboard/Payroll). Tandai sebuah invoice sebagai "Komplain" lewat form Invoice untuk mulai tercatat di sini.
Ranking Teknisi yang Kerjaannya Paling Sering Dikomplain (Bulan Ini)
Teknisi (Pengerjaan Asal)
Jumlah Komplain
Detail Jadwal Komplain
Tgl Komplain
Pelanggan
Invoice Asal
Teknisi Asal (Dikomplain)
Ditangani Oleh
Catatan
Pengajuan Izin & Sakit
Tanggal
Karyawan
Jenis
Status
Aksi
Rekap Tidak Hadir (Bulan Ini)
Tanggal
Karyawan
Keterangan
Riwayat Izin & Sakit Saya
Tanggal
Jenis
Status
Aksi
Kotak Pesan
Tanggal
Dari/Ke
Subjek
Status
Aksi
Live TikTok
Host AI membaca komentar penonton dan menjawab dari data harga & jadwal layanan.
Host AIHalo kak, silakan tanya soal service & harga AC ya.
1. Data yang dibaca host
Tambahan file harga AC / kalender layanan (txt, csv, json, md).
2. Sumber komentar
Tes ekspresi:
Saat penonton kirim gift: host berterima kasih, joget, musik main, dan ada efek hati. Relay harus mengirim {"type":"gift","user":"nama","gift":"Mawar","count":1}.
Suara paling natural: buka aplikasi di Microsoft Edge lalu pilih suara berlabel Online (Natural) (mis. Gadis/Ardi). Di Chrome pilih Google Bahasa Indonesia.
Belum tersambung.
Log
Kantor AI
Semua pegawai di aplikasi ada di kantor virtual ini. Teknisi & helper duduk di Ruang Teknisi (sayap timur) saat tidak ada jadwal, dan hilang dari kantor saat ada jadwal hari ini yang belum selesai. Klik agen lalu ketik pertanyaan: agen membaca data aplikasi sesuai role kamu (perlu API key, lihat tombol API Key agen).
Menyiapkan kantor…
Tarik Saldo Cashback
Info: Minimal penarikan saldo adalah Rp 50.000. Dana akan ditransfer oleh Admin maksimal 2x24 jam kerja.
Acbandungexpress
Jalan Wibawa mukti 3 no III
088222666188
acbandungexpress@gmail.com
https://acbandungexpres.com
INVOICE
Kepada
Nama Pelanggan
Alamat
+62 000-0000-000
INV-12345678
2026-05-17
Draft
No
Item / Jasa
Qty
Harga
Jumlah
Catatan & Garansi
-
Informasi Pembayaran
Bank BCA: 3372437780
a/n PT WIJAYA TEKNIK EXPRESS
Subtotal
Rp 0
Diskon
- Rp 0
Pajak (0%)
+ Rp 0
TOTAL
Rp 0
Dibayar (DP)
- Rp 0
SISA TAGIHAN
Rp 0
Pelanggan,
Hormat Kami,
AC BDG Express
Laporan Pekerjaan (eReport)
Acbandungexpress
jalan wibawa mukti 3 no III
088222666188
Pelanggan
:
Tanggal
:
Teknisi
:
Ref Invoice
:
ID / QR AC Pintar
: -
Kondisi Unit
: Normal
Spesifikasi Unit AC
Ruangan:
Merk:
Tipe:
PK:
Freon:
Parameter Teknis
Parameter
Before
After
Tekanan Freon
Amper
Suhu
Rincian & Tindakan
Pekerjaan:
Kondisi Sebelum:
Kondisi Sesudah:
Saran:
Keterangan Internal:
Dokumentasi Foto
Pelanggan,
Mengetahui / Hormat Kami,
Teknisi AC BDG Express
AC BDG EXPRESS
Spesialis Perawatan & Perbaikan AC Bandung Raya
Website: acbandungexpres.com
BERITA ACARA SERAH TERIMA PEKERJAAN
No. Invoice / BA
: -
Tanggal
: -
Pelanggan
: -
Teknisi
: -
No. Handphone
: -
Alamat Lokasi
: -
Bersama ini menerangkan bahwa kami telah melaksanakan dan menyelesaikan pekerjaan dengan rincian hasil pengecekan teknisi sebagai berikut:
No
Ruangan & Unit AC
Jenis Pekerjaan
Pengecekan Parameter AC
Keterangan / Saran
Sebelum (Before)
Sesudah (After)
Demikian Berita Acara Serah Terima Pekerjaan ini dibuat dengan sebenar-benarnya. AC telah dilakukan pengetesan bersama di lokasi dan dinyatakan berfungsi dengan baik dan normal.
Diterima & Disetujui Oleh,
Dikerjakan Oleh,
Mengetahui,
Pelanggan
Teknisi AC BDG Express
( )
( Teknisi AC BDG Express )
( ........................ )
Dicetak dari Sistem ERP AC BDG Express secara otomatis pada ...
AC BDG EXPRESS
Spesialis Perawatan & Perbaikan AC Bandung Raya
jalan wibawa mukti 3 no 3 | Telp: 088222666188
SURAT PERINTAH KERJA (SPK)
No. Transaksi
: -
Tanggal
: -
Pelanggan
: -
Teknisi
: -
No. Handphone
: -
Alamat Lokasi
: -
Keluhan / Catatan Pelanggan:
-
Rincian Pekerjaan / Material (Berdasarkan Sistem):
No
Nama Pekerjaan / Material / Jasa
Qty
Dibuat Oleh, (Admin / CS)
Mengetahui, (Pelanggan)
Diterima Oleh, (Teknisi Bertugas)
Admin AC BDG Express
( ...................................... )
Nama Teknisi
Dicetak dari Sistem ERP AC BDG Express secara otomatis pada ...
Booking Jadwal Baru
Stok Barang
Peminjaman Alat
Data Karyawan
*Dipakai untuk auto-assign orderan baru sesuai wilayah tugas teknisi (menu Zona Wilayah).
Data Produk & Jasa
Kontak Pelanggan
Program Referral: Buatkan kode referral untuk pelanggan ini agar mereka bisa membagikannya. Jika ada yang servis menggunakan kode ini, pelanggan ini akan otomatis mendapat Cashback.
Form Invoice & Penawaran
*Siapa yang menerima order & membuat jadwal ini (Marketing/Admin/Owner/Teknisi). Dipakai untuk rekap "Produktivitas Marketing".
Data Pelanggan
*Pilih otomatis dari database. Pemilik kode akan mendapat komisi 10% dari nilai Jasa via WA saat Lunas.
*Dipakai untuk rekap "Lacak Komplain": mencatat teknisi mana yang mengerjakan jadwal asalnya. Isi Nama Pelanggan di atas dulu supaya daftarnya muncul.
Ringkasan Biaya
SubtotalRp 0
Diskon (Rp)
Pajak PPN (%)
TOTAL AKHIRRp 0
Dibayar / DP (Rp)
Sisa TagihanRp 0
Item Barang / Jasa
Nama Item / Deskripsi
Qty
Harga Satuan
Subtotal
Hapus
Daftar Unit AC
Pelanggan-
Laporan (eReport)
Info: Menyelesaikan laporan ini menambah Rp 2.500 ke slip gaji Anda.
Identitas AC Pintar (Opsional)
Scan stiker ini untuk mengikat rekam medis (History Servis) pada fisik AC pelanggan.
Spesifikasi Unit AC
Kondisi Unit AC
Jika dipilih Unit Rusak, tombol download eReport unit ini di halaman pelanggan akan berwarna merah.
Parameter Teknis
BFR
AFT
BFR
AFT
BFR
AFT
Rincian Pekerjaan
Dokumentasi Foto (Maks 6)
Tanda Tangan Pelanggan & Mengetahui
Catat Transaksi
Proses Pembayaran Invoice
Transaksi Kasbon
Riwayat Kasbon:
Tanggal
Jenis
Nominal
Aksi
Edit Kehadiran & Bonus
Slip Gaji
AC BANDUNG EXPRESS
Rincian Penghasilan
Nama Jabatan
Tanggal Cetak TGL
Keterangan
Jumlah
Gaji Pokok (0 Hari)
0
Lembur (0 Jam)
0
Bonus / Insentif
0
Bonus eReport
0
Potongan Kasbon
0
Potongan Telat
0
TAKE HOME PAY
0
Export Laba Rugi
Scan Barcode / QR Code
Arahkan kamera ke stiker QR Code yang tertempel pada unit AC.
Kirim Pesan / SP
Detail Pesan
Dari: Sistem
Tanggal
Subjek Pesan
Kalkulator Survey Pemasangan AC
Pilihan material di bawah ini mengambil harga secara otomatis dari menu Produk & Jasa. Pastikan data produk Anda sudah terisi lengkap.
Form Ajukan Izin / Sakit
Pengajuan Kasbon
Pengajuan akan masuk ke kotak pesan Admin/HRD untuk disetujui.
Review Pengajuan Izin
Bukti Foto / Surat Dokter:
Pembayaran QRIS
Rp 0
Scan QRIS ini menggunakan aplikasi M-Banking atau E-Wallet Anda.
Hak Akses Menu Karyawan
Centang menu yang boleh dibuka oleh tiap role. Contoh: agar Marketing hanya bisa membuka Invoice, kosongkan semua centang Marketing kecuali Invoice. Owner selalu punya akses penuh. Menu Dashboard, Pegawai, Payroll, dan Kasbon meminta password tambahan khusus untuk akun Owner.
Pengaturan Sistem
Hanya Owner yang dapat mengubah ini.
Titik Pusat Kantor Dikunci: Lat: -6.9758959 Lng: 107.6540854
Dipakai untuk: kirim invoice, notifikasi lunas, cashback referral, absensi, tugas teknisi, eReport, rating, broadcast promo, notifikasi stok/alat/pegawai, dan semua notifikasi grup selain kasbon.
Dipakai HANYA untuk notifikasi KASBON (japri ke karyawan & grup Finance). Jika dikosongkan, otomatis memakai nomor Produksi.
Masukkan ID Grup WA untuk notifikasi absen masuk/keluar.
Masukkan ID Grup WA untuk notifikasi buku kas, gaji/payroll, kasbon, serta edit/hapus invoice.
Masukkan ID Grup WA untuk broadcast Invoice/Tugas Teknisi baru, kendala teknisi di jalan, dan notifikasi eReport selesai.
Masukkan ID Grup WA untuk notifikasi jadwal/kalender teknisi (jadwal baru, perubahan jadwal, pembatalan). Jika dikosongkan, notifikasi kalender tidak dikirim.
Masukkan ID Grup WA untuk notifikasi invoice yang sudah LUNAS. Jika dikosongkan, memakai Grup Finance.
Dapatkan secara gratis di aistudio.google.com. Jika dikosongkan, chatbot akan menggunakan mode jawaban dasar.
Isi: omzet, jumlah job, teknisi yang tidak absen, piutang. Terkirim otomatis pada jam di atas selama aplikasi terbuka di HP/laptop Owner atau Admin (sebaiknya biarkan 1 laptop kantor tetap login). Jika baru dibuka setelah jam itu, terkirim saat itu juga (hanya sekali per hari).
Teman yang belum pernah servis dapat diskon saat memakai kode referral. Pengajak dapat kupon diskon setelah invoice temannya Lunas (isi 0 untuk menonaktifkan kupon). Cashback 10% yang lama tetap berjalan.
Tim yang Anda rakit di atas akan muncul sebagai pilihan di Form Booking Pelanggan dengan foto profil avatar bertumpuk (overlapping).
Gunakan fitur migrasi di atas untuk memindahkan data dari satu database ke database lainnya secara instan. Download Backup untuk menyimpan salinan lokal keseluruhan database Anda.
Putar Ulang Rute Teknisi
Jam: --:--
0 km ditempuh
Belum ada rekaman perjalanan untuk teknisi & tanggal ini. Rute terekam otomatis di server selama teknisi sudah absen masuk dan Live Tracking aktif.
Broadcast Promo WA
Pesan akan dikirim otomatis ke 0 pelanggan yang memiliki nomor WhatsApp valid.
0%
Asisten Analisa (AI)
Halo Bosku! Saya AI Asisten ERP. Mau analisa apa hari ini?
Coba ketik:
- "Pemasukan bulan ini"
- "Pengeluaran bulan ini"
- "Laba rugi"
- "Produk terlaris"
- "Stok barang tipis"
Chat
Percakapan ini sedang dibekukan oleh Admin.
Tombol Darurat
Gunakan tombol ini hanya untuk situasi darurat (kecelakaan, komplain berat pelanggan, keamanan, dll). Lokasi Anda akan langsung dikirim ke WA Grup Admin.
'
+''+esc(p.nama)+''
+'';
}).join('');
Swal.fire({
title:'Gender pegawai di kantor',
html:'
'+rows+'
',
showCancelButton:true, confirmButtonText:'Simpan', cancelButtonText:'Batal',
preConfirm:function(){ var o=Object.assign({},gmap()); document.querySelectorAll('.kai-g').forEach(function(s){ o[s.getAttribute('data-id')]=s.value; }); return o; }
}).then(function(r){
if(r.isConfirmed){ try{ localStorage.setItem('kai_gender',JSON.stringify(r.value)); }catch(e){} window.kantorAIReload(); }
});
};
/* ---------- akses data ERP untuk agen (baca-saja, dibatasi per role) ---------- */
// Bagian data yang boleh dibaca menurut role orang yang sedang membuka aplikasi (mengikuti menu yang boleh ia buka).
var SECT_VIEWER={
OWNER:['jadwal','ringkas','kas','pelanggan','stok','alat','ereport','komplain'],
ADMIN:['jadwal','ringkas','kas','pelanggan','stok','alat','ereport','komplain'],
MARKETING:['jadwal','pelanggan'],
KEPALA_TEKNISI:['jadwal','stok','alat','ereport','komplain']
};
// Fokus data tiap agen menurut perannya (supaya prompt singkat & relevan). Irisan dengan SECT_VIEWER yang dipakai.
var SECT_AGENT={
ADMIN:['jadwal','ringkas','kas','pelanggan','stok'],
MARKETING:['jadwal','pelanggan','ringkas'],
KEPALA_TEKNISI:['jadwal','stok','alat','ereport','komplain'],
KARYAWAN:['jadwal','alat','stok','ereport']
};
var CTX_MAX=5500;
function rp(n){ n=Number(n)||0; return 'Rp'+Math.round(n).toLocaleString('id-ID'); }
function cut(s,n){ s=String(s==null?'':s).replace(/\s+/g,' ').trim(); return s.length>n?s.slice(0,n-1)+'…':s; }
function readOn(){
var c=aiCfgFull();
if(c.read===false||c.read===0||c.read==='0') return false;
if(c.read===true||c.read===1||c.read==='1') return true;
try{ return localStorage.getItem('kai_readdata')!=='0'; }catch(e){ return true; }
}
function addDays(d,n){ return new Date(d.getTime()+n*86400000); }
function isCancel(st){ return ['dibatalkan','ditolak','batal'].indexOf(String(st||'').trim().toLowerCase())>-1; }
window.__kaiContext=function(agent){
try{
if(!readOn()||typeof db==='undefined'||!db||typeof userAktif==='undefined'||!userAktif) return '';
var vr=userAktif.role, vs=SECT_VIEWER[vr]; if(!vs) return '';
var ar=agent&&agent.erpRole, as=ar?SECT_AGENT[ar]:null;
var allow=vs.filter(function(k){ return !as||as.indexOf(k)>-1; });
var has=function(k){ return allow.indexOf(k)>-1; };
var showHp=(vr==='OWNER'||vr==='ADMIN'||vr==='MARKETING');
var now=new Date(), today=ymd(now), month=today.slice(0,7);
var names={}; (db.pegawai||[]).forEach(function(p){ if(p&&p.id) names[String(p.id)]=p.nama; });
var who=function(ids){ return String(ids||'').split(',').map(function(x){ return names[x.trim()]||''; }).filter(Boolean).join(', ')||'-'; };
var inv=(db.invoice||[]).filter(function(v){ return v&&!isCancel(v.status); });
var out=[];
out.push('=== DATA APLIKASI ERP (baca-saja, real-time, hari ini '+today+') ===');
out.push('Yang sedang bertanya: '+cut(userAktif.nama,40)+' ('+(LBL[vr]||vr)+'). Sapa sesuai namanya. Jawab HANYA dari data di bawah; kalau tidak ada, katakan data belum tersedia. Kamu tidak punya akses gaji, kasbon, password, atau rekening — jangan pernah menyebutnya.');
var pg=(db.pegawai||[]).filter(function(p){ return p&&p.nama; }).slice(0,30).map(function(p){ return cut(p.nama,24)+' ('+cut(p.jabatan||LBL[p.role]||p.role,20)+')'; });
if(pg.length) out.push('Pegawai: '+pg.join('; '));
if(has('jadwal')){
var d0=ymd(addDays(now,-1)), d1=ymd(addDays(now,3));
var js=inv.filter(function(v){ var t=String(v.tanggal||'').slice(0,10); return t>=d0&&t<=d1; })
.sort(function(a,b){ return (String(a.tanggal).slice(0,10)+(a.jam_mulai||'99:99')).localeCompare(String(b.tanggal).slice(0,10)+(b.jam_mulai||'99:99')); })
.slice(0,15);
out.push('Jadwal kemarin s/d 3 hari ke depan ('+js.length+'):');
js.forEach(function(v){
out.push('- '+String(v.tanggal).slice(0,10)+' '+(v.jam_mulai||'')+' | '+cut(v.pelanggan,28)+' | '+cut(v.alamat,40)+(showHp&&v.no_hp?' | HP '+cut(v.no_hp,16):'')+' | '+cut(v.status,12)+' | teknisi: '+who(v.id_teknisi)+(v.total?' | '+rp(v.total):''));
});
if(!js.length) out.push('- (tidak ada jadwal)');
}
if(has('ringkas')){
var bm=inv.filter(function(v){ return String(v.tanggal||'').slice(0,7)===month; }), cnt={}, omzet=0, piutang=0;
bm.forEach(function(v){ var s=String(v.status||'-'); cnt[s]=(cnt[s]||0)+1; if(s.toLowerCase()==='lunas') omzet+=Number(v.total)||0; else piutang+=Number(v.sisa)||0; });
out.push('Ringkasan jadwal/invoice bulan '+month+': '+Object.keys(cnt).map(function(k){ return k+' '+cnt[k]; }).join(', ')+' | omzet lunas '+rp(omzet)+' | sisa tagihan belum lunas '+rp(piutang));
}
if(has('kas')){
var pm=0, pk=0;
if (typeof hitungKasPeriode === 'function') { var _kp = hitungKasPeriode(month); pm = _kp.masuk; pk = _kp.keluar; } else { (db.finance||[]).forEach(function(f){ if(!f||String(f.tanggal||'').slice(0,7)!==month) return; var t=String(f.tipe||'').toLowerCase(), n=Number(f.nominal)||0; if(t.indexOf('pemasukan')===0) pm+=n; else if(t.indexOf('pengeluaran')===0) pk+=n; }); }
out.push('Buku kas bulan '+month+': pemasukan '+rp(pm)+', pengeluaran '+rp(pk)+', selisih '+rp(pm-pk));
}
if(has('pelanggan')){
var pl=db.pelanggan||[];
out.push('Pelanggan: total '+pl.length+'. Terbaru:');
pl.slice(-10).reverse().forEach(function(p){ out.push('- '+cut(p.nama,28)+' | '+cut(p.alamat,35)+(showHp&&p.hp?' | HP '+cut(p.hp,16):'')); });
}
if(has('stok')){
var it=db.inventory||[], low=it.filter(function(x){ return x&&Number(x.stok)<=5; }).slice(0,12);
out.push('Stok barang: '+it.length+' jenis. Menipis (<=5): '+(low.map(function(x){ return cut(x.nama_barang,24)+' '+x.stok+' '+cut(x.satuan,8); }).join('; ')||'tidak ada'));
}
if(has('alat')){
var tl=db.tools||[], dp=tl.filter(function(x){ return x&&x.status==='Dipinjam'; }).slice(0,10), rs=tl.filter(function(x){ return x&&x.status==='Rusak'; }).length;
out.push('Peralatan: '+tl.length+' alat, rusak/hilang '+rs+'. Sedang dipinjam: '+(dp.map(function(x){ return cut(x.nama_tool,24)+' ('+who((x.id_teknisi||'')+','+(x.id_helper||''))+')'; }).join('; ')||'tidak ada'));
}
if(has('ereport')){
var er=(db.ereport||[]).filter(Boolean).slice(-8).reverse();
out.push('eReport terbaru:');
er.forEach(function(e){ out.push('- '+cut(e.tanggal,10)+' | '+cut(e.pelanggan,26)+' | '+cut(e.teknisi,20)+' | '+cut(e.jenis_pekerjaan,40)); });
if(!er.length) out.push('- (belum ada)');
}
if(has('komplain')){
var kk=inv.filter(function(v){ return v.is_komplain==='Ya'&&String(v.tanggal||'').slice(0,7)===month; }).length;
out.push('Komplain bulan '+month+': '+kk);
}
var txt=out.join('\n');
if(txt.length>CTX_MAX) txt=txt.slice(0,CTX_MAX)+'\n[data dipotong karena terlalu panjang]';
return '\n\n'+txt+'\n=== AKHIR DATA ===';
}catch(e){ return ''; }
};
/* ---------- pengaturan API key agen ----------
Disimpan di 2 tempat: (1) baris CONF_KAI_AI di db.config -> dikirim ke server lewat saveSemuaConfig
(pola yang sama dengan setting Gemini/Fonnte/CRM), supaya awet & ikut di semua perangkat;
(2) localStorage sebagai cadangan di browser ini. */
var AI_ROW='CONF_KAI_AI';
var DEFM={anthropic:'claude-haiku-4-5-20251001',openai:'gpt-4o-mini',gemini:'gemini-flash-latest',custom:'qwen-plus'};
var DEAD={'gemini-2.0-flash':1,'gemini-2.0-flash-lite':1,'gemini-1.5-flash':1,'gemini-1.5-pro':1};
function aiRow(){ try{ return (db.config||[]).find(function(x){ return x&&x.id===AI_ROW; }); }catch(e){ return null; } }
function aiCfgFull(){
var r=aiRow();
if(r){ try{ var o=JSON.parse(r.lat||'{}')||{}; o.key=String(r.radius||''); return o; }catch(e){} }
try{ return JSON.parse(localStorage.getItem('aiCfg')||'{}')||{}; }catch(e){ return {}; }
}
function modelOf(c){ return (c.model&&!DEAD[c.model])?c.model:''; }
window.__kaiAiCfg=function(){ var c=aiCfgFull(); return {prov:c.prov||'server',key:c.key||'',model:modelOf(c),base:c.base||''}; };
// c = {prov,key,model[,read]} atau null untuk menghapus. Pembaruan lokal sinkron; hasil server lewat Promise.
window.__kaiAiSave=function(c){
var read=(c&&c.read!==undefined)?!!c.read:readOn();
var prov=c?(c.prov||'server'):'server', key=c?String(c.key||''):'', model=c?String(c.model||''):'', base=c?String(c.base||'').trim().replace(/\/+$/,''):'';
if(DEAD[model]) model='';
var res={local:false,server:'none'};
try{
localStorage.setItem('aiCfg',JSON.stringify({prov:prov,key:key,model:model,base:base}));
localStorage.setItem('kai_readdata',read?'1':'0');
res.local=(JSON.parse(localStorage.getItem('aiCfg')||'{}').key||'')===key;
}catch(e){ res.local=false; }
var row={id:AI_ROW,lat:JSON.stringify({prov:prov,model:model,read:read,base:base}),lng:'',radius:key};
var canServer=false;
try{
if(typeof db!=='undefined'&&db){
if(!Array.isArray(db.config)) db.config=[];
var ix=db.config.findIndex(function(x){ return x&&x.id===AI_ROW; });
if(ix>-1) db.config[ix]=row; else db.config.push(row);
canServer=(typeof google!=='undefined'&&google.script&&google.script.run&&google.script.run.saveSemuaConfig);
}
}catch(e){}
return new Promise(function(resolve){
if(!canServer){ resolve(res); return; }
var done=false, fin=function(s){ if(done) return; done=true; res.server=s; resolve(res); };
setTimeout(function(){ fin('unknown'); },15000);
try{
// kirim SELURUH db.config (bukan 1 baris) supaya setting lain tidak ikut tertimpa di server
google.script.run.withSuccessHandler(function(){ fin('ok'); }).withFailureHandler(function(){ fin('fail'); }).saveSemuaConfig(db.config);
}catch(e){ fin('fail'); }
});
};
async function aiTest(c){
var m=modelOf(c)||DEFM[c.prov], q='Balas satu kata: siap', r, j;
if(c.prov==='anthropic'){
r=await fetch('https://api.anthropic.com/v1/messages',{method:'POST',headers:{'content-type':'application/json','x-api-key':c.key,'anthropic-version':'2023-06-01','anthropic-dangerous-direct-browser-access':'true'},body:JSON.stringify({model:m,max_tokens:20,messages:[{role:'user',content:q}]})});
j=await r.json(); if(!r.ok) throw new Error((j.error&&j.error.message)||r.status);
return j.content.map(function(x){ return x.text||''; }).join('');
}
if(c.prov==='openai'){
r=await fetch('https://api.openai.com/v1/chat/completions',{method:'POST',headers:{'content-type':'application/json',Authorization:'Bearer '+c.key},body:JSON.stringify({model:m,max_tokens:20,messages:[{role:'user',content:q}]})});
j=await r.json(); if(!r.ok) throw new Error((j.error&&j.error.message)||r.status);
return j.choices[0].message.content;
}
if(c.prov==='custom'){
if(!c.base) throw new Error('Isi dulu Base URL (mis. https://alamat-proxy/v1).');
r=await fetch(c.base.replace(/\/+$/,'')+'/chat/completions',{method:'POST',headers:{'content-type':'application/json',Authorization:'Bearer '+c.key},body:JSON.stringify({model:m,max_tokens:20,messages:[{role:'user',content:q}]})});
j=await r.json(); if(!r.ok) throw new Error((j.error&&(j.error.message||j.error))||r.status);
return j.choices[0].message.content;
}
if(c.prov==='gemini'){
r=await fetch('https://generativelanguage.googleapis.com/v1beta/models/'+m+':generateContent',{method:'POST',headers:{'content-type':'application/json','x-goog-api-key':c.key},body:JSON.stringify({contents:[{parts:[{text:q}]}]})});
j=await r.json(); if(!r.ok) throw new Error((j.error&&j.error.message)||r.status);
return j.candidates[0].content.parts.map(function(x){ return x.text||''; }).join('');
}
throw new Error('Pilih penyedia AI dan isi API key dulu.');
}
async function geminiModels(key){
var r=await fetch('https://generativelanguage.googleapis.com/v1beta/models?pageSize=100',{headers:{'x-goog-api-key':key}});
var j=await r.json(); if(!r.ok) throw new Error((j.error&&j.error.message)||r.status);
return (j.models||[]).filter(function(m){ return (m.supportedGenerationMethods||[]).indexOf('generateContent')>-1&&/gemini/.test(m.name); })
.map(function(m){ return String(m.name).replace(/^models\//,''); });
}
async function customModels(base,key){
var r=await fetch(String(base).replace(/\/+$/,'')+'/models',{headers:{Authorization:'Bearer '+key}});
var j=await r.json(); if(!r.ok) throw new Error((j.error&&(j.error.message||j.error))||r.status);
return (j.data||j.models||[]).map(function(m){ return m.id||m.name; }).filter(Boolean);
}
function aiApplyToFrame(){ try{ var w=frame&&frame.contentWindow; if(w&&typeof w.__aiApply==='function') w.__aiApply(); }catch(e){} }
window.kantorAIKey=function(){
if(typeof Swal==='undefined') return;
els();
var c=aiCfgFull();
Swal.fire({
title:'API Key agen Kantor AI',
width:520,
html:'
'
+''
+''
+''
+''
+''
+''
+''
+''
+''
+''
+'
Key disimpan di server aplikasi (sama seperti setting Gemini/Fonnte) dan cadangan di browser ini, jadi cukup diisi sekali dan berlaku untuk semua perangkat & role yang membuka Kantor AI. Pakai key khusus dengan batas biaya. Saat agen menjawab, data yang dibaca dikirim ke penyedia AI yang dipilih. Membalas WhatsApp otomatis butuh server, tidak bisa lewat key di browser.
'
+'
',
showCancelButton:true, confirmButtonText:'Simpan', cancelButtonText:'Batal',
didOpen:function(){
var $=function(id){ return document.getElementById(id); };
$('kkProv').value=c.prov||'server'; $('kkBase').value=c.base||''; $('kkKey').value=c.key||''; $('kkModel').value=modelOf(c); $('kkRead').checked=readOn();
var ph=function(){ $('kkModel').placeholder='mis. '+(DEFM[$('kkProv').value]||'diatur server'); $('kkBaseWrap').style.display=$('kkProv').value==='custom'?'block':'none'; }; ph(); $('kkProv').onchange=ph;
$('kkEye').onclick=function(){ $('kkKey').type=$('kkKey').type==='password'?'text':'password'; };
$('kkStat').textContent=(c.prov&&c.prov!=='server'&&c.key)?'Tersimpan: '+c.prov+(aiRow()?' (di server)':' (hanya browser ini)'):'Belum ada key, memakai server.';
$('kkList').onclick=async function(){
var k=$('kkKey').value.trim();
var pv=$('kkProv').value;
if((pv!=='gemini'&&pv!=='custom')||!k||(pv==='custom'&&!$('kkBase').value.trim())){ $('kkStat').textContent='Daftar model untuk Gemini atau OpenAI-compatible: pilih penyedia, isi key (dan Base URL) dulu.'; return; }
$('kkStat').textContent='Mengambil daftar model…';
try{
var L=(pv==='custom')?await customModels($('kkBase').value.trim(),k):await geminiModels(k);
$('kkModels').innerHTML=L.map(function(n){ return '