empuno 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/empuno.mjs +180 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -38,6 +38,7 @@ empuno leave-requests.approve 123
38
38
  empuno leave-requests.reject 124 --rejection_reason "Yoğun dönem"
39
39
  empuno employees.documents.store 45 --document_type is_sozlesmesi --file file=./sozlesme.pdf
40
40
  empuno leave-reports.download 12 --output ./rapor.pdf
41
+ empuno reference sgk_exit_codes # bir alanın geçerli değerleri
41
42
  empuno call POST /manager/leave-requests/123/approve # ham çağrı
42
43
  empuno api leaves --status approved # salt-okuma Company API
43
44
  ```
@@ -50,6 +51,9 @@ komutu CLI güncellemesi olmadan görünür.
50
51
  - `--alan @dosya` ya da `--file alan=dosya` → multipart yükleme.
51
52
  - `--json '{…}'` / `--json @govde.json`, `--dry-run`, `--output yol`,
52
53
  `--profile ad` (birden çok firma).
54
+ - Listeler `--per_page` (≤100) ve `--page` alır; yeni kaydın kimliği yanıtta
55
+ `flash.created` ya da `redirect.params` içindedir.
56
+ - Geçmiş veri aktarımı (`data-migration.*`) davet/e-posta/bildirim göndermez.
53
57
 
54
58
  ## Talepler (empuno ekibine)
55
59
 
package/empuno.mjs CHANGED
@@ -21,7 +21,7 @@ import os from 'node:os';
21
21
  import path from 'node:path';
22
22
  import crypto from 'node:crypto';
23
23
 
24
- const VERSION = '1.1.1'; // EMPUNO_CLI_VERSION — config/agent_api.php cli_version + package.json ile aynı tutulur
24
+ const VERSION = '1.3.0'; // EMPUNO_CLI_VERSION — config/agent_api.php cli_version + package.json ile aynı tutulur
25
25
  const NPM_PACKAGE = 'empuno';
26
26
  const DEFAULT_URL = 'https://empuno.com';
27
27
  const CATALOG_TTL_MS = 10 * 60 * 1000;
@@ -234,7 +234,7 @@ function compareVersions(a, b) {
234
234
  return 0;
235
235
  }
236
236
 
237
- async function request(auth, method, urlPath, { query = {}, body = null, files = [], output = null, dryRun = false } = {}) {
237
+ async function request(auth, method, urlPath, { query = {}, body = null, files = [], output = null, dryRun = false, headers: extraHeaders = {} } = {}) {
238
238
  const url = new URL(urlPath.startsWith('http') ? urlPath : auth.url + urlPath);
239
239
  for (const [k, v] of Object.entries(query)) {
240
240
  if (Array.isArray(v)) v.forEach((item) => url.searchParams.append(`${k}[]`, String(item)));
@@ -246,6 +246,7 @@ async function request(auth, method, urlPath, { query = {}, body = null, files =
246
246
  Accept: 'application/json',
247
247
  'User-Agent': `empuno-cli/${VERSION} node/${process.versions.node}`,
248
248
  'X-Empuno-Cli': VERSION,
249
+ ...extraHeaders,
249
250
  };
250
251
 
251
252
  let sendMethod = method;
@@ -647,6 +648,23 @@ function installedViaNpm() {
647
648
  }
648
649
  }
649
650
 
651
+ async function cmdReference(args) {
652
+ const auth = resolveAuth(args.flags);
653
+ requireKey(auth);
654
+ const name = args.positional[0];
655
+ const { ok, result } = await request(auth, 'GET', `/api/company/v1/reference${name ? `/${encodeURIComponent(name)}` : ''}`);
656
+ if (wantsText() && ok && name && Array.isArray(result?.data)) {
657
+ for (const row of result.data) {
658
+ const key = row.code ?? row.value ?? row.id;
659
+ const label = row.label ?? row.name ?? '';
660
+ process.stdout.write(`${String(key).padEnd(24)} ${label}\n`);
661
+ }
662
+ return 0;
663
+ }
664
+ print(result);
665
+ return ok ? 0 : 1;
666
+ }
667
+
650
668
  async function cmdUpdate(args) {
651
669
  if (installedViaNpm()) {
652
670
  // npm'in yönettiği dosyanın üzerine yazmak sürüm kaydını bozar.
@@ -760,8 +778,8 @@ async function cmdTickets(args) {
760
778
  const auth = resolveAuth(flags);
761
779
  requireKey(auth);
762
780
 
763
- const send = async (method, urlPath, body = null, query = {}) => {
764
- const response = await request(auth, method, urlPath, { body, query, dryRun: flags['dry-run'] });
781
+ const send = async (method, urlPath, body = null, query = {}, headers = {}) => {
782
+ const response = await request(auth, method, urlPath, { body, query, headers, dryRun: flags['dry-run'] });
765
783
  return response;
766
784
  };
767
785
 
@@ -789,7 +807,10 @@ async function cmdTickets(args) {
789
807
  }
790
808
  let context = opts.context ? readJsonArg(String(opts.context)) : {};
791
809
  if (opts.command) context = { ...context, command: String(opts.command) };
792
- const r = await send('POST', base, { title: String(title), kind, body, priority: opts.priority, context: Object.keys(context).length ? context : undefined });
810
+ // Aynı içerik → aynı anahtar: betik yeniden denese de ikinci talep açılmaz
811
+ // (sunucu mevcut talebi duplicate=true ile döner).
812
+ const idem = crypto.createHash('sha256').update(`${title}|${kind}|${body}`).digest('hex').slice(0, 40);
813
+ const r = await send('POST', base, { title: String(title), kind, body, priority: opts.priority, context: Object.keys(context).length ? context : undefined }, {}, { 'Idempotency-Key': opts['idempotency-key'] ? String(opts['idempotency-key']) : idem });
793
814
  if (r.dryRun) return print(r.result), 0;
794
815
  print(r.result);
795
816
  return r.ok ? 0 : 1;
@@ -819,6 +840,140 @@ async function cmdTickets(args) {
819
840
  }
820
841
  }
821
842
 
843
+ // ─── geçiş (başka sistemden geçmiş verinin aktarımı) ─────────────────────────
844
+
845
+ const MIGRATE_KINDS = {
846
+ employees: { path: '/manager/data-migration/employees/commit', chunk: 200 },
847
+ leaves: { path: '/manager/data-migration/leaves/commit', chunk: 500 },
848
+ advances: { path: '/manager/data-migration/advances/commit', chunk: 200 },
849
+ expenses: { path: '/manager/data-migration/expenses/commit', chunk: 200 },
850
+ documents: { path: '/manager/data-migration/documents/commit', chunk: 10 },
851
+ };
852
+
853
+ /** Basit CSV ayrıştırıcı: tırnak, "" kaçışı, ; ya da , ayırıcı (başlıktan seçilir), BOM. */
854
+ function parseCsv(text) {
855
+ const src = text.replace(/^/, '');
856
+ const firstLine = src.split(/\r?\n/, 1)[0] || '';
857
+ const delim = (firstLine.match(/;/g) || []).length > (firstLine.match(/,/g) || []).length ? ';' : ',';
858
+ const rows = [];
859
+ let row = [];
860
+ let field = '';
861
+ let quoted = false;
862
+ for (let i = 0; i < src.length; i++) {
863
+ const c = src[i];
864
+ if (quoted) {
865
+ if (c === '"' && src[i + 1] === '"') { field += '"'; i++; }
866
+ else if (c === '"') quoted = false;
867
+ else field += c;
868
+ continue;
869
+ }
870
+ if (c === '"') quoted = true;
871
+ else if (c === delim) { row.push(field); field = ''; }
872
+ else if (c === '\n' || c === '\r') {
873
+ if (c === '\r' && src[i + 1] === '\n') i++;
874
+ row.push(field); field = '';
875
+ if (row.some((v) => v !== '')) rows.push(row);
876
+ row = [];
877
+ } else field += c;
878
+ }
879
+ row.push(field);
880
+ if (row.some((v) => v !== '')) rows.push(row);
881
+ const [header, ...data] = rows;
882
+ if (!header) return [];
883
+ const keys = header.map((h) => h.trim());
884
+ return data.map((cells) => {
885
+ const obj = {};
886
+ keys.forEach((k, idx) => {
887
+ const v = (cells[idx] ?? '').trim();
888
+ if (v === '' || !k) return;
889
+ // CSV değerleri metin kalır (TC, sicil no, telefon); yalnız mantıksal değerler çevrilir.
890
+ obj[k] = ['true', 'false'].includes(v.toLowerCase()) ? v.toLowerCase() === 'true' : v;
891
+ });
892
+ return obj;
893
+ });
894
+ }
895
+
896
+ function readRecordsFile(file) {
897
+ if (!fs.existsSync(file)) throw new UsageError(`dosya bulunamadı: ${file}`);
898
+ const text = fs.readFileSync(file, 'utf8');
899
+ if (/\.csv$/i.test(file)) return parseCsv(text);
900
+ let data;
901
+ try { data = JSON.parse(text); } catch (e) { throw new UsageError(`geçersiz JSON (${file}): ${e.message}`); }
902
+ const records = Array.isArray(data) ? data : data?.records;
903
+ if (!Array.isArray(records)) throw new UsageError('JSON bir dizi ya da {"records": [...]} olmalı');
904
+ return records;
905
+ }
906
+
907
+ async function cmdMigrate(args) {
908
+ const { flags, fields, positional } = args;
909
+ const [kind, file] = positional;
910
+ if (!kind || !MIGRATE_KINDS[kind] || !file) {
911
+ throw new UsageError(`kullanım: empuno migrate <${Object.keys(MIGRATE_KINDS).join('|')}> <kayitlar.json|.csv> [--validate] [--on_existing update] [--employee_id N] [--chunk N]
912
+ alanlar: empuno describe data-migration.${kind && MIGRATE_KINDS[kind] ? kind : 'employees'}.commit · rehber: https://empuno.com/api-docs/agent#gecis`);
913
+ }
914
+ const auth = resolveAuth(flags);
915
+ requireKey(auth);
916
+ const opts = fieldMap(fields);
917
+ const spec = MIGRATE_KINDS[kind];
918
+ const records = readRecordsFile(file);
919
+ const chunkSize = Math.max(1, Math.min(Number(opts.chunk) || spec.chunk, spec.chunk));
920
+ const validate = Boolean(flags.validate);
921
+ const baseDir = path.dirname(path.resolve(file));
922
+
923
+ const summary = { kind, file, dry_run: validate, records: records.length, created: 0, updated: 0, skipped: 0, failed: 0, results: [] };
924
+
925
+ for (let offset = 0; offset < records.length; offset += chunkSize) {
926
+ const chunk = records.slice(offset, offset + chunkSize);
927
+ let response;
928
+
929
+ if (kind === 'documents') {
930
+ const files = [];
931
+ const manifest = chunk.map((entry, i) => {
932
+ const filePath = path.resolve(baseDir, String(entry.file ?? ''));
933
+ files.push({ field: 'files[]', file: filePath });
934
+ return { ...entry, file: i };
935
+ });
936
+ for (const f of files) if (!fs.existsSync(f.file)) throw new UsageError(`dosya bulunamadı: ${f.file}`);
937
+ response = await request(auth, 'POST', spec.path, {
938
+ body: { manifest: JSON.stringify(manifest), ...(validate ? { dry_run: 1 } : {}) },
939
+ files,
940
+ dryRun: flags['dry-run'],
941
+ });
942
+ } else {
943
+ const body = { records: chunk, ...(validate ? { dry_run: true } : {}) };
944
+ if (opts.on_existing) body.on_existing = String(opts.on_existing);
945
+ if (opts.employee_id) body.employee_id = Number(opts.employee_id);
946
+ response = await request(auth, 'POST', spec.path, { body, dryRun: flags['dry-run'] });
947
+ }
948
+
949
+ if (response.dryRun) {
950
+ print(response.result);
951
+ return 0;
952
+ }
953
+ const r = response.result || {};
954
+ if (!response.ok) {
955
+ // Doğrulama hatası (422) tüm parçayı etkiler: sunucunun errors'ını kayıt indeksine çevir.
956
+ summary.failed += chunk.length;
957
+ summary.results.push({ chunk_offset: offset, action: 'failed', status: response.status, message: r.message, errors: r.errors });
958
+ continue;
959
+ }
960
+ for (const key of ['created', 'updated', 'skipped', 'failed']) summary[key] += Number(r[key] || 0);
961
+ for (const row of r.results || []) summary.results.push({ ...row, index: offset + Number(row.index ?? 0) });
962
+ if (isTTY) warn(`${Math.min(offset + chunkSize, records.length)}/${records.length} işlendi`);
963
+ }
964
+
965
+ summary.message = `${summary.created} ${validate ? 'eklenecek' : 'eklendi'}, ${summary.updated} güncellen${validate ? 'ecek' : 'di'}, ${summary.skipped} atlandı, ${summary.failed} hatalı${validate ? ' (doğrulama — hiçbir şey yazılmadı)' : ''}`;
966
+ if (wantsText()) {
967
+ process.stdout.write(`${summary.message}\n`);
968
+ for (const row of summary.results.filter((x) => x.action === 'failed')) {
969
+ process.stdout.write(` ✗ #${row.index ?? row.chunk_offset}: ${row.reason || row.message || ''}${row.errors ? ' ' + JSON.stringify(row.errors) : ''}\n`);
970
+ }
971
+ } else {
972
+ print(summary);
973
+ }
974
+ return summary.failed > 0 ? 1 : 0;
975
+ }
976
+
822
977
  // ─── destek (yalnız empuno ekibinin ajanları — emp_staff_ jetonu) ────────────
