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
JavaScript
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);