diff --git a/REST/functions.php b/REST/functions.php
index 325a428d..3aba624e 100644
--- a/REST/functions.php
+++ b/REST/functions.php
@@ -237,4 +237,270 @@ function brecho($msg) {
echo '
';
var_dump($msg);
echo '
';
+}
+
+function createCSVReports()
+{
+ {
+ $db = $GLOBALS['db'];
+ $exportDir = __DIR__ . "/export";
+
+ $jobs = [
+ [
+ 'sql' => "
+ SELECT
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ CASE p.group_ks
+ WHEN 1 THEN 'Towar handlowy'
+ WHEN 2 THEN 'Wyrób gotowy'
+ WHEN 3 THEN 'Usługi'
+ WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
+ WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
+ ELSE 'Nieznane'
+ END AS group_ks,
+ GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
+ ii.quantity,
+ ii.price_netto
+FROM ecminvoiceouts AS i
+INNER JOIN ecminvoiceoutitems AS ii ON i.id = ii.ecminvoiceout_id
+INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id
+LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci
+ AND cb.bean_name = 'EcmProducts'
+ AND cb.deleted = 0
+LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id
+WHERE i.type = 'normal' AND YEAR(i.register_date) = 2024
+GROUP BY
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ p.group_ks,
+ ii.quantity,
+ ii.price_netto
+ORDER BY i.register_date DESC;
+ ",
+ 'filename' => 'invoices_2024.csv',
+ ],
+ [
+ 'sql' => "
+ SELECT
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ CASE p.group_ks
+ WHEN 1 THEN 'Towar handlowy'
+ WHEN 2 THEN 'Wyrób gotowy'
+ WHEN 3 THEN 'Usługi'
+ WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
+ WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
+ ELSE 'Nieznane'
+ END AS group_ks,
+ GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
+ ii.quantity,
+ ii.price_netto
+FROM ecminvoiceouts AS i
+INNER JOIN ecminvoiceoutitems AS ii ON i.id = ii.ecminvoiceout_id
+INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id
+LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci
+ AND cb.bean_name = 'EcmProducts'
+ AND cb.deleted = 0
+LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id
+WHERE i.type = 'normal' AND YEAR(i.register_date) = 2025
+GROUP BY
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ p.group_ks,
+ ii.quantity,
+ ii.price_netto
+ORDER BY i.register_date DESC;
+ ",
+ 'filename' => 'invoices_2025.csv',
+ ],
+ [
+ 'sql' => "
+SELECT
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ CASE p.group_ks
+ WHEN 1 THEN 'Towar handlowy'
+ WHEN 2 THEN 'Wyrób gotowy'
+ WHEN 3 THEN 'Usługi'
+ WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
+ WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
+ ELSE 'Nieznane'
+ END AS group_ks,
+ GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
+ ii.quantity,
+ ii.price_netto
+FROM ecommerce_invoices AS i
+INNER JOIN ecommerce_invoices_products AS ii ON i.id = ii.invoice_id
+INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id
+LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci
+ AND cb.bean_name = 'EcmProducts'
+ AND cb.deleted = 0
+LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id
+WHERE i.type = 'normal' AND YEAR(i.register_date) = 2024
+GROUP BY
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ p.group_ks,
+ ii.quantity,
+ ii.price_netto
+ORDER BY i.register_date DESC;
+ ",
+ 'filename' => 'ecommerce_invoices_2024.csv',
+ ],
+ [
+ 'sql' => "
+SELECT
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ CASE p.group_ks
+ WHEN 1 THEN 'Towar handlowy'
+ WHEN 2 THEN 'Wyrób gotowy'
+ WHEN 3 THEN 'Usługi'
+ WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
+ WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
+ ELSE 'Nieznane'
+ END AS group_ks,
+ GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
+ ii.quantity,
+ ii.price_netto
+FROM ecommerce_invoices AS i
+INNER JOIN ecommerce_invoices_products AS ii ON i.id = ii.invoice_id
+INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id
+LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci
+ AND cb.bean_name = 'EcmProducts'
+ AND cb.deleted = 0
+LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id
+WHERE i.type = 'normal' AND YEAR(i.register_date) = 2025
+GROUP BY
+ i.document_no,
+ i.register_date,
+ i.parent_name,
+ p.code,
+ p.name,
+ p.group_ks,
+ ii.quantity,
+ ii.price_netto
+ORDER BY i.register_date DESC;
+ ",
+ 'filename' => 'ecommerce_invoices_2025.csv',
+ ],
+ [
+ 'sql' => "
+ SELECT
+ ss.product_code,
+ ss.product_name,
+ COALESCE(NULLIF(ss.quantity, ''), 0) AS quantity,
+ s.name
+ FROM ecmstockstates AS ss
+ JOIN ecmstocks AS s ON ss.stock_id = s.id
+ ORDER BY quantity + 0 DESC;",
+ 'filename' => 'stocks.csv',
+ ],
+ ];
+
+ $report = [];
+ foreach ($jobs as $job) {
+ $sql = $job['sql'];
+ $filename = $job['filename'];
+ $headers = isset($job['headers']) ? $job['headers'] : null;
+
+ $res = $db->query($sql);
+ $fullpath = rtrim($exportDir, "/") . "/" . $filename;
+
+ $result = exportToCSVFile($res, $fullpath, $headers, ';', true);
+
+ if ($result['ok']) {
+ $report[] = "OK → {$result['path']} (wiersze: {$result['rows']})".PHP_EOL;
+ } else {
+ $report[] = "ERR → {$result['path']} ({$result['error']})".PHP_EOL;;
+ }
+ }
+
+ echo implode("\n", $report);
+ exit;
+ }
+}
+function exportToCSVFile($res, $fullpath, array $headers = null, $delimiter = ';', $withBom = true)
+{
+ $db = $GLOBALS['db'];
+
+ $dir = dirname($fullpath);
+ if (!is_dir($dir)) {
+ if (!@mkdir($dir, 0775, true)) {
+ return ['ok'=>false, 'path'=>$fullpath, 'rows'=>0, 'error'=>"Nie mogę utworzyć katalogu: $dir"];
+ }
+ }
+ if (!is_writable($dir)) {
+ return ['ok'=>false, 'path'=>$fullpath, 'rows'=>0, 'error'=>"Katalog nie jest zapisywalny: $dir"];
+ }
+
+ $fp = @fopen($fullpath, 'w');
+ if ($fp === false) {
+ return ['ok'=>false, 'path'=>$fullpath, 'rows'=>0, 'error'=>"Nie mogę otworzyć pliku do zapisu: $fullpath"];
+ }
+
+ // BOM dla Excel PL
+ if ($withBom) {
+ fwrite($fp, "\xEF\xBB\xBF");
+ }
+
+ // pobierz pierwszy wiersz, by ewentualnie zbudować nagłówki
+ $first = $db->fetchByAssoc($res);
+
+ // brak danych → pusty plik z ewentualnym nagłówkiem (jeśli podany ręcznie)
+ if (!$first) {
+ if ($headers !== null) {
+ fputcsv($fp, $headers, $delimiter);
+ }
+ fclose($fp);
+ return ['ok'=>true, 'path'=>$fullpath, 'rows'=>0, 'error'=>null];
+ }
+
+ // dynamiczne nagłówki, jeśli nie podano
+ if ($headers === null) {
+ $headers = array_keys($first);
+ }
+
+ // wypisz nagłówki
+ fputcsv($fp, $headers, $delimiter);
+
+ // zapisz pierwszy wiersz w kolejności nagłówków
+ $line = [];
+ foreach ($headers as $h) { $line[] = isset($first[$h]) ? $first[$h] : ''; }
+ fputcsv($fp, $line, $delimiter);
+ $count = 1;
+
+ // pozostałe wiersze
+ while ($row = $db->fetchByAssoc($res)) {
+ $line = [];
+ foreach ($headers as $h) { $line[] = isset($row[$h]) ? $row[$h] : ''; }
+ fputcsv($fp, $line, $delimiter);
+ $count++;
+ }
+
+ fclose($fp);
+ $chmod_ok = @chmod($fullpath, 0664);
+ return ['ok'=>true, 'path'=>$fullpath, 'rows'=>$count, 'chmod'=>$chmod_ok, 'error'=>null];
}
\ No newline at end of file
diff --git a/REST/index.php b/REST/index.php
index e734feb3..06c5bdb2 100644
--- a/REST/index.php
+++ b/REST/index.php
@@ -73,4 +73,7 @@ switch ($_GET["action"]) {
$db->query("UPDATE ecmproducts SET exportedAt='$exportedAt' WHERE id='$id'");
break;
}
+ case 'createCSVReports':
+ createCSVReports();
+ break;
}
\ No newline at end of file