823
978
 
824
979
  function requireStaff(auth) {
@@ -928,7 +1083,8 @@ KURULUM
928
1083
  KEŞİF
929
1084
  empuno whoami # firma, anahtar türü, kimin adına çalıştığı
930
1085
  empuno commands [ara] [--module leaves] [--writes|--reads] [--all] [--full]
931
- empuno describe <komut> # yöntem, yol, yol parametreleri, alanlar/kurallar, gereken yetki
1086
+ empuno describe <komut> # yöntem, yol, yol parametreleri, alanlar/kurallar, gereken yetki, hız sınırı
1087
+ empuno reference [ad] # geçerli değerler: sgk_exit_codes, employee_document_types, departments, branches, leave_types…
932
1088
 
933
1089
  ÇALIŞTIRMA (komut = panel rotası; listeden)
934
1090
  empuno leave-requests.index --status pending --per_page 50
@@ -946,6 +1102,15 @@ ALANLAR
946
1102
  --alan @./dosya.pdf | --file alan=./dosya.pdf # multipart yükleme
947
1103
  --query anahtar=değer # yazma isteğinde ek sorgu parametresi
948
1104
 
1105
+ GEÇİŞ (başka sistemden geçmiş veri — JSON ya da CSV; davet/e-posta/bildirim gitmez; tekrar çalıştırmak güvenli)
1106
+ empuno migrate employees calisanlar.csv --validate # önce yazmadan doğrula
1107
+ empuno migrate employees calisanlar.csv [--on_existing update] # ayrılmışlar dahil; TC ile eşler
1108
+ empuno migrate leaves izinler.json # her satırda identity_number|employee_number + leave_type
1109
+ empuno migrate advances avanslar.csv # geçmiş avans + devreden bakiye (deducted_amount)
1110
+ empuno migrate expenses masraflar.csv # ödenmiş geçmiş masraf (kategori adla)
1111
+ empuno migrate documents manifest.csv # file sütunu: dosya yolu; ≤10'arlı gönderilir
1112
+ alanlar: empuno describe data-migration.<tür>.commit · sıra: çalışanlar → izin → avans → masraf → evrak
1113
+
949
1114
  TALEPLER (geliştirme isteği, hata, eleştiri, soru → empuno ekibi)
950
1115
  empuno tickets [--status active|open|answered|closed|all] [--unread] [--mine]
951
1116
  empuno tickets open "Toplu izin onayı" --kind feature --body "…"|@not.md [--priority high] [--command leave-requests.approve]
@@ -972,8 +1137,9 @@ BELGE: https://empuno.com/api-docs/agent
972
1137
 
973
1138
  async function main() {
974
1139
  const argv = process.argv.slice(2);
975
- const ticketish = ['tickets', 'ticket', 'support'].includes(argv.find((a) => !a.startsWith('--')) ?? '');
976
- const args = parseArgs(argv, ticketish ? ['mine', 'unread', 'close', 'token-stdin'] : []);
1140
+ const sub0 = argv.find((a) => !a.startsWith('--')) ?? '';
1141
+ const ticketish = ['tickets', 'ticket', 'support'].includes(sub0);
1142
+ const args = parseArgs(argv, ticketish ? ['mine', 'unread', 'close', 'token-stdin'] : sub0 === 'migrate' ? ['validate'] : []);
977
1143
  const [sub, ...rest] = args.positional;
978
1144
  args.positional = rest;
979
1145
 
@@ -982,7 +1148,7 @@ async function main() {
982
1148
  return 0;
983
1149
  }
984
1150
  if (args.flags.help) {
985
- if (['tickets', 'ticket', 'support', 'login', 'commands', 'describe', 'call', 'api'].includes(sub)) {
1151
+ if (['tickets', 'ticket', 'support', 'login', 'commands', 'describe', 'call', 'api', 'migrate', 'reference'].includes(sub)) {
986
1152
  process.stdout.write(HELP);
987
1153
  return 0;
988
1154
  }
@@ -1016,6 +1182,11 @@ async function main() {
1016
1182
  case 'tickets':
1017
1183
  case 'ticket':
1018
1184
  return cmdTickets(args);
1185
+ case 'reference':
1186
+ case 'ref':
1187
+ return cmdReference(args);
1188
+ case 'migrate':
1189
+ return cmdMigrate(args);
1019
1190
  case 'support':
1020
1191
  return cmdSupport(args);
1021
1192
  case 'run':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "empuno",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "empuno İK platformunun komut satırı: yönetim panelindeki tüm işlemleri (izin, bordro, vardiya, PDKS, zimmet, masraf, işe alım) yapay zekâ ajanlarına ve otomasyonlara açar.",
5
5
  "type": "module",
6
6
  "bin": {