f43
This commit is contained in:
parent
a0550df293
commit
fa328348a3
52
api.php
52
api.php
@ -42,7 +42,7 @@ function get_csv_sample($filePath) {
|
|||||||
return implode('\n', $sample);
|
return implode('\n', $sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
function call_openai_api($inputSample, $outputSample) {
|
function call_openai_api($inputSample, $outputSample, $customInstructions) {
|
||||||
$apiKey = getenv('OPENAI_API_KEY');
|
$apiKey = getenv('OPENAI_API_KEY');
|
||||||
if (!$apiKey) {
|
if (!$apiKey) {
|
||||||
return ['error' => 'OpenAI API key not configured.'];
|
return ['error' => 'OpenAI API key not configured.'];
|
||||||
@ -50,7 +50,10 @@ function call_openai_api($inputSample, $outputSample) {
|
|||||||
|
|
||||||
$prompt = "I have two CSV files. I want to convert the input file to the output file format.\n\n";
|
$prompt = "I have two CSV files. I want to convert the input file to the output file format.\n\n";
|
||||||
$prompt .= "Here is a sample of the input CSV file:\n---\n" . $inputSample . "\n---\n\n";
|
$prompt .= "Here is a sample of the input CSV file:\n---\n" . $inputSample . "\n---\n\n";
|
||||||
$prompt .= "Here is a sample of the output CSV file:\n---\n" . $outputSample . "\n---\n\n";
|
$prompt .= "Here is a sample of the output CSV file:\n---" . $outputSample . "---\n\n";
|
||||||
|
if (!empty($customInstructions)) {
|
||||||
|
$prompt .= "Additionally, here are some custom instructions to follow:\n---" . $customInstructions . "---\n\n";
|
||||||
|
}
|
||||||
$prompt .= "Please generate a PHP script that contains a single function `convert_csv($input_file_path, $output_file_path)`.\n";
|
$prompt .= "Please generate a PHP script that contains a single function `convert_csv($input_file_path, $output_file_path)`.\n";
|
||||||
$prompt .= "This function should read the data from the `$input_file_path`, transform it into the format of the output sample, and write the result to `$output_file_path`.\n";
|
$prompt .= "This function should read the data from the `$input_file_path`, transform it into the format of the output sample, and write the result to `$output_file_path`.\n";
|
||||||
$prompt .= "The script should handle CSV headers correctly. Do not include any explanations, just the raw PHP code starting with `<?php`.";
|
$prompt .= "The script should handle CSV headers correctly. Do not include any explanations, just the raw PHP code starting with `<?php`.";
|
||||||
@ -103,6 +106,32 @@ function call_openai_api($inputSample, $outputSample) {
|
|||||||
load_env(__DIR__ . '/.env');
|
load_env(__DIR__ . '/.env');
|
||||||
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
$action = $_POST['action'] ?? $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
function sanitize_filename($filename) {
|
||||||
|
// Remove PHP extension if present
|
||||||
|
if (substr($filename, -4) === '.php') {
|
||||||
|
$filename = substr($filename, 0, -4);
|
||||||
|
}
|
||||||
|
// Replace spaces and special characters with hyphens
|
||||||
|
$filename = preg_replace('/[^a-zA-Z0-9_\.-]/', '-', $filename);
|
||||||
|
// Remove leading/trailing hyphens
|
||||||
|
$filename = trim($filename, '-');
|
||||||
|
// Ensure it's not empty
|
||||||
|
if (empty($filename)) {
|
||||||
|
return 'mapping-' . time();
|
||||||
|
}
|
||||||
|
return $filename . '.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($action === 'list_mappings') {
|
||||||
|
$mappingDir = __DIR__ . '/mappings';
|
||||||
|
$files = glob($mappingDir . '/*.php');
|
||||||
|
$mappings = array_map(function($file) {
|
||||||
|
return basename($file);
|
||||||
|
}, $files);
|
||||||
|
echo json_encode(['success' => true, 'mappings' => $mappings]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($action === 'build_mapping') {
|
if ($action === 'build_mapping') {
|
||||||
if (!isset($_FILES['inputSample']) || !isset($_FILES['outputSample'])) {
|
if (!isset($_FILES['inputSample']) || !isset($_FILES['outputSample'])) {
|
||||||
echo json_encode(['success' => false, 'error' => 'Input and output sample files are required.']);
|
echo json_encode(['success' => false, 'error' => 'Input and output sample files are required.']);
|
||||||
@ -114,13 +143,14 @@ if ($action === 'build_mapping') {
|
|||||||
|
|
||||||
$inputSampleContent = get_csv_sample($inputTmpPath);
|
$inputSampleContent = get_csv_sample($inputTmpPath);
|
||||||
$outputSampleContent = get_csv_sample($outputTmpPath);
|
$outputSampleContent = get_csv_sample($outputTmpPath);
|
||||||
|
$customInstructions = $_POST['customInstructions'] ?? '';
|
||||||
|
|
||||||
if ($inputSampleContent === null || $outputSampleContent === null) {
|
if ($inputSampleContent === null || $outputSampleContent === null) {
|
||||||
echo json_encode(['success' => false, 'error' => 'Failed to read sample files.']);
|
echo json_encode(['success' => false, 'error' => 'Failed to read sample files.']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = call_openai_api($inputSampleContent, $outputSampleContent);
|
$result = call_openai_api($inputSampleContent, $outputSampleContent, $customInstructions);
|
||||||
|
|
||||||
if (isset($result['error'])) {
|
if (isset($result['error'])) {
|
||||||
echo json_encode(['success' => false, 'error' => $result['error'], 'details' => $result['details'] ?? null]);
|
echo json_encode(['success' => false, 'error' => $result['error'], 'details' => $result['details'] ?? null]);
|
||||||
@ -131,10 +161,20 @@ if ($action === 'build_mapping') {
|
|||||||
if (!is_dir($mappingDir)) {
|
if (!is_dir($mappingDir)) {
|
||||||
mkdir($mappingDir, 0775, true);
|
mkdir($mappingDir, 0775, true);
|
||||||
}
|
}
|
||||||
$mappingFile = 'mappings/mapping_' . time() . '.php';
|
|
||||||
file_put_contents($mappingFile, $result['code']);
|
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'mapping_file' => $mappingFile]);
|
$mappingName = $_POST['mappingName'] ?? '';
|
||||||
|
if (empty($mappingName)) {
|
||||||
|
$inputName = pathinfo($_FILES['inputSample']['name'], PATHINFO_FILENAME);
|
||||||
|
$outputName = pathinfo($_FILES['outputSample']['name'], PATHINFO_FILENAME);
|
||||||
|
$mappingName = $inputName . '-to-' . $outputName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mappingFile = sanitize_filename($mappingName);
|
||||||
|
$mappingPath = $mappingDir . '/' . $mappingFile;
|
||||||
|
|
||||||
|
file_put_contents($mappingPath, $result['code']);
|
||||||
|
|
||||||
|
echo json_encode(['success' => true, 'mapping_file' => 'mappings/' . $mappingFile]);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,8 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
|
|
||||||
const fullInputFile = document.getElementById('fullInputFile');
|
const fullInputFile = document.getElementById('fullInputFile');
|
||||||
const convertBtn = document.getElementById('convertBtn');
|
const convertBtn = document.getElementById('convertBtn');
|
||||||
|
const existingMappings = document.getElementById('existingMappings');
|
||||||
|
|
||||||
const convertSection = document.getElementById('convertSection');
|
|
||||||
const convertResult = document.getElementById('convertResult');
|
const convertResult = document.getElementById('convertResult');
|
||||||
const mappingFileInput = document.getElementById('mappingFile');
|
const mappingFileInput = document.getElementById('mappingFile');
|
||||||
|
|
||||||
@ -17,13 +17,41 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
buildMappingBtn.disabled = !(inputSample.files.length > 0 && outputSample.files.length > 0);
|
buildMappingBtn.disabled = !(inputSample.files.length > 0 && outputSample.files.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkConvertFile() {
|
function checkConvertState() {
|
||||||
convertBtn.disabled = !(fullInputFile.files.length > 0);
|
const mappingSelected = mappingFileInput.value !== '';
|
||||||
|
const fileSelected = fullInputFile.files.length > 0;
|
||||||
|
convertBtn.disabled = !(mappingSelected && fileSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMappings() {
|
||||||
|
fetch('api.php?action=list_mappings')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.mappings.length > 0) {
|
||||||
|
existingMappings.innerHTML = '<option value="" selected>Select a mapping</option>';
|
||||||
|
data.mappings.forEach(mapping => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = `mappings/${mapping}`;
|
||||||
|
option.textContent = mapping;
|
||||||
|
existingMappings.appendChild(option);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
existingMappings.innerHTML = '<option value="" selected>No mappings found</option>';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading mappings:', error);
|
||||||
|
existingMappings.innerHTML = '<option value="" selected>Error loading mappings</option>';
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inputSample.addEventListener('change', checkMappingFiles);
|
inputSample.addEventListener('change', checkMappingFiles);
|
||||||
outputSample.addEventListener('change', checkMappingFiles);
|
outputSample.addEventListener('change', checkMappingFiles);
|
||||||
fullInputFile.addEventListener('change', checkConvertFile);
|
fullInputFile.addEventListener('change', checkConvertState);
|
||||||
|
existingMappings.addEventListener('change', function() {
|
||||||
|
mappingFileInput.value = this.value;
|
||||||
|
checkConvertState();
|
||||||
|
});
|
||||||
|
|
||||||
mappingForm.addEventListener('submit', function (e) {
|
mappingForm.addEventListener('submit', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -42,7 +70,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert('Mapping built successfully!');
|
alert('Mapping built successfully!');
|
||||||
mappingFileInput.value = data.mapping_file;
|
mappingFileInput.value = data.mapping_file;
|
||||||
convertSection.classList.remove('d-none');
|
loadMappings(); // Refresh the list
|
||||||
|
// Select the new mapping in the dropdown
|
||||||
|
setTimeout(() => {
|
||||||
|
existingMappings.value = data.mapping_file;
|
||||||
|
checkConvertState();
|
||||||
|
}, 500); // Give it a moment to reload
|
||||||
} else {
|
} else {
|
||||||
let errorMsg = 'Error building mapping: ' + data.error;
|
let errorMsg = 'Error building mapping: ' + data.error;
|
||||||
if(data.details) {
|
if(data.details) {
|
||||||
@ -89,6 +122,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
convertBtn.disabled = false;
|
convertBtn.disabled = false;
|
||||||
convertBtn.innerHTML = '<i class="fas fa-sync-alt me-2"></i>Convert';
|
convertBtn.innerHTML = '<i class="fas fa-sync-alt me-2"></i>Convert';
|
||||||
|
checkConvertState();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initial load
|
||||||
|
loadMappings();
|
||||||
});
|
});
|
||||||
54
index.php
54
index.php
@ -29,20 +29,34 @@
|
|||||||
|
|
||||||
<main class="container my-5">
|
<main class="container my-5">
|
||||||
<div class="row g-5">
|
<div class="row g-5">
|
||||||
<div class="col-lg-6">
|
<div class="col-12">
|
||||||
<div class="card h-100">
|
<div class="card">
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body">
|
||||||
<h2 class="card-title h4 mb-4"><span class="badge bg-primary me-2">1</span>Build Mapping</h2>
|
<h2 class="card-title h4 mb-4"><span class="badge bg-primary me-2">1</span>Build New Mapping</h2>
|
||||||
<p class="text-muted">Provide input and output sample files (CSV or XLSX) to generate a mapping configuration using AI.</p>
|
<p class="text-muted">Provide input and output sample files (CSV or XLSX) to generate a mapping configuration using AI.</p>
|
||||||
<form id="mappingForm" class="mt-auto">
|
<form id="mappingForm">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="inputSample" class="form-label">Input Sample</label>
|
<label for="inputSample" class="form-label">Input Sample</label>
|
||||||
<input class="form-control" type="file" id="inputSample" name="inputSample" accept=".csv, .xlsx" required>
|
<input class="form-control" type="file" id="inputSample" name="inputSample" accept=".csv, .xlsx" required>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="outputSample" class="form-label">Output Sample</label>
|
<label for="outputSample" class="form-label">Output Sample</label>
|
||||||
<input class="form-control" type="file" id="outputSample" name="outputSample" accept=".csv, .xlsx" required>
|
<input class="form-control" type="file" id="outputSample" name="outputSample" accept=".csv, .xlsx" required>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="mappingName" class="form-label">Mapping Name (Optional)</label>
|
||||||
|
<input type="text" class="form-control" id="mappingName" name="mappingName" placeholder="e.g., my-custom-mapping">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="customInstructions" class="form-label">Custom Instructions (Optional)</label>
|
||||||
|
<textarea class="form-control" id="customInstructions" name="customInstructions" rows="3" placeholder="e.g., map this field to that, use ; instead of ,"></textarea>
|
||||||
|
</div>
|
||||||
<button type="submit" id="buildMappingBtn" class="btn btn-primary w-100" disabled>
|
<button type="submit" id="buildMappingBtn" class="btn btn-primary w-100" disabled>
|
||||||
<i class="fas fa-cogs me-2"></i>Build Mapping
|
<i class="fas fa-cogs me-2"></i>Build Mapping
|
||||||
</button>
|
</button>
|
||||||
@ -50,18 +64,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="convertSection" class="col-lg-6 d-none">
|
</div>
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-body d-flex flex-column">
|
<hr class="my-5">
|
||||||
<h2 class="card-title h4 mb-4"><span class="badge bg-secondary me-2">2</span>Convert Data</h2>
|
|
||||||
<p class="text-muted">Use the generated mapping to convert your full input file.</p>
|
<div id="convertSection" class="row g-5">
|
||||||
<form id="convertForm" class="mt-auto">
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title h4 mb-4"><span class="badge bg-secondary me-2">2</span>Convert Data with Mapping</h2>
|
||||||
|
<form id="convertForm">
|
||||||
<input type="hidden" id="mappingFile" name="mappingFile">
|
<input type="hidden" id="mappingFile" name="mappingFile">
|
||||||
|
<div class="row align-items-end">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="existingMappings" class="form-label">Use Existing Mapping</label>
|
||||||
|
<select class="form-select" id="existingMappings" name="existingMappings">
|
||||||
|
<option value="" selected>Select a mapping</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="fullInputFile" class="form-label">Full Input File</label>
|
<label for="fullInputFile" class="form-label">Full Input File</label>
|
||||||
<input class="form-control" type="file" id="fullInputFile" name="fullInputFile" accept=".csv, .xlsx" required>
|
<input class="form-control" type="file" id="fullInputFile" name="fullInputFile" accept=".csv, .xlsx" required>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" id="convertBtn" class="btn btn-secondary w-100" disabled>
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" id="convertBtn" class="btn btn-secondary w-100 mt-3" disabled>
|
||||||
<i class="fas fa-sync-alt me-2"></i>Convert
|
<i class="fas fa-sync-alt me-2"></i>Convert
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
108
mappings/mapping_1760650861.php
Normal file
108
mappings/mapping_1760650861.php
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
function convert_csv($inputFile, $outputFile) {
|
||||||
|
$strip_bom = function ($s) {
|
||||||
|
if ($s === null) return '';
|
||||||
|
if (strncmp($s, "\xEF\xBB\xBF", 3) === 0) {
|
||||||
|
return substr($s, 3);
|
||||||
|
}
|
||||||
|
return $s;
|
||||||
|
};
|
||||||
|
|
||||||
|
$in = @fopen($inputFile, 'r');
|
||||||
|
if ($in === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
$header = fgetcsv($in, 0, ',', '"', '\\');
|
||||||
|
if ($header === false) {
|
||||||
|
fclose($in);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize header names and build index map
|
||||||
|
$index = [];
|
||||||
|
foreach ($header as $i => $name) {
|
||||||
|
$name = trim($strip_bom($name));
|
||||||
|
$index[$name] = $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
$get = function ($row, $name) use ($index) {
|
||||||
|
if (!isset($index[$name])) return '';
|
||||||
|
$i = $index[$name];
|
||||||
|
return isset($row[$i]) ? trim($row[$i]) : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
$out = @fopen($outputFile, 'w');
|
||||||
|
if ($out === false) {
|
||||||
|
fclose($in);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output header using semicolon separator
|
||||||
|
$outHeader = [
|
||||||
|
'Data księgowania',
|
||||||
|
'Nadawca / Odbiorca',
|
||||||
|
'Adres nadawcy / odbiorcy',
|
||||||
|
'Rachunek źródłowy',
|
||||||
|
'Rachunek docelowy',
|
||||||
|
'Tytuł',
|
||||||
|
'Kwota'
|
||||||
|
];
|
||||||
|
fputcsv($out, $outHeader, ';');
|
||||||
|
|
||||||
|
while (($row = fgetcsv($in, 0, ',', '"', '\\')) !== false) {
|
||||||
|
// Skip completely empty rows
|
||||||
|
$nonEmpty = false;
|
||||||
|
foreach ($row as $v) {
|
||||||
|
if ($v !== null && $v !== '') { $nonEmpty = true; break; }
|
||||||
|
}
|
||||||
|
if (!$nonEmpty) continue;
|
||||||
|
|
||||||
|
$date = $get($row, 'Date completed (UTC)');
|
||||||
|
if ($date === '') {
|
||||||
|
$date = $get($row, 'Date started (UTC)');
|
||||||
|
}
|
||||||
|
|
||||||
|
$description = $get($row, 'Description');
|
||||||
|
$payer = $get($row, 'Payer');
|
||||||
|
$counterparty = $description !== '' ? $description : $payer;
|
||||||
|
|
||||||
|
$sourceAccount = $get($row, 'Account');
|
||||||
|
|
||||||
|
$benefIban = $get($row, 'Beneficiary IBAN');
|
||||||
|
$benefAccNo = $get($row, 'Beneficiary account number');
|
||||||
|
$destAccount = $benefIban !== '' ? $benefIban : $benefAccNo;
|
||||||
|
|
||||||
|
$reference = $get($row, 'Reference');
|
||||||
|
$type = $get($row, 'Type');
|
||||||
|
$title = $reference !== '' ? $reference : str_replace('_', ' ', $type);
|
||||||
|
if ($title === '' && $counterparty !== '') {
|
||||||
|
$title = $counterparty;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer Amount, then Total amount, then Orig amount
|
||||||
|
$amount = $get($row, 'Amount');
|
||||||
|
if ($amount === '') $amount = $get($row, 'Total amount');
|
||||||
|
if ($amount === '') $amount = $get($row, 'Orig amount');
|
||||||
|
|
||||||
|
// Normalize decimal separator just in case
|
||||||
|
$amount = str_replace(',', '.', $amount);
|
||||||
|
|
||||||
|
$outputRow = [
|
||||||
|
$date,
|
||||||
|
$counterparty,
|
||||||
|
'',
|
||||||
|
$sourceAccount,
|
||||||
|
$destAccount,
|
||||||
|
$title,
|
||||||
|
$amount
|
||||||
|
];
|
||||||
|
|
||||||
|
fputcsv($out, $outputRow, ';');
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($in);
|
||||||
|
fclose($out);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
47
outputs/converted_1760650923.csv
Normal file
47
outputs/converted_1760650923.csv
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"Data księgowania";"Nadawca / Odbiorca";"Adres nadawcy / odbiorcy";"Rachunek źródłowy";"Rachunek docelowy";Tytuł;Kwota
|
||||||
|
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"CARD PAYMENT";-18.48
|
||||||
|
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"CARD PAYMENT";-0.24
|
||||||
|
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"CARD PAYMENT";-34.35
|
||||||
|
2025-08-23;"Web Innovative Softwar";;"EUR Card EUR";;"CARD PAYMENT";-0.24
|
||||||
|
2025-08-23;"Discord* 12xserverboos";;"EUR Card EUR";;"CARD PAYMENT";-44.54
|
||||||
|
2025-08-22;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"Przelew na wlasne konto";-50.00
|
||||||
|
2025-08-22;Cloudflare;;"EUR Card EUR";;"CARD PAYMENT";-8.03
|
||||||
|
2025-08-20;Cloudflare;;"EUR Card EUR";;"CARD PAYMENT";-25.71
|
||||||
|
2025-08-20;"Openai *chatgpt Subscr";;"EUR Card EUR";;"CARD PAYMENT";-186.18
|
||||||
|
2025-08-20;Calendly;;"EUR Card EUR";;"CARD PAYMENT";-42.24
|
||||||
|
2025-08-20;"Facebk *whhzuwgcy2";;"EUR Card EUR";;"CARD PAYMENT";-25.74
|
||||||
|
2025-08-19;"Main · EUR → Main · USD";;"EUR Main";;EXCHANGE;-4115.97
|
||||||
|
2025-08-19;"Card EUR · EUR → Card USD · USD";;"EUR Card EUR";;EXCHANGE;-250.15
|
||||||
|
2025-08-19;"To Card EUR";;"EUR Main";;TRANSFER;-1300.00
|
||||||
|
2025-08-19;"From Main";;"EUR Card EUR";;TRANSFER;1300.00
|
||||||
|
2025-08-14;"Main · EUR → Main · PLN";;"EUR Main";;"Transfer to Main to recover negative balance";-41.07
|
||||||
|
2025-08-14;"Money added from MOGANADEN MOORGHEN";;"EUR Main";;"July 2025 software development";6468.75
|
||||||
|
2025-08-12;Ing*meeting15.com;;"EUR Card EUR";;"CARD PAYMENT";-59.48
|
||||||
|
2025-08-07;"Google*cloud Qzxlb5";;"EUR Card EUR";;"CARD PAYMENT";-417.23
|
||||||
|
2025-08-07;"Google Cloud Sv93dn";;"EUR Card EUR";;"CARD PAYMENT";-500.00
|
||||||
|
2025-08-06;"Card USD · USD → Card EUR · EUR";;"EUR Card EUR";;EXCHANGE;1000.00
|
||||||
|
2025-08-04;"Main · EUR → Card USD · USD";;"EUR Main";;EXCHANGE;-6007.69
|
||||||
|
2025-08-04;"Main · EUR → Card USD · USD";;"EUR Main";;EXCHANGE;-3460.84
|
||||||
|
2025-08-04;"Money added from FLATLOGIC POLAND SPOLKA Z OGRANICZONA ODPOWIEDZIALNOSCIA";;"EUR Main";;"Przelew na wlasny rachunek";8800.00
|
||||||
|
2025-08-04;"Openai *chatgpt Subscr";;"EUR Card EUR";;"CARD PAYMENT";-17.41
|
||||||
|
2025-07-29;"Github, Inc.";;"EUR Card EUR";;"CARD PAYMENT";-81.96
|
||||||
|
2025-07-29;Dropbox*sgvfd1d5tz31;;"EUR Card EUR";;"CARD PAYMENT";-11.99
|
||||||
|
2025-07-28;"To Flatlogic EU";;"EUR Main";;"Invoice FL EU #759";-400.00
|
||||||
|
2025-07-28;Figma;;"EUR Card EUR";;"CARD PAYMENT";-102.39
|
||||||
|
2025-07-27;"Trello.com* Atlassian";;"EUR Card EUR";;"CARD PAYMENT";-5.12
|
||||||
|
2025-07-26;Paypro-charge.com;;"EUR Card EUR";;"CARD PAYMENT";-11.62
|
||||||
|
2025-07-23;"Discord* 12xserverboos";;"EUR Card EUR";;"CARD PAYMENT";-44.27
|
||||||
|
2025-07-21;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"Przelew na wlasne konto";-450.00
|
||||||
|
2025-07-20;Cloudflare;;"EUR Card EUR";;"CARD PAYMENT";-25.83
|
||||||
|
2025-07-20;Calendly;;"EUR Card EUR";;"CARD PAYMENT";-42.36
|
||||||
|
2025-07-18;"Main · EUR → Main · USD";;"EUR Main";;EXCHANGE;-972.24
|
||||||
|
2025-07-18;"To Flatlogic Poland";;"EUR Main";PL51109025900000000161061759;"Przelew na wlasne konto";-250.00
|
||||||
|
2025-07-17;"Openai *chatgpt Subscr";;"EUR Card EUR";;"CARD PAYMENT";-186.18
|
||||||
|
2025-07-16;"To Card EUR";;"EUR Main";;TRANSFER;-500.00
|
||||||
|
2025-07-16;"From Main";;"EUR Card EUR";;TRANSFER;500.00
|
||||||
|
2025-07-16;"Veed.io Basic";;"EUR Card EUR";;"CARD PAYMENT";-44.00
|
||||||
|
2025-07-14;"Main · EUR → Main · PLN";;"EUR Main";;"Transfer to Main to recover negative balance";-8.49
|
||||||
|
2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;EXCHANGE;-17.12
|
||||||
|
2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;EXCHANGE;-2121.00
|
||||||
|
2025-07-14;"Main · EUR → Main · USD";;"EUR Main";;EXCHANGE;-1711.65
|
||||||
|
2025-07-14;"Money added from MOGANADEN MOORGHEN";;"EUR Main";;TOPUP;1631.25
|
||||||
|
Loading…
x
Reference in New Issue
Block a user