empuno 1.2.0 → 1.4.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.
- package/README.md +20 -0
- package/empuno.mjs +166 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -55,6 +55,26 @@ komutu CLI güncellemesi olmadan görünür.
|
|
|
55
55
|
`flash.created` ya da `redirect.params` içindedir.
|
|
56
56
|
- Geçmiş veri aktarımı (`data-migration.*`) davet/e-posta/bildirim göndermez.
|
|
57
57
|
|
|
58
|
+
## Başka sistemden geçiş
|
|
59
|
+
|
|
60
|
+
Eski sistemin geçmişini JSON ya da Excel'den alınmış CSV ile taşıyın. Çalışan her
|
|
61
|
+
satırda TC (`identity_number`), sicil no ya da `employee_id` ile belirtilir;
|
|
62
|
+
tekrar çalıştırmak güvenlidir (aynı kayıt `skipped` döner).
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
empuno migrate employees calisanlar.csv --validate # önce yazmadan doğrula
|
|
66
|
+
empuno migrate employees calisanlar.csv # ayrılmışlar dahil
|
|
67
|
+
empuno migrate leaves izinler.csv
|
|
68
|
+
empuno migrate advances avanslar.csv # devreden bakiye: deducted_amount
|
|
69
|
+
empuno migrate expenses masraflar.csv
|
|
70
|
+
empuno migrate payroll bordrolar.csv --decimal , # geçmiş bordro; 38.000,50 biçimi
|
|
71
|
+
empuno migrate documents manifest.csv # file sütunu: dosya yolu
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Geçmiş bordro tutarları yeniden hesaplanmaz, aynen saklanır; empuno sonraki ayı
|
|
75
|
+
aktarılan kümülatif gelir vergisi matrahıyla devralır. Alanlar:
|
|
76
|
+
`empuno describe data-migration.payroll.commit`.
|
|
77
|
+
|
|
58
78
|
## Talepler (empuno ekibine)
|
|
59
79
|
|
|
60
80
|
Ajanınız yapamadığı bir işlemi, bir hatayı ya da geliştirme önerisini empuno
|
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.
|
|
24
|
+
const VERSION = '1.4.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,155 @@ 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
|
+
// Dönem sırasıyla gönderilir: aynı ayın bordroları aynı istekte kalır, kümülatif zincir aydan aya kurulur.
|
|
852
|
+
payroll: { path: '/manager/data-migration/payroll/commit', chunk: 300, sortBy: 'period' },
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
/** Basit CSV ayrıştırıcı: tırnak, "" kaçışı, ; ya da , ayırıcı (başlıktan seçilir), BOM. */
|
|
856
|
+
function parseCsv(text) {
|
|
857
|
+
const src = text.replace(/^/, '');
|
|
858
|
+
const firstLine = src.split(/\r?\n/, 1)[0] || '';
|
|
859
|
+
const delim = (firstLine.match(/;/g) || []).length > (firstLine.match(/,/g) || []).length ? ';' : ',';
|
|
860
|
+
const rows = [];
|
|
861
|
+
let row = [];
|
|
862
|
+
let field = '';
|
|
863
|
+
let quoted = false;
|
|
864
|
+
for (let i = 0; i < src.length; i++) {
|
|
865
|
+
const c = src[i];
|
|
866
|
+
if (quoted) {
|
|
867
|
+
if (c === '"' && src[i + 1] === '"') { field += '"'; i++; }
|
|
868
|
+
else if (c === '"') quoted = false;
|
|
869
|
+
else field += c;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (c === '"') quoted = true;
|
|
873
|
+
else if (c === delim) { row.push(field); field = ''; }
|
|
874
|
+
else if (c === '\n' || c === '\r') {
|
|
875
|
+
if (c === '\r' && src[i + 1] === '\n') i++;
|
|
876
|
+
row.push(field); field = '';
|
|
877
|
+
if (row.some((v) => v !== '')) rows.push(row);
|
|
878
|
+
row = [];
|
|
879
|
+
} else field += c;
|
|
880
|
+
}
|
|
881
|
+
row.push(field);
|
|
882
|
+
if (row.some((v) => v !== '')) rows.push(row);
|
|
883
|
+
const [header, ...data] = rows;
|
|
884
|
+
if (!header) return [];
|
|
885
|
+
const keys = header.map((h) => h.trim());
|
|
886
|
+
return data.map((cells) => {
|
|
887
|
+
const obj = {};
|
|
888
|
+
keys.forEach((k, idx) => {
|
|
889
|
+
const v = (cells[idx] ?? '').trim();
|
|
890
|
+
if (v === '' || !k) return;
|
|
891
|
+
// CSV değerleri metin kalır (TC, sicil no, telefon); yalnız mantıksal değerler çevrilir.
|
|
892
|
+
obj[k] = ['true', 'false'].includes(v.toLowerCase()) ? v.toLowerCase() === 'true' : v;
|
|
893
|
+
});
|
|
894
|
+
return obj;
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/** --decimal , : Türk dışa aktarımlarındaki 38.000,50 → 38000.50 (TC, tarih, sicil no dokunulmaz). */
|
|
899
|
+
function commaDecimals(record) {
|
|
900
|
+
for (const [k, v] of Object.entries(record)) {
|
|
901
|
+
if (typeof v === 'string' && /^-?(\d{1,3}(\.\d{3})+|\d+)(,\d+)?$/.test(v) && /[.,]/.test(v)) record[k] = v.replace(/\./g, '').replace(',', '.');
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function readRecordsFile(file) {
|
|
906
|
+
if (!fs.existsSync(file)) throw new UsageError(`dosya bulunamadı: ${file}`);
|
|
907
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
908
|
+
if (/\.csv$/i.test(file)) return parseCsv(text);
|
|
909
|
+
let data;
|
|
910
|
+
try { data = JSON.parse(text); } catch (e) { throw new UsageError(`geçersiz JSON (${file}): ${e.message}`); }
|
|
911
|
+
const records = Array.isArray(data) ? data : data?.records;
|
|
912
|
+
if (!Array.isArray(records)) throw new UsageError('JSON bir dizi ya da {"records": [...]} olmalı');
|
|
913
|
+
return records;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
async function cmdMigrate(args) {
|
|
917
|
+
const { flags, fields, positional } = args;
|
|
918
|
+
const [kind, file] = positional;
|
|
919
|
+
if (!kind || !MIGRATE_KINDS[kind] || !file) {
|
|
920
|
+
throw new UsageError(`kullanım: empuno migrate <${Object.keys(MIGRATE_KINDS).join('|')}> <kayitlar.json|.csv> [--validate] [--on_existing update] [--employee_id N] [--chunk N] [--decimal ,]
|
|
921
|
+
alanlar: empuno describe data-migration.${kind && MIGRATE_KINDS[kind] ? kind : 'employees'}.commit · rehber: https://empuno.com/api-docs/agent#gecis`);
|
|
922
|
+
}
|
|
923
|
+
const auth = resolveAuth(flags);
|
|
924
|
+
requireKey(auth);
|
|
925
|
+
const opts = fieldMap(fields);
|
|
926
|
+
const spec = MIGRATE_KINDS[kind];
|
|
927
|
+
const raw = readRecordsFile(file);
|
|
928
|
+
if (opts.decimal === ',') raw.forEach(commaDecimals);
|
|
929
|
+
// Sıralanan türlerde sonuçtaki index yine dosyadaki sırayı gösterir.
|
|
930
|
+
const order = raw.map((_, i) => i);
|
|
931
|
+
if (spec.sortBy) order.sort((a, b) => String(raw[a][spec.sortBy] ?? '').localeCompare(String(raw[b][spec.sortBy] ?? '')) || a - b);
|
|
932
|
+
const records = order.map((i) => raw[i]);
|
|
933
|
+
const chunkSize = Math.max(1, Math.min(Number(opts.chunk) || spec.chunk, spec.chunk));
|
|
934
|
+
const validate = Boolean(flags.validate);
|
|
935
|
+
const baseDir = path.dirname(path.resolve(file));
|
|
936
|
+
|
|
937
|
+
const summary = { kind, file, dry_run: validate, records: records.length, created: 0, updated: 0, skipped: 0, failed: 0, results: [] };
|
|
938
|
+
|
|
939
|
+
for (let offset = 0; offset < records.length; offset += chunkSize) {
|
|
940
|
+
const chunk = records.slice(offset, offset + chunkSize);
|
|
941
|
+
let response;
|
|
942
|
+
|
|
943
|
+
if (kind === 'documents') {
|
|
944
|
+
const files = [];
|
|
945
|
+
const manifest = chunk.map((entry, i) => {
|
|
946
|
+
const filePath = path.resolve(baseDir, String(entry.file ?? ''));
|
|
947
|
+
files.push({ field: 'files[]', file: filePath });
|
|
948
|
+
return { ...entry, file: i };
|
|
949
|
+
});
|
|
950
|
+
for (const f of files) if (!fs.existsSync(f.file)) throw new UsageError(`dosya bulunamadı: ${f.file}`);
|
|
951
|
+
response = await request(auth, 'POST', spec.path, {
|
|
952
|
+
body: { manifest: JSON.stringify(manifest), ...(validate ? { dry_run: 1 } : {}) },
|
|
953
|
+
files,
|
|
954
|
+
dryRun: flags['dry-run'],
|
|
955
|
+
});
|
|
956
|
+
} else {
|
|
957
|
+
const body = { records: chunk, ...(validate ? { dry_run: true } : {}) };
|
|
958
|
+
if (opts.on_existing) body.on_existing = String(opts.on_existing);
|
|
959
|
+
if (opts.employee_id) body.employee_id = Number(opts.employee_id);
|
|
960
|
+
response = await request(auth, 'POST', spec.path, { body, dryRun: flags['dry-run'] });
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (response.dryRun) {
|
|
964
|
+
print(response.result);
|
|
965
|
+
return 0;
|
|
966
|
+
}
|
|
967
|
+
const r = response.result || {};
|
|
968
|
+
if (!response.ok) {
|
|
969
|
+
// Doğrulama hatası (422) tüm parçayı etkiler: sunucunun errors'ını kayıt indeksine çevir.
|
|
970
|
+
summary.failed += chunk.length;
|
|
971
|
+
summary.results.push({ chunk_offset: offset, indexes: order.slice(offset, offset + chunk.length), action: 'failed', status: response.status, message: r.message, errors: r.errors });
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
for (const key of ['created', 'updated', 'skipped', 'failed']) summary[key] += Number(r[key] || 0);
|
|
975
|
+
for (const row of r.results || []) summary.results.push({ ...row, index: order[offset + Number(row.index ?? 0)] });
|
|
976
|
+
if (r.periods) (summary.periods ??= []).push(...r.periods);
|
|
977
|
+
if (isTTY) warn(`${Math.min(offset + chunkSize, records.length)}/${records.length} işlendi`);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
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ı)' : ''}`;
|
|
981
|
+
if (wantsText()) {
|
|
982
|
+
process.stdout.write(`${summary.message}\n`);
|
|
983
|
+
for (const row of summary.results.filter((x) => x.action === 'failed')) {
|
|
984
|
+
process.stdout.write(` ✗ #${row.index ?? row.chunk_offset}: ${row.reason || row.message || ''}${row.errors ? ' ' + JSON.stringify(row.errors) : ''}\n`);
|
|
985
|
+
}
|
|
986
|
+
} else {
|
|
987
|
+
print(summary);
|
|
988
|
+
}
|
|
989
|
+
return summary.failed > 0 ? 1 : 0;
|
|
990
|
+
}
|
|
991
|
+
|
|
843
992
|
// ─── destek (yalnız empuno ekibinin ajanları — emp_staff_ jetonu) ────────────
|
|
844
993
|
|
|
845
994
|
function requireStaff(auth) {
|
|
@@ -968,6 +1117,16 @@ ALANLAR
|
|
|
968
1117
|
--alan @./dosya.pdf | --file alan=./dosya.pdf # multipart yükleme
|
|
969
1118
|
--query anahtar=değer # yazma isteğinde ek sorgu parametresi
|
|
970
1119
|
|
|
1120
|
+
GEÇİŞ (başka sistemden geçmiş veri — JSON ya da CSV; davet/e-posta/bildirim gitmez; tekrar çalıştırmak güvenli)
|
|
1121
|
+
empuno migrate employees calisanlar.csv --validate # önce yazmadan doğrula
|
|
1122
|
+
empuno migrate employees calisanlar.csv [--on_existing update] # ayrılmışlar dahil; TC ile eşler
|
|
1123
|
+
empuno migrate leaves izinler.json # her satırda identity_number|employee_number + leave_type
|
|
1124
|
+
empuno migrate advances avanslar.csv # geçmiş avans + devreden bakiye (deducted_amount)
|
|
1125
|
+
empuno migrate expenses masraflar.csv # ödenmiş geçmiş masraf (kategori adla)
|
|
1126
|
+
empuno migrate documents manifest.csv # file sütunu: dosya yolu; ≤10'arlı gönderilir
|
|
1127
|
+
empuno migrate payroll bordrolar.csv # geçmiş bordro (müşavir değerleri aynen); kilitli 'ödendi' dönem + kümülatif GV matrahı
|
|
1128
|
+
alanlar: empuno describe data-migration.<tür>.commit · sıra: çalışanlar → izin → avans → masraf → evrak
|
|
1129
|
+
|
|
971
1130
|
TALEPLER (geliştirme isteği, hata, eleştiri, soru → empuno ekibi)
|
|
972
1131
|
empuno tickets [--status active|open|answered|closed|all] [--unread] [--mine]
|
|
973
1132
|
empuno tickets open "Toplu izin onayı" --kind feature --body "…"|@not.md [--priority high] [--command leave-requests.approve]
|
|
@@ -994,8 +1153,9 @@ BELGE: https://empuno.com/api-docs/agent
|
|
|
994
1153
|
|
|
995
1154
|
async function main() {
|
|
996
1155
|
const argv = process.argv.slice(2);
|
|
997
|
-
const
|
|
998
|
-
const
|
|
1156
|
+
const sub0 = argv.find((a) => !a.startsWith('--')) ?? '';
|
|
1157
|
+
const ticketish = ['tickets', 'ticket', 'support'].includes(sub0);
|
|
1158
|
+
const args = parseArgs(argv, ticketish ? ['mine', 'unread', 'close', 'token-stdin'] : sub0 === 'migrate' ? ['validate'] : []);
|
|
999
1159
|
const [sub, ...rest] = args.positional;
|
|
1000
1160
|
args.positional = rest;
|
|
1001
1161
|
|
|
@@ -1004,7 +1164,7 @@ async function main() {
|
|
|
1004
1164
|
return 0;
|
|
1005
1165
|
}
|
|
1006
1166
|
if (args.flags.help) {
|
|
1007
|
-
if (['tickets', 'ticket', 'support', 'login', 'commands', 'describe', 'call', 'api'].includes(sub)) {
|
|
1167
|
+
if (['tickets', 'ticket', 'support', 'login', 'commands', 'describe', 'call', 'api', 'migrate', 'reference'].includes(sub)) {
|
|
1008
1168
|
process.stdout.write(HELP);
|
|
1009
1169
|
return 0;
|
|
1010
1170
|
}
|
|
@@ -1041,6 +1201,8 @@ async function main() {
|
|
|
1041
1201
|
case 'reference':
|
|
1042
1202
|
case 'ref':
|
|
1043
1203
|
return cmdReference(args);
|
|
1204
|
+
case 'migrate':
|
|
1205
|
+
return cmdMigrate(args);
|
|
1044
1206
|
case 'support':
|
|
1045
1207
|
return cmdSupport(args);
|
|
1046
1208
|
case 'run':
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "empuno",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.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": {
|