UNPKG

e-fatura-cli

Version:

Bu paket komut satırı arayüzü (CLI) üzerinden e-Arşiv faturalarını listeler, imzalar, indirir ve daha fazlasını yapar.

1,335 lines (1,300 loc) 48.8 kB
#!/usr/bin/env node import dayjs from 'dayjs'; import { createOption, createCommand } from 'commander'; import * as dotenv from 'dotenv'; import { isEInvoiceApiResponseError, EInvoiceApiError, EInvoiceApiErrorCode, EInvoiceError, EInvoiceApi, InvoiceApprovalStatus, HourlySearchInterval } from 'e-fatura'; import chalk from 'chalk'; import figures from '@inquirer/figures'; import * as fs from 'node:fs/promises'; import axios from 'axios'; import ora from 'ora'; import ansiEscapes from 'ansi-escapes'; import * as os from 'node:os'; import * as path from 'node:path'; import { launch } from 'puppeteer-core'; import Listr from 'listr'; import { AbortPromptError } from '@inquirer/core'; import { table } from 'inquirer-table'; import input from '@inquirer/input'; import writeXlsxFile from 'write-excel-file/node'; import { stringify } from 'csv-stringify/sync'; import { unzipSync, zipSync } from 'fflate'; import customParseFormat from 'dayjs/plugin/customParseFormat.js'; class Print { static info(...args) { if (process.stdout.isTTY) { console.info(chalk.blue(figures.info), ...args); } } static warn(...args) { if (process.stdout.isTTY) { console.warn(chalk.yellow(figures.warning), ...args); } } static error(...args) { if (process.stdout.isTTY) { console.error(chalk.red(figures.cross), ...args); } } static success(...args) { if (process.stdout.isTTY) { console.log(chalk.green(figures.tick), ...args); } } } async function isFileExists(path) { try { const stat = await fs.stat(path); return stat.isFile(); } catch { return false; } } let retryCount = 0; async function tryPromise(factory, options) { const { isTTY = process.stdout.isTTY, exitOnError = true, loadingText, maxRetryCount = 3, whenSessionTimeout } = options || {}; let load; if (isTTY && loadingText) { load = ora(loadingText).start(); } const printError = (...args) => { if (isTTY) { Print.error(...args); } }; try { const result = await factory(); if (load) { load.stop(); process.stdout.write(ansiEscapes.cursorShow); } retryCount = 0; return result; } catch (err) { if (load) { load.stop(); process.stdout.write(ansiEscapes.cursorShow); } if (axios.isAxiosError(err)) { const error = err; const data = error.response?.data; if (whenSessionTimeout && isEInvoiceApiResponseError(data)) { const isSessionTimeoutError = data.messages.some((message) => message.type === '4'); if (retryCount <= maxRetryCount && isSessionTimeoutError) { return whenSessionTimeout(); } } printError(`${error.name}:`, error.message); } else if (err instanceof EInvoiceApiError) { if (whenSessionTimeout && retryCount <= maxRetryCount && err.errorCode === EInvoiceApiErrorCode.SESSION_TIMEOUT) { return whenSessionTimeout(); } else { const error = err; const response = error.response; if (isEInvoiceApiResponseError(response.data)) { const messages = response.data.messages.map((message) => `[${message.type}] ${message.text}`); printError(`${error.name}:`, ...messages); } else { printError(`${error.name}:`, err.message); } } } else if (err instanceof EInvoiceError) { const error = err; printError(`${error.name}:`, error.message); } else { printError(err); } if (exitOnError) { process.exit(1); } else { throw err; } } } const E_INVOICE_PATH = path.resolve(os.homedir(), 'e-fatura'); const DOWNLOAD_PATH = path.join(E_INVOICE_PATH, 'downloads'); const OUTPUTS_PATH = path.join(E_INVOICE_PATH, 'outputs'); const XSLT_OUTPUT_PATH = path.join(OUTPUTS_PATH, 'xslt'); const USERNAME_ENV_KEY = 'E_ARCHIVE_USERNAME'; const PASSWORD_ENV_KEY = 'E_ARCHIVE_PASSWORD'; const E_ARCHIVE_VERIFICATION_CODE_LENGTH = 6; class EInvoiceApiClient { static client = null; static credentials = null; static async exec(factory, envFile, options) { const client = this.client; if (!client) { Print.error('e-Arşiv istemcisi tanımlanmamış.'); process.exit(1); } return await tryPromise(() => factory(client), { whenSessionTimeout: async () => { await this.login(envFile, false); return await factory(client); }, ...options }); } static async login(envFile, isTTY) { await this.logout(); const credentials = await this.getCredentials(envFile); this.client = await tryPromise(async () => { const client = EInvoiceApi.create(); client.setTestMode(credentials.password === '1'); client.setCredentials(credentials); await client.initAccessToken(); return client; }, { isTTY, loadingText: 'Giriş yapılıyor...' }); return this.client; } static async logout() { if (this.client) { await tryPromise(() => this.client.logout()); this.client = null; } } static async getCredentials(envFile) { if (this.credentials) { return this.credentials; } if (envFile && (await isFileExists(envFile))) { dotenv.config({ path: envFile, override: true }); } const username = process.env[USERNAME_ENV_KEY]; const password = process.env[PASSWORD_ENV_KEY]; if (!username || !password) { Print.error(`${chalk.cyan(`$${USERNAME_ENV_KEY}`)} ve/veya ${chalk.cyan(`$${PASSWORD_ENV_KEY}`)} adlı ortam değişken(ler)i sağlanmadı.`); process.exit(1); } this.credentials = { username, password }; return this.credentials; } } function objectGet(object, path, defaultValue) { const keys = typeof path === 'string' ? path.split('.') : path; return keys.length > 0 ? keys.reduce((acc, curr) => (acc != null ? acc[curr] : defaultValue), object) : defaultValue; } const FORMAT_REGEX = /\{[a-zA-Z0-9-_.]+}/g; function formatValue(value, property) { switch (typeof value) { case 'string': case 'bigint': case 'number': case 'boolean': return `${value}`; case 'object': return JSON.stringify(value); default: return property; } } function format(tmpl, values) { if (!values) { return tmpl; } return tmpl.replace(FORMAT_REGEX, (property) => { const key = property.slice(1, -1); return property.replace(property, formatValue(objectGet(values, key, property), property)); }); } function capitalize(value) { return (value.charAt(0).toUpperCase() + value.slice(1)); } function shortenText(text) { text = text.trim(); if (text.length === 0) { return text; } const words = text .split(' ') .map((word) => word.trim()) .filter(Boolean); if (words.length < 3) { return text; } const lastWord = words.pop(); if (!lastWord || words.length === 0) { return text; } const chars = words.map((word) => word.charAt(0)); return `${chars.join('.')} ${lastWord}`; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function objectMap(object, callback) { return Object.keys(object).map((key, index) => callback(key, object[key], index)); } function launchBrowser(executablePath) { return launch({ args: ['--no-sandbox', '--disable-gpu', '--disable-setuid-sandbox'], timeout: 5_000, headless: true, executablePath: executablePath || process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN || '/usr/bin/chromium-browser' }); } async function isDirectoryExists(path) { try { const stat = await fs.stat(path); return stat.isDirectory(); } catch { return false; } } async function ensureDirectory(path, options) { if (!(await isDirectoryExists(path))) { await fs.mkdir(path, options); } } const DATE_FORMAT = 'YYYY-MM-DD'; var Period; (function (Period) { Period["YESTERDAY"] = "yesterday"; Period["THIS_WEEK"] = "this-week"; Period["PREV_WEEK"] = "prev-week"; Period["THIS_MONTH"] = "this-month"; Period["PREV_MONTH"] = "prev-month"; Period["THIS_YEAR"] = "this-year"; Period["PREV_YEAR"] = "prev-year"; })(Period || (Period = {})); function periodConvertToDateRange(period) { let startDate; let endDate; switch (period) { case Period.YESTERDAY: { const date = dayjs().subtract(1, 'day'); startDate = date.startOf('day'); endDate = date.endOf('day'); break; } case Period.THIS_WEEK: { const date = dayjs(); startDate = date.startOf('week'); endDate = date.endOf('week'); break; } case Period.PREV_WEEK: { const date = dayjs().subtract(1, 'week'); startDate = date.startOf('week'); endDate = date.endOf('week'); break; } case Period.THIS_MONTH: { const date = dayjs(); startDate = date.startOf('month'); endDate = date.endOf('day'); break; } case Period.PREV_MONTH: { const date = dayjs().subtract(1, 'month'); startDate = date.startOf('month'); endDate = date.endOf('month'); break; } case Period.THIS_YEAR: { const date = dayjs(); startDate = date.startOf('year'); endDate = date.endOf('day'); break; } case Period.PREV_YEAR: { const date = dayjs().subtract(1, 'year'); startDate = date.startOf('year'); endDate = date.endOf('year'); break; } } return { startDate: startDate.format(DATE_FORMAT), endDate: endDate.format(DATE_FORMAT) }; } function commandWithDateOptions(command) { const periodOption = createOption('--period [period]', 'Faturaların düzenlenlenme dönemi/periyodu').choices(Object.values(Period)); const startDateOption = createOption('--start-date [date]', `Faturaların düzenlenme dönemi aralığı başlangıç tarihi (${DATE_FORMAT} formatında)`); const endDateOption = createOption('--end-date [date]', `Faturaların düzenlenme dönemi aralığı bitiş tarihi (${DATE_FORMAT} formatında)`); return command .addOption(periodOption) .addOption(startDateOption) .addOption(endDateOption) .hook('preAction', (thisCommand) => { const opts = thisCommand.opts(); if (opts.period) { const dateRange = periodConvertToDateRange(opts.period); thisCommand.setOptionValue(startDateOption.attributeName(), dateRange.startDate); thisCommand.setOptionValue(endDateOption.attributeName(), dateRange.endDate); } else { if (opts.startDate) { const startDate = dayjs(opts.startDate, DATE_FORMAT); if (!startDate.isValid()) { thisCommand.error(`--${startDateOption.name()} is not a valid date.`); } } if (opts.endDate) { const endDate = dayjs(opts.endDate, DATE_FORMAT); if (!endDate.isValid()) { thisCommand.error(`--${endDateOption.name()} is not a valid date.`); } } } }); } function parsePdfOptions(options) { const opts = {}; const parseSizeValue = (value) => { const numeric = +value; return Number.isFinite(numeric) ? numeric : value; }; for (const option of options) { const [key, value] = option.split('='); switch (key) { case 'scale': { if (value) { opts.scale = Math.min(Math.max(0.1, +value), 2); } break; } case 'format': { if (value !== '') { opts.format = value; } else { opts.format = 'A4'; } break; } case 'width': { if (value) { opts.width = parseSizeValue(value); } break; } case 'height': { if (value) { opts.height = parseSizeValue(value); } break; } case 'landscape': { opts.landscape = value === undefined || value === 'true'; break; } case 'pageRanges': { if (value) { opts.pageRanges = value; } break; } case 'preferCSSPageSize': { opts.preferCSSPageSize = value === undefined || value === 'true'; break; } case 'margin': { if (value) { const marginSize = parseSizeValue(value); opts.margin = { top: marginSize, bottom: marginSize, left: marginSize, right: marginSize }; } break; } case 'marginTop': case 'marginBottom': case 'marginLeft': case 'marginRight': { if (value) { const property = key .replace('margin', '') .toLowerCase(); opts.margin = { ...opts.margin, [property]: parseSizeValue(value) }; } break; } case 'marginVertical': { if (value) { const marginSize = parseSizeValue(value); opts.margin = { ...opts.margin, top: marginSize, bottom: marginSize }; } break; } case 'marginHorizontal': { if (value) { const marginSize = parseSizeValue(value); opts.margin = { ...opts.margin, left: marginSize, right: marginSize }; } break; } } } return opts; } function commandWidthPdfOptions(command) { const pdfOptions = createOption('--pdf-options [options...]', 'PDF oluşturma seçenekleri'); return command .addOption(pdfOptions) .option('--browser-executable-path [path]', 'PDF oluşturmak için kullanılacak tarayıcının çalıştırılabilir dosya yolu') .hook('preAction', (thisCommand) => { const pdfOptionsValue = thisCommand.getOptionValue(pdfOptions.attributeName()); if (pdfOptionsValue != null) { thisCommand.setOptionValue(pdfOptions.attributeName(), parsePdfOptions(pdfOptionsValue)); } }); } function commandWithEnvFileOption(command) { const envFileOption = createOption('--env-file [file]', 'e-Arşiv giriş bilgilerinin bulunduğu ortam değişkenleri dosyasının yolu').default(path.resolve('.env')); return command.addOption(envFileOption).hook('preAction', (thisCommand) => { const envFilePath = thisCommand.getOptionValue(envFileOption.attributeName()); thisCommand.setOptionValue(envFileOption.attributeName(), path.resolve(envFilePath)); }); } const invoiceApprovalStatusKeyMap = { approved: InvoiceApprovalStatus.APPROVED, unapproved: InvoiceApprovalStatus.UNAPPROVED, deleted: InvoiceApprovalStatus.DELETED }; function commandWithStatusOption(command) { const statusOption = createOption('--status [status]', 'Faturaların onay durumu').choices(Object.keys(invoiceApprovalStatusKeyMap)); return command.addOption(statusOption).hook('preAction', (thisCommand) => { const statusKey = thisCommand.getOptionValue(statusOption.attributeName()); if (statusKey) { thisCommand.setOptionValue(statusOption.attributeName(), invoiceApprovalStatusKeyMap[statusKey]); } }); } const hourlySearchIntervalKeyMap = { none: HourlySearchInterval.NONE, 'first-half': HourlySearchInterval.FIRST_HALF, 'last-half': HourlySearchInterval.LAST_HALF }; function commandWithHourlySearchIntervalOption(command) { const hourlySearchIntervalOption = createOption('--hourly-search-interval [value]', 'Adınıza düzenlenen faturaların günün hangi aralığında düzenlendiği').choices(Object.keys(hourlySearchIntervalKeyMap)); return command .option('--issued-to-me', 'Adınıza düzenlenen faturaları listele') .addOption(hourlySearchIntervalOption) .hook('preAction', (thisCommand) => { const hourlySearchIntervalKey = thisCommand.getOptionValue(hourlySearchIntervalOption.attributeName()); if (hourlySearchIntervalKey) { thisCommand.setOptionValue(hourlySearchIntervalOption.attributeName(), hourlySearchIntervalKeyMap[hourlySearchIntervalKey]); } }); } function arrayChunk(array, size) { let i = 0; const chunks = []; while (i < array.length) { chunks.push(array.slice(i, (i += size))); } return chunks; } function createListr(tasks, options) { return new Listr(tasks, options); } class InvoiceTasksContext { countOfTasks; countOfSkips = 0; countOfErrors = 0; constructor(countOfTasks) { this.countOfTasks = countOfTasks; } get countOfCompleted() { return this.countOfTasks - this.countOfSkips - this.countOfErrors; } } async function createInvoiceTasks(invoices, options) { const { task, batchSize = 30, taskTitle = ({ invoice }) => `[${invoice.uuid}] ${shortenText(invoice.titleOrFullName)} (${invoice.documentDate})` } = options; const chunks = arrayChunk(invoices, batchSize); const tasks = chunks.map((chunkItems) => createListr(chunkItems.map((invoice, index) => ({ title: taskTitle({ index, invoice }), task: async (ctx, taskWrapper) => { const prevSkipTask = taskWrapper.skip.bind(taskWrapper); taskWrapper.skip = function (message) { ctx.countOfSkips++; return prevSkipTask(message); }; try { return await task(invoice, taskWrapper); } catch (err) { ctx.countOfErrors++; throw err; } } })), { exitOnError: false })); const context = new InvoiceTasksContext(invoices.length); for (const task of tasks) { try { await task.run(context); } catch { // no-empty } } return context; } const basicInvoiceKeyMap = { uuid: 'ETTN', titleOrFullName: 'Unvan/Ad Soyad', documentDate: 'Düzenlenme Tarihi', taxOrIdentityNumber: 'VKN/TCKN', documentType: 'Belge Türü', documentNumber: 'Belge No', approvalStatus: 'Durum' }; const columns = [ { header: '#', renderCell: (_, index) => index + 1 }, { header: basicInvoiceKeyMap.uuid, accessorKey: 'uuid' }, { header: basicInvoiceKeyMap.titleOrFullName, width: 25, accessorKey: 'titleOrFullName' }, { header: basicInvoiceKeyMap.documentDate, align: 'center', accessorKey: 'documentDate' }, { header: basicInvoiceKeyMap.documentType, align: 'center', accessorKey: 'documentType' }, { header: basicInvoiceKeyMap.taxOrIdentityNumber, accessorKey: 'taxOrIdentityNumber' }, { header: basicInvoiceKeyMap.documentDate, accessorKey: 'documentNumber' }, { header: basicInvoiceKeyMap.approvalStatus, accessorKey: 'approvalStatus' } ]; async function createInvoicesTable(invoices, options) { const { multiple = true, selectable = true, emptyDescription = 'İşlem yapılacak fatura bulunamadı.', emptyResultDescription = 'Herhangi bir fatura seçmedin. İşlem yapmak için en az bir fatura seçin.' } = options || {}; if (invoices.length === 0) { Print.warn(emptyDescription); return process.exit(); } const controller = new AbortController(); const result = await table({ data: invoices, columns, pageSize: 10, multiple: multiple, selectable: selectable, renderHeader: ({ multiple, selectable, abortController }) => { const header = []; if (selectable && multiple) { header.push(`${chalk.cyan('<space>')} satırı seçmek için`); } header.push(`${chalk.cyan('<up>')} ve ${chalk.cyan('<down>')} satırlar arasında gezinmek için`); if (selectable) { header.push(`${chalk.cyan('<enter>')} ${multiple ? 'seçimleri' : 'seçimi'} onaylamak için`); } if (abortController) { header.push(`${chalk.cyan('<backspace>')} iptal etmek için`); } return header.length ? `(${header.join(', ')}) tuşlarına basın` : null; }, renderFooter: ({ data, range, pageSize, selectable, selectedRows }) => { const footer = []; const countOfData = data.length; if (countOfData > pageSize) { const [startRange, endRange] = range; footer.push(`Toplam ${countOfData} ögeden ${startRange} - ${endRange} gösteriliyor.`); if (selectable) { footer.push(` (${selectedRows.length} öge seçildi.)`); } } else if (selectable) { footer.push(`${selectedRows.length} öge seçildi.`); } return footer.join(''); }, abortController: controller }, { signal: controller.signal }).catch((error) => { if (error.name === AbortPromptError.name) { return undefined; } throw error; }); if (!result) { Print.info('İşlem iptal edildi.'); return process.exit(); } if (Array.isArray(result) && result.length === 0) { Print.warn(emptyResultDescription); return process.exit(); } return result; } const leadingZero = (value) => value.toString().padStart(2, '0'); function createFilenameFromTmpl(tmpl, options) { const { ext = 'zip', values } = options || {}; const date = new Date(); return format(tmpl, { ...values, ext, day: leadingZero(date.getDate()), date: leadingZero(date.getDate()), year: date.getFullYear(), month: leadingZero(date.getMonth() + 1), hour: leadingZero(date.getHours()), minute: leadingZero(date.getMinutes()) }); } const listInvoicesCommand = createCommand('list').description('e-Arşiv üzerinde bulunan faturaları listele'); commandWithDateOptions(listInvoicesCommand); commandWithStatusOption(listInvoicesCommand); commandWithEnvFileOption(listInvoicesCommand); commandWithHourlySearchIntervalOption(listInvoicesCommand); listInvoicesCommand.action(async function () { if (!process.stdout.isTTY) { process.exit(); } const opts = this.opts(); await EInvoiceApiClient.login(opts.envFile); const invoices = await EInvoiceApiClient.exec((client) => !opts.issuedToMe ? client.getBasicInvoices({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: opts.status }) : client.getBasicInvoicesIssuedToMe({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: opts.status, hourlySearchInterval: opts.hourlySearchInterval }), opts.envFile, { loadingText: 'Faturalar yükleniyor...' }); await createInvoicesTable(invoices, { selectable: false, emptyDescription: 'Listelenecek fatura bulunamadı.' }); }); const signInvoicesCommand = createCommand('sign').description('e-Arşiv üzerinde bulunan faturaları imzala'); commandWithDateOptions(signInvoicesCommand); commandWithEnvFileOption(signInvoicesCommand); signInvoicesCommand.action(async function () { if (!process.stdout.isTTY) { process.exit(); } const opts = this.opts(); const credentials = await EInvoiceApiClient.getCredentials(opts.envFile); if (credentials.password === '1') { Print.warn('Fatura imzalama işlemi test hesapları ile gerçekleştirilemez.'); process.exit(); } await EInvoiceApiClient.login(opts.envFile); const invoices = await EInvoiceApiClient.exec((client) => client.getBasicInvoices({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: InvoiceApprovalStatus.UNAPPROVED }), opts.envFile, { loadingText: 'Faturalar yükleniyor...' }); const selectedInvoices = await createInvoicesTable(invoices); const sendCodeResult = await EInvoiceApiClient.exec((client) => client.sendSMSCode(), opts.envFile, { loadingText: 'Doğrulama kodu gönderiliyor...' }); const verificationCode = await input({ message: `(${chalk.cyan(sendCodeResult.phoneNumber)}) telefon numarasına gönderilen doğrulama kodunu girin:`, validate: (value) => value.length === E_ARCHIVE_VERIFICATION_CODE_LENGTH || `Doğrulama kodu ${E_ARCHIVE_VERIFICATION_CODE_LENGTH} haneli olmalıdır.` }); const verifyCodeResult = await EInvoiceApiClient.exec((client) => client.signInvoices(verificationCode, sendCodeResult.oid, selectedInvoices), opts.envFile, { loadingText: 'Faturalar imzalanıyor...' }); if (verifyCodeResult) { Print.success('Faturalar başarılı bir şekilde imzalandı.'); } else { Print.error('Faturalar imzalanamadı.'); } }); const invoiceExportTypeMap = { csv: true, json: true, excel: true }; const invoiceExtensionMap = { csv: 'csv', json: 'json', excel: 'xlsx' }; const normalizeDateOptions = (options) => ({ ...options, startDate: options.startDate.replace(/\//g, '-'), endDate: options.endDate.replace(/\//g, '-') }); const actions$1 = { async writeToFile(data, options) { const { type, outputPath, filenameFormat } = options; if (process.stdout.isTTY) { const filename = createFilenameFromTmpl(filenameFormat, { ext: invoiceExtensionMap[type], values: { this: normalizeDateOptions(options) } }); const outputDir = path.join(path.resolve(outputPath), type); const filepath = path.join(outputDir, filename); await ensureDirectory(outputDir, { recursive: true }); if (await isFileExists(filepath)) { Print.warn(`"${filepath}" adlı dosya zaten var.`); process.exit(); } const fileData = await data(); await fs.writeFile(filepath, fileData, typeof fileData === 'string' ? 'utf-8' : undefined); Print.success(capitalize(type), 'dosyası şuraya kaydedildi:', chalk.bold.green(filepath)); } else { process.stdout.write(await data()); } }, async invoicesExportToCsv(invoices, options) { await actions$1.writeToFile(() => Promise.resolve(stringify(invoices, { header: true, encoding: 'utf-8', columns: objectMap(basicInvoiceKeyMap, (key, header) => ({ key, header })) })), options); }, async invoicesExportToJson(invoices, options) { await actions$1.writeToFile(() => Promise.resolve(JSON.stringify(invoices)), options); }, async invoicesExportToExcel(invoices, options) { const schema = [ { column: basicInvoiceKeyMap.uuid, value: (object) => object.uuid, width: 15 }, { column: basicInvoiceKeyMap.titleOrFullName, value: (object) => object.titleOrFullName, width: 20 }, { column: basicInvoiceKeyMap.documentDate, value: (object) => object.documentDate, width: 18, align: 'center' }, { column: basicInvoiceKeyMap.taxOrIdentityNumber, value: (object) => object.taxOrIdentityNumber, width: 12, align: 'center' }, { column: basicInvoiceKeyMap.documentType, value: (object) => object.documentType, width: 12, align: 'center' }, { column: basicInvoiceKeyMap.documentNumber, value: (object) => object.documentNumber, width: 18, align: 'center' }, { column: basicInvoiceKeyMap.approvalStatus, value: (object) => object.approvalStatus, width: 12, align: 'center' } ]; await actions$1.writeToFile(() => writeXlsxFile(invoices, { schema, buffer: true, getHeaderStyle: (columnSchema) => ({ align: 'center', fontFamily: columnSchema.fontFamily, fontWeight: 'bold' }) }), options); } }; const exportInvoicesCommand = createCommand('export') .description('e-Arşiv üzerinde bulunan temel fatura bilgilerini dışa aktar') .addOption(createOption('--type [type]', 'Faturaların hangi formatta dışarı aktarılacağı') .default('json') .choices(Object.keys(invoiceExportTypeMap))); commandWithDateOptions(exportInvoicesCommand); commandWithStatusOption(exportInvoicesCommand); commandWithEnvFileOption(exportInvoicesCommand); commandWithHourlySearchIntervalOption(exportInvoicesCommand); exportInvoicesCommand .option('--output-path [path]', 'Çıktıların kaydedileceği dizin yolu', OUTPUTS_PATH) .option('--filename-format [format]', 'Çıktının dosya adı formatı', '{this.startDate}-{this.endDate}.{ext}') .option('-i, --interactive', 'Belirli faturaları dışa aktarmak istiyorsanız bu seçeneği kullanın. Eğer seçenek aktifse faturaları seçmeniz için bir tablo arayüzü gösterilecektir') .action(async function () { const opts = this.opts(); if (opts.interactive && !process.stdout.isTTY) { process.exit(); } await EInvoiceApiClient.login(opts.envFile); const invoices = await EInvoiceApiClient.exec((client) => !opts.issuedToMe ? client.getBasicInvoices({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: opts.status }) : client.getBasicInvoicesIssuedToMe({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: opts.status, hourlySearchInterval: opts.hourlySearchInterval }), opts.envFile, { loadingText: 'Faturalar yükleniyor...' }); let selectedInvoices = invoices; if (opts.interactive) { selectedInvoices = await createInvoicesTable(invoices); } switch (opts.type) { case 'csv': await actions$1.invoicesExportToCsv(selectedInvoices, opts); break; case 'json': await actions$1.invoicesExportToJson(selectedInvoices, opts); break; case 'excel': await actions$1.invoicesExportToExcel(selectedInvoices, opts); break; } }); const taskTitleWithFilename$1 = (options) => { return ({ invoice }) => { const filename = createFilenameFromTmpl(options.filenameFormat, { ext: options.type === 'zip+pdf' ? 'zip' : options.type, values: { invoice } }); return `[${filename}] ${shortenText(invoice.titleOrFullName)} (${invoice.documentDate})`; }; }; const invoiceDownloadTypeMap = { xml: 'XML', pdf: 'PDF', html: 'HTML', zip: 'ZIP', 'zip+pdf': 'ZIP + PDF' }; const actions = { async downloadInvoicesAsPdf(invoices, options) { const { envFile, filenameFormat, downloadPath, pdfOptions, browserExecutablePath } = options; const pdfDownloadPath = path.resolve(downloadPath, 'pdf'); await ensureDirectory(pdfDownloadPath, { recursive: true }); const browser = await launchBrowser(browserExecutablePath); try { const context = await createInvoiceTasks(invoices, { task: async (invoice, task) => { const filename = createFilenameFromTmpl(filenameFormat, { ext: 'pdf', values: { invoice } }); const filepath = path.join(pdfDownloadPath, filename); if (await isFileExists(filepath)) { return task.skip(`"${filename}" adlı dosya zaten var.`); } const invoiceHtml = await EInvoiceApiClient.exec((client) => client.getInvoiceHtml(invoice), envFile, { isTTY: false, exitOnError: false }); const page = await browser.newPage(); await page.setContent(invoiceHtml, { waitUntil: 'domcontentloaded' }); const pdfBytes = await page.pdf({ ...pdfOptions, path: undefined }); await page.close(); await fs.writeFile(filepath, pdfBytes); await sleep(100); }, taskTitle: taskTitleWithFilename$1(options) }); if (context.countOfCompleted > 0) { Print.success(context.countOfCompleted, 'adet fatura şuraya kaydedildi:', chalk.bold.green(pdfDownloadPath)); } } catch { // no-empty } finally { await browser.close(); } }, async downloadInvoicesAsZip(invoices, options, includePdfFile) { const { envFile, filenameFormat, downloadPath, pdfOptions, browserExecutablePath } = options; const zipDownloadPath = path.resolve(downloadPath, 'zip'); await ensureDirectory(zipDownloadPath, { recursive: true }); let browser; if (includePdfFile) { browser = await launchBrowser(browserExecutablePath); } try { const context = await createInvoiceTasks(invoices, { task: async (invoice, task) => { const filename = createFilenameFromTmpl(filenameFormat, { ext: 'zip', values: { invoice } }); const filepath = path.join(zipDownloadPath, filename); if (await isFileExists(filepath)) { return task.skip(`"${filename}" adlı dosya zaten var.`); } const zipBuffer = await EInvoiceApiClient.exec((client) => client.getInvoiceZip(invoice), envFile, { isTTY: false, exitOnError: false }); if (browser && includePdfFile) { const page = await browser.newPage(); const zipEntries = unzipSync(zipBuffer); const htmlFileBytes = zipEntries[`${invoice.uuid}_f.html`]; const htmlFileContent = Buffer.from(htmlFileBytes).toString('utf-8'); await page.setContent(htmlFileContent, { waitUntil: 'domcontentloaded' }); const pdfBytes = await page.pdf({ ...pdfOptions, path: undefined }); await page.close(); await fs.writeFile(filepath, zipSync({ ...zipEntries, [`${invoice.uuid}_f.pdf`]: pdfBytes })); } else { await fs.writeFile(filepath, zipBuffer); } await sleep(100); }, taskTitle: taskTitleWithFilename$1(options) }); if (context.countOfCompleted > 0) { Print.success(context.countOfCompleted, 'adet fatura şuraya kaydedildi:', chalk.bold.green(zipDownloadPath)); } } catch { // no-empty } finally { if (browser) { await browser.close(); } } }, async downloadInvoicesAsHtml(invoices, options) { const { envFile, filenameFormat, downloadPath } = options; const htmlDownloadPath = path.resolve(downloadPath, 'html'); await ensureDirectory(htmlDownloadPath, { recursive: true }); const context = await createInvoiceTasks(invoices, { task: async (invoice, task) => { const filename = createFilenameFromTmpl(filenameFormat, { ext: 'html', values: { invoice } }); const filepath = path.join(htmlDownloadPath, filename); if (await isFileExists(filepath)) { return task.skip(`"${filename}" adlı dosya zaten var.`); } const invoiceHtml = await EInvoiceApiClient.exec((client) => client.getInvoiceHtml(invoice), envFile, { isTTY: false, exitOnError: false }); await fs.writeFile(filepath, invoiceHtml); await sleep(100); }, taskTitle: taskTitleWithFilename$1(options) }); if (context.countOfCompleted > 0) { Print.success(context.countOfCompleted, 'adet fatura şuraya kaydedildi:', chalk.bold.green(htmlDownloadPath)); } }, async downloadInvoicesAsXml(invoices, options) { const { envFile, filenameFormat, downloadPath } = options; const xmlDownloadPath = path.resolve(downloadPath, 'xml'); await ensureDirectory(xmlDownloadPath, { recursive: true }); const context = await createInvoiceTasks(invoices, { task: async (invoice, task) => { const filename = createFilenameFromTmpl(filenameFormat, { ext: 'xml', values: { invoice } }); const filepath = path.join(xmlDownloadPath, filename); if (await isFileExists(filepath)) { return task.skip(`"${filename}" adlı dosya zaten var.`); } const invoiceXml = await EInvoiceApiClient.exec((client) => client.getInvoiceXml(invoice), envFile, { isTTY: false, exitOnError: false }); await fs.writeFile(filepath, invoiceXml); await sleep(100); }, taskTitle: taskTitleWithFilename$1(options) }); if (context.countOfCompleted > 0) { Print.success(context.countOfCompleted, 'adet fatura şuraya kaydedildi:', chalk.bold.green(xmlDownloadPath)); } } }; const downloadInvoicesCommand = createCommand('download') .description('e-Arşiv üzerinde bulunan faturaları indir') .addOption(createOption('--type [type]', 'Faturaların hangi formatta indirileceği') .choices(Object.keys(invoiceDownloadTypeMap)) .default('zip')); commandWidthPdfOptions(downloadInvoicesCommand); commandWithDateOptions(downloadInvoicesCommand); commandWithStatusOption(downloadInvoicesCommand); commandWithEnvFileOption(downloadInvoicesCommand); downloadInvoicesCommand .option('--download-path [path]', 'Faturaların indirileceği dizin yolu', DOWNLOAD_PATH) .option('--filename-format [format]', 'İndirilecek faturanın dosya adı formatı', '{invoice.uuid}.{ext}') .option('-i, --interactive', 'Belirli faturaları indirmek istiyorsanız bu seçeneği kullanın. Eğer seçenek aktifse faturaları seçmeniz için bir tablo arayüzü gösterilecektir') .action(async function () { if (!process.stdout.isTTY) { process.exit(); } const opts = this.opts(); await EInvoiceApiClient.login(opts.envFile); const invoices = await EInvoiceApiClient.exec((client) => client.getBasicInvoices({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: opts.status }), opts.envFile, { loadingText: 'Faturalar yükleniyor...' }); let selectedInvoices = invoices; if (opts.interactive) { selectedInvoices = await createInvoicesTable(invoices); } else { if (selectedInvoices.length === 0) { Print.warn('İndirilecek fatura bulunamadı.'); return process.exit(); } } switch (opts.type) { case 'pdf': await actions.downloadInvoicesAsPdf(selectedInvoices, opts); break; case 'xml': await actions.downloadInvoicesAsXml(selectedInvoices, opts); break; case 'html': await actions.downloadInvoicesAsHtml(selectedInvoices, opts); break; case 'zip': case 'zip+pdf': await actions.downloadInvoicesAsZip(selectedInvoices, opts, opts.type === 'zip+pdf'); break; } }); const taskTitleWithFilename = (options) => { return ({ invoice }) => { const filename = createFilenameFromTmpl(options.filenameFormat, { ext: 'zip', values: { invoice } }); return `[${filename}] ${shortenText(invoice.titleOrFullName)} (${invoice.documentDate})`; }; }; const xsltRendererCommand = createCommand('xslt-renderer').description('e-Arşiv üzerinde bulunan faturaları xslt ile işle'); commandWidthPdfOptions(xsltRendererCommand); commandWithDateOptions(xsltRendererCommand); commandWithStatusOption(xsltRendererCommand); commandWithEnvFileOption(xsltRendererCommand); xsltRendererCommand .option('--output-path [path]', 'İşlenen faturaların kaydedileceği dizin yolu', XSLT_OUTPUT_PATH) .option('--filename-format [format]', 'Fatura çıktısının dosya adı formatı', '{invoice.uuid}.zip') .option('--include-pdf', 'Aktifse fatura çıktısına PDF dosyası da dahil edilir') .option('--xsltproc-executable-path [path]', 'xsltproc komut satırı uygulamasının çalıştırılabilir dosya yolu', '/usr/bin/xsltproc') .arguments('<xslt-path>') .action(async function (xsltPath) { if (!process.stdout.isTTY) { process.exit(); } const opts = this.opts(); const xsltFilepath = path.resolve(xsltPath); if (!(await isFileExists(xsltFilepath))) { Print.error('Sağladığınız xslt dosyası bulunamadı. Dosya yolunu kontrol ederek tekrar deneyin.'); process.exit(1); } await EInvoiceApiClient.login(opts.envFile); const invoices = await EInvoiceApiClient.exec((client) => client.getBasicInvoices({ startDate: opts.startDate, endDate: opts.endDate, approvalStatus: opts.status }), opts.envFile, { loadingText: 'Faturalar yükleniyor...' }); const selectedInvoices = await createInvoicesTable(invoices); const xsltOutputPath = path.resolve(opts.outputPath); let browser; await ensureDirectory(xsltOutputPath, { recursive: true }); if (opts.includePdf) { browser = await launchBrowser(opts.browserExecutablePath); } try { const context = await createInvoiceTasks(selectedInvoices, { async task(invoice, task) { const zipFilename = createFilenameFromTmpl(opts.filenameFormat, { ext: 'zip', values: { invoice } }); const zipFilepath = path.join(xsltOutputPath, zipFilename); if (await isFileExists(zipFilepath)) { return task.skip(`"${zipFilename}" adlı dosya zaten var.`); } const xsltRenderer = await EInvoiceApiClient.exec((client) => Promise.resolve(client.invoiceXsltRenderer(invoice, xsltFilepath, { xsltprocOptions: { xsltprocExecutablePath: opts.xsltprocExecutablePath } })), opts.envFile); const xmlResult = await xsltRenderer.toXml(); const htmlResult = await xsltRenderer.toHtml(); const zipEntries = { [`${invoice.uuid}.xml`]: xmlResult, [`${invoice.uuid}.html`]: htmlResult }; if (browser && opts.includePdf) { const page = await browser.newPage(); await page.setContent(htmlResult.toString('utf-8'), { waitUntil: 'domcontentloaded' }); zipEntries[`${invoice.uuid}.pdf`] = await page.pdf({ ...opts.pdfOptions, path: undefined }); await page.close(); } await fs.writeFile(zipFilepath, zipSync(zipEntries)); }, taskTitle: taskTitleWithFilename(opts) }); if (context.countOfCompleted > 0) { Print.success(context.countOfCompleted, 'adet fatura şuraya kaydedildi:', chalk.bold.green(xsltOutputPath)); } } catch (_) { // no-empty } finally { if (browser) { await browser.close(); } } }); const commands = [ listInvoicesCommand, signInvoicesCommand, exportInvoicesCommand, downloadInvoicesCommand, xsltRendererCommand ]; const version = "1.0.1"; const description = "Bu paket komut satırı arayüzü (CLI) üzerinden e-Arşiv faturalarını listeler, imzalar, indirir ve daha fazlasını yapar."; dayjs.extend(customParseFormat); const program = createCommand('e-fatura'); process.on('SIGINT', () => { EInvoiceApiClient.logout().finally(() => { process.exit(0); }); }); process.on('SIGHUP', () => { EInvoiceApiClient.logout().finally(() => { process.exit(0); }); }); process.on('beforeExit', async () => { await EInvoiceApiClient.logout(); }); for (const command of commands) { program.addCommand(command); } program.version(version).description(description).parse(process.argv);