empuno 1.2.0 → 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 (2) hide show
  1. package/empuno.mjs +150 -4
  2. package/package.json +1 -1
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.2.0'; // 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;
@@ -840,6 +840,140 @@ async function cmdTickets(args) {
840
840
  }
841
841
  }
842
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
+
843
977
  // ─── destek (yalnız empuno ekibinin ajanları — emp_staff_ jetonu) ────────────
844
978
 
845
979
  function requireStaff(auth) {
@@ -968,6 +1102,15 @@ ALANLAR
968
1102
  --alan @./dosya.pdf | --file alan=./dosya.pdf # multipart yükleme
969
1103
  --query anahtar=değer # yazma isteğinde ek sorgu parametresi
970
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
+
971
1114
  TALEPLER (geliştirme isteği, hata, eleştiri, soru → empuno ekibi)
972
1115
  empuno tickets [--status active|open|answered|closed|all] [--unread] [--mine]
973
1116
  empuno tickets open "Toplu izin onayı" --kind feature --body "…"|@not.md [--priority high] [--command leave-requests.approve]
@@ -994,8 +1137,9 @@ BELGE: https://empuno.com/api-docs/agent
994
1137
 
995
1138
  async function main() {
996
1139
  const argv = process.argv.slice(2);
997
- const ticketish = ['tickets', 'ticket', 'support'].includes(argv.find((a) => !a.startsWith('--')) ?? '');
998
- 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'] : []);
999
1143
  const [sub, ...rest] = args.positional;
1000
1144
  args.positional = rest;
1001
1145
 
@@ -1004,7 +1148,7 @@ async function main() {
1004
1148
  return 0;
1005
1149
  }
1006
1150
  if (args.flags.help) {
1007
- 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)) {
1008
1152
  process.stdout.write(HELP);
1009
1153
  return 0;
1010
1154
  }
@@ -1041,6 +1185,8 @@ async function main() {
1041
1185
  case 'reference':
1042
1186
  case 'ref':
1043
1187
  return cmdReference(args);
1188
+ case 'migrate':
1189
+ return cmdMigrate(args);
1044
1190
  case 'support':
1045
1191
  return cmdSupport(args);
1046
1192
  case 'run':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "empuno",
3
- "version": "1.2.0",
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": {