This commit is contained in:
Flatlogic Bot 2025-10-17 12:39:38 +00:00
parent a0550df293
commit fa328348a3
5 changed files with 294 additions and 32 deletions

52
api.php
View File

@ -42,7 +42,7 @@ function get_csv_sample($filePath) {
return implode('\n', $sample);
}
function call_openai_api($inputSample, $outputSample) {
function call_openai_api($inputSample, $outputSample, $customInstructions) {
$apiKey = getenv('OPENAI_API_KEY');
if (!$apiKey) {
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 .= "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 .= "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`.";
@ -103,6 +106,32 @@ function call_openai_api($inputSample, $outputSample) {
load_env(__DIR__ . '/.env');
$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 (!isset($_FILES['inputSample']) || !isset($_FILES['outputSample'])) {
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);
$outputSampleContent = get_csv_sample($outputTmpPath);
$customInstructions = $_POST['customInstructions'] ?? '';
if ($inputSampleContent === null || $outputSampleContent === null) {
echo json_encode(['success' => false, 'error' => 'Failed to read sample files.']);
exit;
}
$result = call_openai_api($inputSampleContent, $outputSampleContent);
$result = call_openai_api($inputSampleContent, $outputSampleContent, $customInstructions);
if (isset($result['error'])) {
echo json_encode(['success' => false, 'error' => $result['error'], 'details' => $result['details'] ?? null]);
@ -131,10 +161,20 @@ if ($action === 'build_mapping') {
if (!is_dir($mappingDir)) {
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;
}

View File

@ -8,8 +8,8 @@ document.addEventListener('DOMContentLoaded', function () {
const fullInputFile = document.getElementById('fullInputFile');
const convertBtn = document.getElementById('convertBtn');
const existingMappings = document.getElementById('existingMappings');
const convertSection = document.getElementById('convertSection');
const convertResult = document.getElementById('convertResult');
const mappingFileInput = document.getElementById('mappingFile');
@ -17,13 +17,41 @@ document.addEventListener('DOMContentLoaded', function () {
buildMappingBtn.disabled = !(inputSample.files.length > 0 && outputSample.files.length > 0);
}
function checkConvertFile() {
convertBtn.disabled = !(fullInputFile.files.length > 0);
function checkConvertState() {
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);
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) {
e.preventDefault();
@ -42,7 +70,12 @@ document.addEventListener('DOMContentLoaded', function () {
if (data.success) {
alert('Mapping built successfully!');
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 {
let errorMsg = 'Error building mapping: ' + data.error;
if(data.details) {
@ -89,6 +122,10 @@ document.addEventListener('DOMContentLoaded', function () {
.finally(() => {
convertBtn.disabled = false;
convertBtn.innerHTML = '<i class="fas fa-sync-alt me-2"></i>Convert';
checkConvertState();
});
});
});
// Initial load
loadMappings();
});

View File

@ -29,19 +29,33 @@
<main class="container my-5">
<div class="row g-5">
<div class="col-lg-6">
<div class="card h-100">
<div class="card-body d-flex flex-column">
<h2 class="card-title h4 mb-4"><span class="badge bg-primary me-2">1</span>Build Mapping</h2>
<div class="col-12">
<div class="card">
<div class="card-body">
<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>
<form id="mappingForm" class="mt-auto">
<div class="mb-3">
<label for="inputSample" class="form-label">Input Sample</label>
<input class="form-control" type="file" id="inputSample" name="inputSample" accept=".csv, .xlsx" required>
<form id="mappingForm">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="inputSample" class="form-label">Input Sample</label>
<input class="form-control" type="file" id="inputSample" name="inputSample" accept=".csv, .xlsx" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="outputSample" class="form-label">Output Sample</label>
<input class="form-control" type="file" id="outputSample" name="outputSample" accept=".csv, .xlsx" required>
</div>
</div>
</div>
<div class="mb-3">
<label for="outputSample" class="form-label">Output Sample</label>
<input class="form-control" type="file" id="outputSample" name="outputSample" accept=".csv, .xlsx" required>
<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>
<i class="fas fa-cogs me-2"></i>Build Mapping
@ -50,18 +64,34 @@
</div>
</div>
</div>
<div id="convertSection" class="col-lg-6 d-none">
<div class="card h-100">
<div class="card-body d-flex flex-column">
<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>
<form id="convertForm" class="mt-auto">
</div>
<hr class="my-5">
<div id="convertSection" class="row g-5">
<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">
<div class="mb-3">
<label for="fullInputFile" class="form-label">Full Input File</label>
<input class="form-control" type="file" id="fullInputFile" name="fullInputFile" accept=".csv, .xlsx" required>
<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">
<label for="fullInputFile" class="form-label">Full Input File</label>
<input class="form-control" type="file" id="fullInputFile" name="fullInputFile" accept=".csv, .xlsx" required>
</div>
</div>
</div>
<button type="submit" id="convertBtn" class="btn btn-secondary w-100" disabled>
<button type="submit" id="convertBtn" class="btn btn-secondary w-100 mt-3" disabled>
<i class="fas fa-sync-alt me-2"></i>Convert
</button>
</form>

View 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;
}

View 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
1 Data księgowania Nadawca / Odbiorca Adres nadawcy / odbiorcy Rachunek źródłowy Rachunek docelowy Tytuł Kwota
2 2025-08-23 Web Innovative Softwar EUR Card EUR CARD PAYMENT -18.48
3 2025-08-23 Web Innovative Softwar EUR Card EUR CARD PAYMENT -0.24
4 2025-08-23 Web Innovative Softwar EUR Card EUR CARD PAYMENT -34.35
5 2025-08-23 Web Innovative Softwar EUR Card EUR CARD PAYMENT -0.24
6 2025-08-23 Discord* 12xserverboos EUR Card EUR CARD PAYMENT -44.54
7 2025-08-22 To Flatlogic Poland EUR Main PL51109025900000000161061759 Przelew na wlasne konto -50.00
8 2025-08-22 Cloudflare EUR Card EUR CARD PAYMENT -8.03
9 2025-08-20 Cloudflare EUR Card EUR CARD PAYMENT -25.71
10 2025-08-20 Openai *chatgpt Subscr EUR Card EUR CARD PAYMENT -186.18
11 2025-08-20 Calendly EUR Card EUR CARD PAYMENT -42.24
12 2025-08-20 Facebk *whhzuwgcy2 EUR Card EUR CARD PAYMENT -25.74
13 2025-08-19 Main · EUR → Main · USD EUR Main EXCHANGE -4115.97
14 2025-08-19 Card EUR · EUR → Card USD · USD EUR Card EUR EXCHANGE -250.15
15 2025-08-19 To Card EUR EUR Main TRANSFER -1300.00
16 2025-08-19 From Main EUR Card EUR TRANSFER 1300.00
17 2025-08-14 Main · EUR → Main · PLN EUR Main Transfer to Main to recover negative balance -41.07
18 2025-08-14 Money added from MOGANADEN MOORGHEN EUR Main July 2025 software development 6468.75
19 2025-08-12 Ing*meeting15.com EUR Card EUR CARD PAYMENT -59.48
20 2025-08-07 Google*cloud Qzxlb5 EUR Card EUR CARD PAYMENT -417.23
21 2025-08-07 Google Cloud Sv93dn EUR Card EUR CARD PAYMENT -500.00
22 2025-08-06 Card USD · USD → Card EUR · EUR EUR Card EUR EXCHANGE 1000.00
23 2025-08-04 Main · EUR → Card USD · USD EUR Main EXCHANGE -6007.69
24 2025-08-04 Main · EUR → Card USD · USD EUR Main EXCHANGE -3460.84
25 2025-08-04 Money added from FLATLOGIC POLAND SPOLKA Z OGRANICZONA ODPOWIEDZIALNOSCIA EUR Main Przelew na wlasny rachunek 8800.00
26 2025-08-04 Openai *chatgpt Subscr EUR Card EUR CARD PAYMENT -17.41
27 2025-07-29 Github, Inc. EUR Card EUR CARD PAYMENT -81.96
28 2025-07-29 Dropbox*sgvfd1d5tz31 EUR Card EUR CARD PAYMENT -11.99
29 2025-07-28 To Flatlogic EU EUR Main Invoice FL EU #759 -400.00
30 2025-07-28 Figma EUR Card EUR CARD PAYMENT -102.39
31 2025-07-27 Trello.com* Atlassian EUR Card EUR CARD PAYMENT -5.12
32 2025-07-26 Paypro-charge.com EUR Card EUR CARD PAYMENT -11.62
33 2025-07-23 Discord* 12xserverboos EUR Card EUR CARD PAYMENT -44.27
34 2025-07-21 To Flatlogic Poland EUR Main PL51109025900000000161061759 Przelew na wlasne konto -450.00
35 2025-07-20 Cloudflare EUR Card EUR CARD PAYMENT -25.83
36 2025-07-20 Calendly EUR Card EUR CARD PAYMENT -42.36
37 2025-07-18 Main · EUR → Main · USD EUR Main EXCHANGE -972.24
38 2025-07-18 To Flatlogic Poland EUR Main PL51109025900000000161061759 Przelew na wlasne konto -250.00
39 2025-07-17 Openai *chatgpt Subscr EUR Card EUR CARD PAYMENT -186.18
40 2025-07-16 To Card EUR EUR Main TRANSFER -500.00
41 2025-07-16 From Main EUR Card EUR TRANSFER 500.00
42 2025-07-16 Veed.io Basic EUR Card EUR CARD PAYMENT -44.00
43 2025-07-14 Main · EUR → Main · PLN EUR Main Transfer to Main to recover negative balance -8.49
44 2025-07-14 Main · EUR → Main · USD EUR Main EXCHANGE -17.12
45 2025-07-14 Main · EUR → Main · USD EUR Main EXCHANGE -2121.00
46 2025-07-14 Main · EUR → Main · USD EUR Main EXCHANGE -1711.65
47 2025-07-14 Money added from MOGANADEN MOORGHEN EUR Main TOPUP 1631.25