Compare commits

27 Commits

Author SHA1 Message Date
578817cf83 update bimai export 2025-11-28 10:33:33 +00:00
5862a89885 Merge branch 'main' of ssh://code.bim-it.pl:2222/mz/crm.twinpol.com 2025-11-27 20:02:17 +00:00
2d362277c6 debug 2025-11-27 20:02:14 +00:00
52efb9f850 csv fix 2025-11-27 21:01:32 +01:00
97274ccde1 apilo - refresh token fix 2025-11-25 10:27:03 +00:00
7140100a08 Apilo - refresh token fix 2025-11-25 10:22:11 +00:00
34f16df681 Merge branches 'main' and 'main' of ssh://code.bim-it.pl:2222/mz/crm.twinpol.com 2025-11-22 09:02:56 +01:00
ff44b45465 remove logs 2025-11-22 08:57:48 +01:00
0a0d9fa0a6 ECommerce Invoices Export 2025-11-22 08:54:49 +01:00
5920fe9a03 Amazon 10.2025 2025-11-15 08:47:38 +00:00
Michał Zieliński
36f750e42b Merge remote-tracking branch 'origin/main' 2025-11-07 10:10:34 +01:00
Michał Zieliński
0fa2cf7423 Add currency to CSV reports 2025-11-07 10:10:25 +01:00
99cf02c003 TEMU 2025-10-31 11:47:29 +00:00
b60ac8e2c0 WIP: TEMU 2025-10-29 08:12:07 +00:00
Michał Zieliński
351e9af67d WIP: TEMU API 2025-10-21 19:18:31 +02:00
569a39b153 scheduler fixes 2025-10-21 15:05:18 +00:00
Michał Zieliński
dc48841d9b Merge remote-tracking branch 'origin/main' 2025-10-21 17:04:25 +02:00
Michał Zieliński
28d637ecc1 BIMAI: REST get ecommerceinvoices 2025-10-21 17:04:15 +02:00
cf65b86547 WIP: schedulers.. 2025-10-08 15:53:45 +00:00
f440223a26 WIP: schedulers.. 2025-10-07 16:40:56 +00:00
7200d0e6af WIP: ecommerce 2025-10-06 07:13:09 +00:00
e6d9326872 WIP: ecommerce 2025-10-04 10:40:28 +00:00
601b31439c import apilo 2025-09-29 17:18:32 +00:00
f709c11d00 import apilo 2025-09-25 20:59:26 +00:00
Michał Zieliński
aa4a2a6ebb import apilo invoices 2025-09-25 22:33:49 +02:00
Michał Zieliński
508cde0a68 import apilo invoices 2025-09-25 21:50:59 +02:00
7943842f90 amazon august 2025-09-24 06:38:38 +00:00
59 changed files with 3201 additions and 2113 deletions

64
REST/bimai.php Normal file
View File

@@ -0,0 +1,64 @@
<?php
function bimai_exportEcommerceDate($since)
{
if (!isset($since)) {
echo json_encode(array('ok' => false, 'error' => 'Missing since parameter'));
return false;
}
$sinceDate = date('Y-m-d H:i:s', intval($_GET['since']));
$query = "
SELECT
i.id,
i .document_no,
i.type,
i.register_date,
i.sell_date,
i.parent_name,
i.parent_nip,
i.parent_address_street,
i.parent_address_postalcode,
i.parent_address_city,
i.parent_address_country,
i.currency,
i.total_netto,
i.total_brutto,
i.total_vat,
i.origin
FROM ecommerce_invoices i
WHERE
i.origin = 'allegro'
AND YEAR(i.register_date) = 2024
AND i.register_date > '$sinceDate'
ORDER BY i.register_date
LIMIT 0,10000;
";
$db = $GLOBALS['db'];
$query = $db->query($query);
if ($db->last_error != '') {
echo json_encode(array('ok' => false, 'error' => $db->last_error));
return false;
}
$data = array();
while ($row = $db->fetchByAssoc($query)) {
$d = array();
$d['e5Id'] = $row['id'];
$d['documentNo'] = $row['document_no'];
$d['type'] = $row['type'];
$d['registerDate'] = $row['register_date'];
$d['sellDate'] = $row['sell_date'];
$d['clientName'] = $row['parent_name'];
$d['clientId'] = $row['parent_id'] ?: '';
$d['clientNip'] = $row['parent_nip'] ?: '';
$d['clientAddress'] = $row['parent_address_street'] . ", " . $row['parent_address_postalcode'] . " " . $row['parent_address_city'] . " " . $row['parent_address_country'];;
$d['currency'] = $row['currency'];
$d['totalNetto'] = $row['total_netto'];
$d['totalBrutto'] = $row['total_brutto'];
$d['totalVat'] = $row['total_vat'];
$d['source'] = $row['origin'];
$data[] = $d;
}
echo json_encode(array('ok' => true, 'dataSize' => count($data), 'data' => $data));
return true;
}

View File

@@ -323,81 +323,97 @@ function createCSVReports()
$jobs = [ $jobs = [
[ [
'sql' => " 'sql' => "
SELECT SELECT
i.document_no, i.document_no,
i.register_date, i.register_date,
i.parent_name, i.parent_name,
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Usługi' WHEN '3' THEN 'Usługi'
WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały' WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce' WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
ii.quantity, ii.quantity,
ii.price_netto ii.price_netto,
COALESCE(cur.name, 'PLN') AS currency_name
FROM ecminvoiceouts AS i FROM ecminvoiceouts AS i
INNER JOIN ecminvoiceoutitems AS ii ON i.id = ii.ecminvoiceout_id INNER JOIN ecminvoiceoutitems AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.ecminvoiceout_id
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci AND ii.deleted = 0
AND cb.bean_name = 'EcmProducts' INNER JOIN ecmproducts AS p
AND cb.deleted = 0 ON ii.ecmproduct_id = p.id
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id AND p.deleted = 0
WHERE i.type = 'normal' AND YEAR(i.register_date) = 2024 LEFT JOIN currencies AS cur
ON cur.id = i.currency_id
AND cur.deleted = 0
LEFT JOIN ecmproductcategories_bean AS cb
ON cb.bean_id = p.id
AND cb.bean_name = 'EcmProducts'
AND cb.deleted = 0
LEFT JOIN ecmproductcategories AS c
ON c.id = cb.ecmproductcategory_id
AND c.deleted = 0
WHERE i.type = 'normal'
AND i.register_date BETWEEN '2024-01-01' AND '2024-12-31'
AND i.deleted = 0
GROUP BY GROUP BY
i.document_no, i.id,
i.register_date, ii.id
i.parent_name, ORDER BY
p.code, i.register_date DESC;
p.name,
p.group_ks,
ii.quantity,
ii.price_netto
ORDER BY i.register_date DESC;
", ",
'filename' => 'invoices_2024.csv', 'filename' => 'invoices_2024.csv',
], // invoices 2024 ], // invoices 2024
[ [
'sql' => " 'sql' => "
SELECT SELECT
i.document_no, i.document_no,
i.register_date, i.register_date,
i.parent_name, i.parent_name,
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Usługi' WHEN '3' THEN 'Usługi'
WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały' WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce' WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
ii.quantity, ii.quantity,
ii.price_netto ii.price_netto,
COALESCE(cur.name, 'PLN') AS currency_name
FROM ecminvoiceouts AS i FROM ecminvoiceouts AS i
INNER JOIN ecminvoiceoutitems AS ii ON i.id = ii.ecminvoiceout_id INNER JOIN ecminvoiceoutitems AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.ecminvoiceout_id
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci AND ii.deleted = 0
AND cb.bean_name = 'EcmProducts' INNER JOIN ecmproducts AS p
AND cb.deleted = 0 ON ii.ecmproduct_id = p.id
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id AND p.deleted = 0
WHERE i.type = 'normal' AND YEAR(i.register_date) = 2025 LEFT JOIN currencies AS cur
ON cur.id = i.currency_id
AND cur.deleted = 0
LEFT JOIN ecmproductcategories_bean AS cb
ON cb.bean_id = p.id
AND cb.bean_name = 'EcmProducts'
AND cb.deleted = 0
LEFT JOIN ecmproductcategories AS c
ON c.id = cb.ecmproductcategory_id
AND c.deleted = 0
WHERE i.type = 'normal'
AND i.register_date BETWEEN '2025-01-01' AND '2025-12-31'
AND i.deleted = 0
GROUP BY GROUP BY
i.document_no, i.id,
i.register_date, ii.id
i.parent_name, ORDER BY
p.code, i.register_date DESC;
p.name,
p.group_ks,
ii.quantity,
ii.price_netto
ORDER BY i.register_date DESC;
", ",
'filename' => 'invoices_2025.csv', 'filename' => 'invoices_2025.csv',
], // invoices 2025 ], // invoices 2025
@@ -412,39 +428,38 @@ SELECT
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Usługi' WHEN '3' THEN 'Usługi'
WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
ii.quantity_corrected AS quantity_correced, ii.quantity_corrected AS quantity_corrected,
ii.total_netto_corrected AS total_netto_corrected ii.total_netto_corrected AS total_netto_corrected,
COALESCE(cur.name, 'PLN') AS currency_name
FROM ecminvoiceouts AS i FROM ecminvoiceouts AS i
INNER JOIN ecminvoiceoutitems AS ii ON i.id = ii.ecminvoiceout_id INNER JOIN ecminvoiceoutitems AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.ecminvoiceout_id AND ii.deleted = 0
INNER JOIN ecminvoiceouts AS oi ON oi.id = i.ecminvoiceout_id INNER JOIN ecmproducts AS p
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci ON ii.ecmproduct_id = p.id AND p.deleted = 0
AND cb.bean_name = 'EcmProducts' LEFT JOIN ecminvoiceouts AS oi
AND cb.deleted = 0 ON oi.id = i.ecminvoiceout_id AND oi.deleted = 0
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id LEFT JOIN currencies AS cur
WHERE i.type = 'correct' AND YEAR(i.register_date) = 2024 ON cur.id = i.currency_id AND cur.deleted = 0
LEFT JOIN ecmproductcategories_bean AS cb
ON cb.bean_id = p.id
AND cb.bean_name = 'EcmProducts'
AND cb.deleted = 0
LEFT JOIN ecmproductcategories AS c
ON c.id = cb.ecmproductcategory_id AND c.deleted = 0
WHERE i.type = 'correct'
AND i.register_date BETWEEN '2024-01-01' AND '2024-12-31'
AND i.deleted = 0
GROUP BY GROUP BY
i.document_no, i.id,
i.register_date, ii.id,
oi.document_no, oi.id
oi.register_date, ORDER BY i.register_date DESC;
i.parent_name,
p.code,
p.name,
p.group_ks,
ii.quantity,
ii.price_netto,
ii.quantity_corrected,
ii.total_netto_corrected
ORDER BY i.register_date DESC;
", ",
'filename' => 'correct_invoices_2024.csv', 'filename' => 'correct_invoices_2024.csv',
], // correct invoices 2024 ], // correct invoices 2024
@@ -459,38 +474,37 @@ SELECT
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Usługi' WHEN '3' THEN 'Usługi'
WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category,
ii.quantity_corrected AS quantity_correced, ii.quantity_corrected AS quantity_corrected,
ii.total_netto_corrected AS total_netto_corrected ii.total_netto_corrected AS total_netto_corrected,
COALESCE(cur.name, 'PLN') AS currency_name
FROM ecminvoiceouts AS i FROM ecminvoiceouts AS i
INNER JOIN ecminvoiceoutitems AS ii ON i.id = ii.ecminvoiceout_id INNER JOIN ecminvoiceoutitems AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.ecminvoiceout_id AND ii.deleted = 0
INNER JOIN ecminvoiceouts AS oi ON oi.id = i.ecminvoiceout_id INNER JOIN ecmproducts AS p
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci ON ii.ecmproduct_id = p.id AND p.deleted = 0
AND cb.bean_name = 'EcmProducts' LEFT JOIN ecminvoiceouts AS oi
AND cb.deleted = 0 ON oi.id = i.ecminvoiceout_id AND oi.deleted = 0
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id LEFT JOIN currencies AS cur
WHERE i.type = 'correct' AND YEAR(i.register_date) = 2025 ON cur.id = i.currency_id AND cur.deleted = 0
LEFT JOIN ecmproductcategories_bean AS cb
ON cb.bean_id = p.id
AND cb.bean_name = 'EcmProducts'
AND cb.deleted = 0
LEFT JOIN ecmproductcategories AS c
ON c.id = cb.ecmproductcategory_id AND c.deleted = 0
WHERE i.type = 'correct'
AND i.register_date BETWEEN '2025-01-01' AND '2025-12-31'
AND i.deleted = 0
GROUP BY GROUP BY
i.document_no, i.id,
i.register_date, ii.id,
oi.document_no, oi.id
oi.register_date,
i.parent_name,
p.code,
p.name,
p.group_ks,
ii.quantity,
ii.price_netto,
ii.quantity_corrected,
ii.total_netto_corrected
ORDER BY i.register_date DESC; ORDER BY i.register_date DESC;
", ",
'filename' => 'correct_invoices_2025.csv', 'filename' => 'correct_invoices_2025.csv',
@@ -504,33 +518,37 @@ SELECT
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Usługi' WHEN '3' THEN 'Usługi'
WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały' WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce' WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, pc.category,
ii.quantity, ii.quantity,
ii.price_netto ii.price_netto
FROM ecommerce_invoices AS i FROM ecommerce_invoices AS i
INNER JOIN ecommerce_invoices_products AS ii ON i.id = ii.invoice_id INNER JOIN ecommerce_invoices_products AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.invoice_id
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci INNER JOIN ecmproducts AS p
AND cb.bean_name = 'EcmProducts' ON ii.ecmproduct_id = p.id
AND cb.deleted = 0 LEFT JOIN (
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id SELECT
WHERE i.type = 'normal' AND YEAR(i.register_date) = 2024 cb.bean_id AS product_id,
GROUP BY GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category
i.document_no, FROM ecmproductcategories_bean AS cb
i.register_date, INNER JOIN ecmproductcategories AS c
i.parent_name, ON c.id = cb.ecmproductcategory_id
p.code, AND c.deleted = 0
p.name, WHERE cb.bean_name = 'EcmProducts'
p.group_ks, AND cb.deleted = 0
ii.quantity, GROUP BY cb.bean_id
ii.price_netto ) AS pc
ON pc.product_id = p.id
WHERE i.type = 'normal'
AND i.register_date >= '2024-01-01'
AND i.register_date < '2025-01-01'
ORDER BY i.register_date DESC; ORDER BY i.register_date DESC;
", ",
'filename' => 'ecommerce_invoices_2024.csv', 'filename' => 'ecommerce_invoices_2024.csv',
@@ -544,33 +562,37 @@ SELECT
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Usługi' WHEN '3' THEN 'Usługi'
WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały' WHEN '530547ef-2dea-7622-843b-59d745b14c64' THEN 'Materiały'
WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce' WHEN '8451dded-710f-51c2-7ed1-60a377eaa7b7' THEN 'Surowce'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, pc.category,
ii.quantity, ii.quantity,
ii.price_netto ii.price_netto
FROM ecommerce_invoices AS i FROM ecommerce_invoices AS i
INNER JOIN ecommerce_invoices_products AS ii ON i.id = ii.invoice_id INNER JOIN ecommerce_invoices_products AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.invoice_id
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci INNER JOIN ecmproducts AS p
AND cb.bean_name = 'EcmProducts' ON ii.ecmproduct_id = p.id
AND cb.deleted = 0 LEFT JOIN (
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id SELECT
WHERE i.type = 'normal' AND YEAR(i.register_date) = 2025 cb.bean_id AS product_id,
GROUP BY GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category
i.document_no, FROM ecmproductcategories_bean AS cb
i.register_date, INNER JOIN ecmproductcategories AS c
i.parent_name, ON c.id = cb.ecmproductcategory_id
p.code, AND c.deleted = 0
p.name, WHERE cb.bean_name = 'EcmProducts'
p.group_ks, AND cb.deleted = 0
ii.quantity, GROUP BY cb.bean_id
ii.price_netto ) AS pc
ON pc.product_id = p.id
WHERE i.type = 'normal'
AND i.register_date >= '2025-01-01'
AND i.register_date < '2026-01-01'
ORDER BY i.register_date DESC; ORDER BY i.register_date DESC;
", ",
'filename' => 'ecommerce_invoices_2025.csv', 'filename' => 'ecommerce_invoices_2025.csv',
@@ -595,32 +617,40 @@ SELECT
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Surowiec' WHEN '3' THEN 'Surowiec'
WHEN 4 THEN 'Usługa' WHEN '4' THEN 'Usługa'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, pc.category,
s.name AS stock, s.name AS stock,
ii.quantity ii.quantity
FROM ecmstockdocinsideouts AS i FROM ecmstockdocinsideouts AS i
INNER JOIN ecmstockdocinsideoutitems AS ii ON i.id = ii.ecmstockdocinsideout_id INNER JOIN ecmstockdocinsideoutitems AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.ecmstockdocinsideout_id
INNER JOIN ecmstocks AS s ON i.stock_id = s.id AND ii.deleted = 0
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci INNER JOIN ecmproducts AS p
AND cb.bean_name = 'EcmProducts' ON ii.ecmproduct_id = p.id
AND cb.deleted = 0 AND p.deleted = 0
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id INNER JOIN ecmstocks AS s
WHERE YEAR(i.register_date) = 2025 ON i.stock_id = s.id
GROUP BY AND s.deleted = 0
i.document_no, LEFT JOIN (
i.register_date, SELECT
p.code, cb.bean_id AS product_id,
p.name, GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category
p.group_ks, FROM ecmproductcategories_bean AS cb
s.name, INNER JOIN ecmproductcategories AS c
ii.quantity ON c.id = cb.ecmproductcategory_id
AND c.deleted = 0
WHERE cb.bean_name = 'EcmProducts'
AND cb.deleted = 0
GROUP BY cb.bean_id
) AS pc
ON pc.product_id = p.id
WHERE i.deleted = 0
AND i.register_date BETWEEN '2025-01-01' AND '2025-12-31'
ORDER BY i.register_date DESC; ORDER BY i.register_date DESC;
", ",
'filename' => 'rw_2025.csv', 'filename' => 'rw_2025.csv',
@@ -633,32 +663,40 @@ SELECT
p.code, p.code,
p.name, p.name,
CASE p.group_ks CASE p.group_ks
WHEN 1 THEN 'Towar handlowy' WHEN '1' THEN 'Towar handlowy'
WHEN 2 THEN 'Wyrób gotowy' WHEN '2' THEN 'Wyrób gotowy'
WHEN 3 THEN 'Surowiec' WHEN '3' THEN 'Surowiec'
WHEN 4 THEN 'Usługa' WHEN '4' THEN 'Usługa'
ELSE 'Nieznane' ELSE 'Nieznane'
END AS group_ks, END AS group_ks_name,
GROUP_CONCAT(c.name ORDER BY cb.position SEPARATOR ' | ') AS category, pc.category,
s.name AS stock, s.name AS stock,
ii.quantity ii.quantity
FROM ecmstockdocinsideouts AS i FROM ecmstockdocinsideouts AS i
INNER JOIN ecmstockdocinsideoutitems AS ii ON i.id = ii.ecmstockdocinsideout_id INNER JOIN ecmstockdocinsideoutitems AS ii
INNER JOIN ecmproducts AS p ON ii.ecmproduct_id = p.id ON i.id = ii.ecmstockdocinsideout_id
INNER JOIN ecmstocks AS s ON i.stock_id = s.id AND ii.deleted = 0
LEFT JOIN ecmproductcategories_bean AS cb ON cb.bean_id COLLATE utf8_general_ci = p.id COLLATE utf8_general_ci INNER JOIN ecmproducts AS p
AND cb.bean_name = 'EcmProducts' ON ii.ecmproduct_id = p.id
AND cb.deleted = 0 AND p.deleted = 0
LEFT JOIN ecmproductcategories AS c ON c.id = cb.ecmproductcategory_id INNER JOIN ecmstocks AS s
WHERE YEAR(i.register_date) = 2024 ON i.stock_id = s.id
GROUP BY AND s.deleted = 0
i.document_no, LEFT JOIN (
i.register_date, SELECT
p.code, cb.bean_id AS product_id,
p.name, GROUP_CONCAT(DISTINCT c.name ORDER BY cb.position SEPARATOR ' | ') AS category
p.group_ks, FROM ecmproductcategories_bean AS cb
s.name, INNER JOIN ecmproductcategories AS c
ii.quantity ON c.id = cb.ecmproductcategory_id
AND c.deleted = 0
WHERE cb.bean_name = 'EcmProducts'
AND cb.deleted = 0
GROUP BY cb.bean_id
) AS pc
ON pc.product_id = p.id
WHERE i.deleted = 0
AND i.register_date BETWEEN '2024-01-01' AND '2024-12-31'
ORDER BY i.register_date DESC; ORDER BY i.register_date DESC;
", ",
'filename' => 'rw_2024.csv', 'filename' => 'rw_2024.csv',
@@ -746,6 +784,5 @@ function exportToCSVFile($res, $fullpath, array $headers = null, $delimiter = ';
} }
fclose($fp); fclose($fp);
$chmod_ok = @chmod($fullpath, 0664); return ['ok'=>true, 'path'=>$fullpath, 'rows'=>$count, 'error'=>null];
return ['ok'=>true, 'path'=>$fullpath, 'rows'=>$count, 'chmod'=>$chmod_ok, 'error'=>null];
} }

View File

@@ -14,6 +14,7 @@
} }
require_once('./REST/functions.php'); require_once('./REST/functions.php');
require_once('./REST/bimai.php');
// Enable SugarCRM features // Enable SugarCRM features
if(!defined('sugarEntry'))define('sugarEntry', true); if(!defined('sugarEntry'))define('sugarEntry', true);
require_once('./include/entryPoint.php'); require_once('./include/entryPoint.php');
@@ -41,6 +42,8 @@
case 'createCSVReports': case 'createCSVReports':
createCSVReports(); createCSVReports();
break; break;
case 'bimai.export.ecommerce':
bimai_exportEcommerceDate($_GET['since']);
break;
} }
// https://crm.twinpol.com/REST/index.php?key=d68dac4c-f784-4e1b-8267-9ffcfa0eda4c&action=createCostDocumentFromInvoice&record=c3f6eaa6-0cbd-8c89-1a8c-683ff19a36db // https://crm.twinpol.com/REST/index.php?key=d68dac4c-f784-4e1b-8267-9ffcfa0eda4c&action=createCostDocumentFromInvoice&record=c3f6eaa6-0cbd-8c89-1a8c-683ff19a36db
?>

View File

@@ -236,7 +236,7 @@ $sugar_config = array (
'log_memory_usage' => false, 'log_memory_usage' => false,
'logger' => 'logger' =>
array ( array (
'level' => 'off', 'level' => 'bimit',
'file' => 'file' =>
array ( array (
'ext' => '.log', 'ext' => '.log',

View File

@@ -16,7 +16,6 @@ $sugar_config['stack_trace_errors'] = false;
$sugar_config['developerMode'] = false; $sugar_config['developerMode'] = false;
$sugar_config['list_max_entries_per_page'] = '100'; $sugar_config['list_max_entries_per_page'] = '100';
$sugar_config['other_group_tab_displayed'] = true; $sugar_config['other_group_tab_displayed'] = true;
$sugar_config['logger']['level'] = 'off';
$sugar_config['passwordsetting']['systexpiration'] = '2'; $sugar_config['passwordsetting']['systexpiration'] = '2';
$sugar_config['passwordsetting']['systexpirationlogin'] = '1'; $sugar_config['passwordsetting']['systexpirationlogin'] = '1';
$sugar_config['passwordsetting']['systexpirationtime'] = ''; $sugar_config['passwordsetting']['systexpirationtime'] = '';

View File

@@ -68,7 +68,6 @@ $current_user->getSystemUser();
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
//// PREP FOR SCHEDULER PID //// PREP FOR SCHEDULER PID
$GLOBALS['log']->debug('--------------------------------------------> at cron.php <--------------------------------------------');
$cachePath = $GLOBALS['sugar_config']['cache_dir'].'modules/Schedulers'; $cachePath = $GLOBALS['sugar_config']['cache_dir'].'modules/Schedulers';
$pid = 'pid.php'; $pid = 'pid.php';
@@ -79,14 +78,10 @@ if(!is_file($cachePath.'/'.$pid)) {
if(is_writable($cachePath)) { // the "file" does not yet exist if(is_writable($cachePath)) { // the "file" does not yet exist
write_array_to_file('timestamp', array(strtotime(date('H:i'))) , $cachePath.'/'.$pid); write_array_to_file('timestamp', array(strtotime(date('H:i'))) , $cachePath.'/'.$pid);
require_once($cachePath.'/'.$pid); require_once($cachePath.'/'.$pid);
} else {
$GLOBALS['log']->fatal('Scheduler cannot write PID file. Please check permissions on '.$cachePath);
} }
} else { } else {
if(is_writable($cachePath.'/'.$pid)) { if(is_writable($cachePath.'/'.$pid)) {
require_once($cachePath.'/'.$pid); require_once($cachePath.'/'.$pid);
} else {
$GLOBALS['log']->fatal('Scheduler cannot read the PID file. Please check permissions on '.$cachePath);
} }
} }
//// END PREP FOR SCHEDULER PID //// END PREP FOR SCHEDULER PID
@@ -102,11 +97,7 @@ if($timestamp[0] < strtotime(date('H:i'))) {
$s = new Scheduler(); $s = new Scheduler();
$s->flushDeadJobs(); $s->flushDeadJobs();
$s->checkPendingJobs(); $s->checkPendingJobs();
} else {
$GLOBALS['log']->fatal('Scheduler cannot write PID file. Please check permissions on '.$cachePath);
} }
} else {
$GLOBALS['log']->fatal('If you see a whole string of these, there is a chance someone is attacking your system.');
} }
$exit_on_cleanup = true; $exit_on_cleanup = true;
sugar_cleanup($exit_on_cleanup); sugar_cleanup($exit_on_cleanup);

View File

@@ -0,0 +1,452 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Eugene Kin Chee Yip
# Date: 7 Nov 2008
import cherrypy
import socket
import math
import openFlashChart
from openFlashChart_varieties import (Line,
Line_Dot,
Line_Hollow,
Bar,
Bar_Filled,
Bar_Glass,
Bar_3d,
Bar_Sketch,
HBar,
Bar_Stack,
Area_Line,
Area_Hollow,
Pie,
Scatter,
Scatter_Line)
from openFlashChart_varieties import (dot_value,
hbar_value,
bar_value,
bar_3d_value,
bar_glass_value,
bar_sketch_value,
bar_stack_value,
pie_value,
scatter_value,
x_axis_labels,
x_axis_label)
class OFC:
@cherrypy.expose
def index(self):
graphs = []
# Line Charts
graphs.append(openFlashChart.flashHTML('100%', '400', '/line', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/line_dot', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/line_hollow', '/flashes/'))
# Bar Charts
graphs.append(openFlashChart.flashHTML('100%', '400', '/bar', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/bar_filled', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/bar_glass', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/bar_3d', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/bar_sketch', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/hbar', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/bar_stack', '/flashes/'))
# Area Charts
graphs.append(openFlashChart.flashHTML('100%', '400', '/area_line', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/area_hollow', '/flashes/'))
# Pie Chart
graphs.append(openFlashChart.flashHTML('100%', '400', '/pie', '/flashes/'))
# Scatter Charts
graphs.append(openFlashChart.flashHTML('100%', '400', '/scatter', '/flashes/'))
graphs.append(openFlashChart.flashHTML('100%', '400', '/scatter_line', '/flashes/'))
# Radar Charts
graphs.append(openFlashChart.flashHTML('100%', '400', '/radar', '/flashes/'))
# Testing Chart
graphs.append(openFlashChart.flashHTML('100%', '400', '/test', '/flashes/'))
graphs.append(self.source("html_snippet.html"))
return self.source("OFC.htm") %({"chart": "<br/><br/><br/><br/>".join(graphs)})
# Line Charts
@cherrypy.expose
def line(self):
plot1 = Line(text = "line1", fontsize = 20, values = range(0,10))
plot2 = Line(text = "line2", fontsize = 06, values = range(10,0, -1))
plot3 = Line(text = "line3", fontsize = 12, values = range(-5,5))
plot1.set_line_style(4, 3)
plot2.set_line_style(5, 5)
plot3.set_line_style(4, 8)
plot1.set_colour('#D4C345')
plot2.set_colour('#C95653')
plot3.set_colour('#8084FF')
chart = openFlashChart.template("Line chart")
chart.set_y_axis(min = -6, max = 10)
chart.add_element(plot1)
chart.add_element(plot2)
chart.add_element(plot3)
return chart.encode()
@cherrypy.expose
def line_dot(self):
plot1 = Line_Dot(values = [math.sin(float(x)/10) * 1.9 + 4 for x in range(0, 62, 2)])
plot2 = Line_Dot(values = [math.sin(float(x)/10) * 1.9 + 7 for x in range(0, 62, 2)])
plot3 = Line_Dot(values = [math.sin(float(x)/10) * 1.9 + 10 for x in range(0, 62, 2)])
plot1.set_halo_size(0)
plot2.set_halo_size(1)
plot3.set_halo_size(3)
plot1.set_width(1)
plot2.set_width(2)
plot3.set_width(6)
plot1.set_dot_size(4)
plot2.set_dot_size(4)
plot3.set_dot_size(6)
chart = openFlashChart.template("Line_Dot chart")
chart.set_y_axis(min = 0, max = 15, steps = 5)
chart.add_element(plot1)
chart.add_element(plot2)
chart.add_element(plot3)
return chart.encode()
@cherrypy.expose
def line_hollow(self):
plot1 = Line_Hollow(values = [math.sin(float(x)/10) * 1.9 + 4 for x in range(0, 62, 2)])
plot2 = Line_Hollow(values = [math.sin(float(x)/10) * 1.9 + 7 for x in range(0, 62, 2)])
plot3 = Line_Hollow(values = [math.sin(float(x)/10) * 1.9 + 10 for x in range(0, 62, 2)])
plot1.set_halo_size(3)
plot2.set_halo_size(1)
plot3.set_halo_size(0)
plot1.set_width(1)
plot2.set_width(2)
plot3.set_width(3)
plot1.set_dot_size(4)
plot2.set_dot_size(4)
plot3.set_dot_size(6)
chart = openFlashChart.template("Line_Hollow chart")
chart.set_y_axis(min = 0, max = 15, steps = 5)
chart.add_element(plot1)
chart.add_element(plot2)
chart.add_element(plot3)
return chart.encode()
# Bar Charts
@cherrypy.expose
def bar(self):
plot = Bar(text = "bar1", values = range(9, 0, -1))
chart = openFlashChart.template("Bar chart")
chart.add_element(plot)
return chart.encode()
@cherrypy.expose
def bar_filled(self):
plot = Bar_Filled(values = range(9, 0, -1) + [bar_value((5, 3), '#AAAAAA', 'Special:<br>Top = #top#<br>Bottom = #bottom#')], colour = '#E2D66A', outline = '#577261')
chart = openFlashChart.template("Bar_Filled chart",)
chart.add_element(plot)
chart.set_bg_colour('#FFFFFF')
return chart.encode()
@cherrypy.expose
def bar_glass(self):
plot = Bar_Glass(values = range(-5, 5, 2) + [bar_glass_value(5, '#333333', 'Special:<br>Top = #top#<br>Bottom = #bottom#')])
chart = openFlashChart.template("Bar_Glass chart")
chart.set_y_axis(min = -6, max = 6)
chart.add_element(plot)
return chart.encode()
@cherrypy.expose
def bar_3d(self):
plot = Bar_3d(values = range(-8, 8, 2) + [bar_3d_value(5, '#333333', 'Special:<br>Top = #top#<br>Bottom = #bottom#')])
plot.set_colour('#D54C78')
chart = openFlashChart.template("Bar_3d chart")
chart.set_y_axis(min = -8, max = 8)
chart.set_x_axis(colour = '#909090', three_d = 5, labels = list('qp#m^fur'))
chart.add_element(plot)
return chart.encode()
@cherrypy.expose
def bar_sketch(self):
plot1 = Bar_Sketch(values = range(10, 0, -1) + [bar_sketch_value(5, '#333333', 'Special:<br>Top = #top#')], colour = '#81AC00', outline = '#567300')
plot2 = Bar_Sketch(values = [bar_sketch_value(5, '#333333', 'Special:<br>Top = #top#')] + range(10, 0, -1), colour = '#81ACFF', outline = '#5673FF')
chart = openFlashChart.template("Bar_Sketch chart", style = '{color: #567300; font-size: 14px}')
chart.add_element(plot1)
chart.add_element(plot2)
return chart.encode()
@cherrypy.expose
def hbar(self):
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
plot = HBar(colour = '#86BBEF')
plot.set_tooltip('Months: #val#')
plot.append_values(hbar_value((0, 4), colour = '#909090'))
plot.append_values(hbar_value((4, 8), colour = '#909009'))
plot.append_values(hbar_value((8, 11), tooltip = '#left# to #right#<br>%s to %s (#val# months)' %(months[8], months[11])))
chart = openFlashChart.template("HBar chart")
chart.set_x_axis(offset = False, labels = x_axis_labels(labels = months))
chart.set_y_axis(offset = True, labels = ['one', 'two', 'three'])
chart.add_element(plot)
return chart.encode()
@cherrypy.expose
def bar_stack(self):
plot = Bar_Stack(colours = ['#C4D318', '#50284A', '#7D7B6A'])
plot.set_tooltip('X label [#x_label#], Value [#val#]<br>Total [#total#]')
plot.append_keys('#C4D318', 'job1', 13)
plot.append_keys('#50284A', 'job2', 13)
plot.append_keys('#7D7B6A', 'job3', 13)
plot.append_keys('#ff0000', 'job4', 13)
plot.append_keys('#ff00ff', 'job5', 13)
plot.append_stack([2.5, 5, 2.5])
plot.append_stack([2.5, 5, 1.25, 1.25])
plot.append_stack([5, bar_stack_value(5, '#ff0000')])
plot.append_stack([2, 2, 2, 2, bar_stack_value(2, '#ff00ff')])
chart = openFlashChart.template("Bar_Stack chart", style = '{font-size: 20px; color: #F24062; text-align: center;}')
chart.set_tooltip(behaviour = 'hover')
chart.set_x_axis(labels = x_axis_labels(labels = ['Winter', 'Spring', 'Summer', 'Autumn']))
chart.set_y_axis(min = 0, max = 14, steps = 2)
chart.add_element(plot)
return chart.encode()
# Area charts
@cherrypy.expose
def area_line(self):
plot = Area_Line(colour = '#C4B86A', fill = '#C4B86A', fill_alpha = 0.7, values = [math.sin(float(x)/10) * 1.9 for x in range(0, 62, 2)])
plot.set_halo_size(1)
plot.set_width(2)
plot.set_dot_size(4)
chart = openFlashChart.template("Area_Line chart")
chart.set_y_axis(min = -2, max = 2, steps = 2, offset = True)
chart.set_x_axis(labels = x_axis_labels(labels = ['%d' %i for i in range(0, 62, 2)], steps = 4, rotate = 'vertical'), steps = 2)
chart.add_element(plot)
return chart.encode()
@cherrypy.expose
def area_hollow(self):
plot = Area_Hollow(colour = '#838A96', fill = '#E01B49', fill_alpha = 0.4, values = [math.sin(float(x)/10) * 1.9 for x in range(0, 62, 2)])
plot.set_halo_size(1)
plot.set_width(2)
plot.set_dot_size(4)
chart = openFlashChart.template("Area_Hollow chart")
chart.set_y_axis(min = -2, max = 2, steps = 2, offset = False)
chart.set_x_axis(labels = x_axis_labels(rotate = 'diagonal'), steps = 2)
chart.add_element(plot)
return chart.encode()
# Pie chart
@cherrypy.expose
def pie(self):
plot = Pie(start_angle = 35, animate = True, values = [2, 3, pie_value(6.5, ('hello (6.5)', '#FF33C9', 24))], colours = ['#D01F3C', '#356AA0', '#C79810'], label_colour = '#432BAF')
plot.set_tooltip('#val# of #total#<br>#percent# of 100%')
plot.set_gradient_fill(True)
plot.set_on_click('plot1')
plot.set_no_labels(False)
chart = openFlashChart.template("Pie chart")
chart.add_element(plot)
return chart.encode()
# Scatter charts
@cherrypy.expose
def scatter(self):
radians = [math.radians(degree) for degree in xrange(0, 360, 5)]
values = [scatter_value(('%.2f' %math.sin(radian), '%.2f' %math.cos(radian))) for radian in radians]
plot1 = Scatter(colour = '#FFD600', values = [scatter_value((0, 0))])
plot2 = Scatter(colour = '#D600FF', values = values)
plot1.set_dot_size(10)
plot2.set_dot_size(3)
chart = openFlashChart.template("Scatter chart")
chart.set_x_axis(min = -2, max = 3)
chart.set_y_axis(min = -2, max = 2)
chart.add_element(plot1)
chart.add_element(plot2)
return chart.encode()
@cherrypy.expose
def scatter_line(self):
from random import randint
x_values = [0]
while x_values[-1] < 25:
x_values.append(x_values[-1] + float(randint(5, 15))/10)
values = [scatter_value((x, float(randint(-15, 15))/10)) for x in x_values]
plot = Scatter_Line(colour = '#FFD600', values = values)
plot.set_dot_size(3)
chart = openFlashChart.template("Scatter_Line chart")
chart.set_x_axis(min = 0, max = 25)
chart.set_y_axis(min = -10, max = 10)
chart.add_element(plot)
return chart.encode()
# Radar Charts
@cherrypy.expose
def radar(self):
plot = Area_Hollow(colour = '#45909F', fill = '#45909F', fill_alpha = 0.4, values = [3, 4, 5, 4, 3, 3, 2.5])
plot.set_width(1)
plot.set_dot_size(4)
plot.set_halo_size(1)
plot.set_loop()
chart = openFlashChart.template("Radar chart")
chart.set_bg_colour('#DFFFEC')
chart.set_radar_axis(max = 5, colour = '#EFD1EF', grid_colour = '#EFD1EF', labels = list('012345'), spoke_labels = list('1234567'))
chart.set_tooltip(behaviour = 'proximity')
chart.add_element(plot)
return chart.encode()
# Testing Graph
@cherrypy.expose
def test(self):
plot1 = Line(text = "line1", fontsize = 20, values = [None, 5, 1, 2, 4, None, None, 2, 7, 5])
plot2 = Line(text = "line2", fontsize = 12, values = range(-4, 7, 1))
plot3 = Bar_Glass(text = "bar1", values = [4, None, -4, 3, bar_glass_value((5, -2), '#333333', 'Special:<br>Top = #top#<br>Bottom = #bottom#'), 7, None, None, -5, 5])
plot1.set_tooltip('Title1:<br>Amount = #val#')
plot2.set_tooltip('Title2:<br>Value = #val#')
plot3.set_tooltip('Title3:<br>Height = #val#')
plot1.set_on_click('plot1')
plot2.set_on_click('plot2')
plot1.set_line_style(4, 3)
plot2.set_line_style(4, 8)
plot1.set_colour('#D4C345')
plot2.set_colour('#8084FF')
plot3.set_colour('#FF84FF')
chart = openFlashChart.template("Testing chart", style = '{font-size: 40px; font-family: Times New Roman; color: #A2ACBA; text-align: right;}')
chart.set_x_axis(stroke = 10, colour = '#165132', tick_height = 30, grid_colour = '#AAEE00', offset = True, steps = 2, labels = x_axis_labels(labels = list('sfwertr56w') + [x_axis_label('custom!!', '#2683CF', 24, 'diagonal')], steps = 2))
chart.set_y_axis(stroke = 5, colour = '#1E33FF', tick_length = 15, grid_colour = '#090305', offset = True, steps = 4, min = -6)
chart.set_y_axis_right(stroke = 5, colour = '#44FF22', tick_length = 20, grid_colour = '#55ff55', offset = True, steps = 1)
chart.set_x_legend("x-axis legend", style = '{font-size: 20px; color: #778877}')
chart.set_y_legend("y-axis legend", style = '{font-size: 22px; color: #778877}')
chart.set_tooltip(shadow = True, stroke = 4, colour = '#909090', bg_colour = '#FAFAFA', title_style = '{font-size: 14px; color: #CC2A43;}', body_style = '{font-size: 10px; font-weight: bold; color: #000000;}')
chart.add_element(plot1)
chart.add_element(plot2)
chart.add_element(plot3)
return chart.encode()
@cherrypy.expose
def ajax(self, count):
if int(count) % 3 is 0:
plot = Line_Dot(text = "line1", fontsize = 20, values = [None, 5, dot_value(1, '#D02020', '#val#<br>Text'), 2, 4, None, None, 2, 7, 5])
elif int(count) % 3 is 1:
plot = Line(text = "line2", fontsize = 12, values = range(-4, 7, 1))
else:
plot = Bar_Glass(text = "bar1", values = [4, None, -4, 3, bar_glass_value((5, -2), '#333333', 'Special:<br>Top = #top#<br>Bottom = #bottom#'), 7, None, None, -5, 5])
plot.set_tooltip('Title1:<br>Amount = #val#')
plot.set_on_click('plot1')
plot.set_line_style(4, 3)
plot.set_colour('#D4C345')
chart = openFlashChart.template("Testing chart: %s" %count, style = '{font-size: 40px; font-family: Times New Roman; color: #A2ACBA; text-align: right;}')
chart.set_x_axis(stroke = 10, colour = '#165132', tick_height = 30, grid_colour = '#AAEE00', offset = True, steps = 2, labels = x_axis_labels(labels = list('sfwertr56w') + [x_axis_label('custom!!', '#2683CF', 24, 210)], steps = 2))
chart.set_y_axis(stroke = 5, colour = '#1E33FF', tick_length = 15, grid_colour = '#090305', offset = True, steps = 4, min = -6)
chart.set_y_axis_right(stroke = 5, colour = '#44FF22', tick_length = 20, grid_colour = '#55ff55', offset = True, steps = 1)
chart.set_x_legend("x-axis legend", style = '{font-size: 20px; color: #778877}')
chart.set_y_legend("y-axis legend", style = '{font-size: 22px; color: #778877}')
chart.set_tooltip(shadow = True, stroke = 4, colour = '#909090', bg_colour = '#FAFAFA', title_style = '{font-size: 14px; color: #CC2A43;}', body_style = '{font-size: 10px; font-weight: bold; color: #000000;}')
chart.add_element(plot)
return chart.encode()
def source(self, filename):
"""Opens a file specified by the file/pathname in read-only"""
file = open(filename, 'r')
result = file.read()
file.close()
return result
@cherrypy.expose
def flashes(self, filename):
cherrypy.response.headers['Content-Type'] = "application/x-shockwave-flash"
cherrypy.response.headers['Expires'] = "Tue, 01 Dec 2009 12:00:00 GMT"
cherrypy.response.headers['Cache-Control'] = "Public"
return open(filename)
cherrypy.server.socket_host = socket.gethostbyname(socket.gethostname())
cherrypy.quickstart(OFC(), config = 'serverconfig.conf')

View File

@@ -0,0 +1,88 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Emanuel Fonseca
# Email: emdfonseca<at>gmail<dot>com
# Date: 25 August 2008
#
# Author: Eugene Kin Chee Yip
# Date: 7 Nov 2008
import cjson
from openFlashChart_elements import (title,
x_legend,
y_legend,
x_axis,
y_axis,
radar_axis,
tooltip)
def flashHTML(width, height, url, ofc_base_url="/flashes/", ofc_swf="OFC.swf" ):
return (
"""
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,115,0"
width="%(width)s" height="%(height)s" id="chart" align="middle">
<param name="allowScriptAccess" value="sameDomain"/>
<param name="movie" value="%(ofc_base_url)s%(ofc_swf)s"/>
<param name="FlashVars" value="data-file=%(url)s"/>
<param name="quality" value="high"/>
<param name="bgcolor" value="#FFFFFF"/>
<embed src="%(ofc_base_url)s%(ofc_swf)s" FlashVars="data-file=%(url)s" quality="high" bgcolor="#FFFFFF"
width=%(width)s height=%(height)s name="chart" align="middle" allowScriptAccess="sameDomain"
type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"/>
</object>
""") % locals()
class template(dict):
def __init__(self, title_string, style = None):
self['title'] = title(title_string, style)
def set_x_legend(self, legend, style):
self['x_legend'] = x_legend(legend, style)
def set_y_legend(self, legend, style):
self['y_legend'] = y_legend(legend, style)
def set_x_axis(self, stroke = None, tick_height = None, colour = None, grid_colour = None, labels = None, three_d = None, max = None, min = None, steps = None, offset = None):
self['x_axis'] = x_axis(stroke, tick_height, colour, grid_colour, labels, three_d, max, min, steps, offset)
def set_y_axis(self, stroke = None, tick_length = None, colour = None, grid_colour = None, labels = None, max = None, min = None, steps = None, offset = None):
self['y_axis'] = y_axis(stroke, tick_length, colour, grid_colour, labels, max, min, steps, offset)
def set_y_axis_right(self, stroke = None, tick_length = None, colour = None, grid_colour = None, labels = None, max = None, min = None, steps = None, offset = None):
self['y_axis_right'] = y_axis(stroke, tick_length, colour, grid_colour, labels, max, min, steps, offset)
def set_radar_axis(self, stroke = None, tick_height = None, colour = None, grid_colour = None, labels = None, max = None, min = None, steps = None, spoke_labels = None):
self['radar_axis'] = radar_axis(stroke, tick_height, colour, grid_colour, labels, max, min, steps, spoke_labels)
def set_bg_colour(self, colour):
self['bg_colour'] = colour
def set_tooltip(self, shadow = None, stroke = None, colour = None, bg_colour = None, title_style = None, body_style = None, behaviour = None):
self['tooltip'] = tooltip(shadow, stroke, colour, bg_colour, title_style, body_style, behaviour)
def add_element(self, element):
try:
self['elements'].append(element)
except:
self['elements'] = [element]
def encode(self):
return cjson.encode(self)

View File

@@ -0,0 +1,163 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Emanuel Fonseca
# Email: emdfonseca<at>gmail<dot>com
# Date: 25 August 2008
#
# Author: Eugene Kin Chee Yip
# Date: 7 Nov 2008
##########################################
# title class
class title(dict):
def __init__(self, title, style=None):
self['text'] = title
self.set_style(style)
def set_style(self, style):
self['style'] = style if style else '{color: #000000; font-size: 12px;}'
class y_legend(title):
pass
class x_legend(title):
pass
##########################################
# axis classes
class axis(dict):
def __init__(self, stroke = None, colour = None, grid_colour = None, labels = None, max = None, min = None, steps = None, offset = None):
self.set_stroke(stroke)
self.set_colour(colour)
self.set_grid_colour(grid_colour)
self.set_labels(labels)
self.set_steps(steps)
self.set_offset(offset)
self.set_max(max)
self.set_min(min)
def set_stroke(self, stroke):
if stroke:
self['stroke'] = stroke
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_grid_colour(self, grid_colour):
if grid_colour:
self['grid-colour'] = grid_colour
def set_labels(self, labels):
if labels:
self['labels'] = labels
def set_steps(self, steps):
if steps:
self['steps'] = steps
def set_offset(self, offset):
if offset is not None:
self['offset'] = offset
def set_max(self, max):
if max:
self['max'] = max
def set_min(self, min):
if min:
self['min'] = min
class x_axis(axis):
def __init__(self, stroke = None, tick_height = None, colour = None, grid_colour = None, labels = None, three_d = None, max = None, min = None, steps = None, offset = None):
axis.__init__(self, stroke, colour, grid_colour, labels, max, min, steps, offset)
self.set_tick_height(tick_height)
self.set_3d(three_d)
def set_tick_height(self, tick_height):
if tick_height:
self['tick-height'] = tick_height
def set_3d(self, three_d):
if three_d:
self['3d'] = three_d
class y_axis(axis):
def __init__(self, stroke = None, tick_length = None, colour = None, grid_colour = None, labels = None, max = None, min = None, steps = None, offset = None):
axis.__init__(self, stroke, colour, grid_colour, labels, max, min, steps, offset)
self.set_tick_length(tick_length)
self.set_offset(offset)
def set_tick_length(self, tick_length):
if tick_length:
self['tick-length'] = tick_length
class radar_axis(axis):
def __init__(self, stroke = None, tick_height = None, colour = None, grid_colour = None, labels = None, max = None, min = None, steps = None, spoke_labels = None):
axis.__init__(self, stroke, colour, grid_colour, labels, max, min, steps)
self.set_tick_height(tick_height)
self.set_spoke_labels(spoke_labels)
def set_tick_height(self, tick_height):
if tick_height:
self['tick-height'] = tick_height
def set_spoke_labels(self, spoke_labels):
if spoke_labels:
self['spoke-labels'] = {'labels': spoke_labels}
##########################################
# tooltip class
class tooltip(dict):
def __init__(self, shadow = None, stroke = None, colour = None, bg_colour = None, title_style = None, body_style = None, behaviour = None):
self.set_shadow(shadow)
self.set_stroke(stroke)
self.set_colour(colour)
self.set_background(bg_colour)
self.set_title(title_style)
self.set_body(body_style)
self.set_behaviour(behaviour)
def set_shadow(self, shadow):
if shadow is not None:
self['shadow'] = shadow
def set_stroke(self, stroke):
if stroke:
self['stroke'] = stroke
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_background(self, background):
if background:
self['background'] = background
def set_title(self, title):
if title:
self['title'] = title
def set_body(self, body):
if body:
self['body'] = body
def set_behaviour(self, behaviour):
if behaviour == 'proximity':
self['mouse'] = 1
if behaviour == 'hover':
self['mouse'] = 2

View File

@@ -0,0 +1,391 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Emanuel Fonseca
# Email: emdfonseca<at>gmail<dot>com
# Date: 25 August 2008
#
# Author: Eugene Kin Chee Yip
# Date: 7 Nov 2008
class varieties(dict):
def __init__(self, type = None, alpha = None, colour = None, text = None, fontsize = None, values = None):
self.set_type(type)
self.set_alpha(alpha)
self.set_colour(colour)
self.set_text(text)
self.set_fontsize(fontsize)
self.set_values(values)
def set_type(self, type):
if type:
self['type'] = type
def set_alpha(self, alpha):
if alpha:
self['alpha'] = alpha
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_gradient_fill(self, gradient_fill):
self['gradient-fill'] = gradient_fill
def set_halo_size(self, size):
self['halo-size'] = size
def set_width(self, width):
self['width'] = width
def set_dot_size(self, size):
self['dot-size'] = size
def set_text(self, text):
if text:
self['text'] = text
def set_fontsize(self, fontsize):
if fontsize:
self['font-size'] = fontsize
def set_values(self, values):
if values:
self['values'] = values
def append_keys(self, colour = None, text = None, fontsize = None):
try:
self['keys'].append(bar_stack_key(colour, text, fontsize))
except:
self['keys'] = [bar_stack_key(colour, text, fontsize)]
def append_values(self, values):
try:
self['values'].append(values)
except:
self['values'] = [values]
def append_stack(self, values):
self.append_values(values)
def set_line_style(self, on, off):
self['line-style'] = line_style(on, off)
def set_tooltip(self, text):
self['tip'] = text
def set_no_labels(self, no_labels):
self['no-labels'] = no_labels
def set_loop(self):
self['loop'] = True
def set_on_click(self, javascript_call):
self['on-click'] = javascript_call
##########################################
# key class for bar stack
class bar_stack_key(dict):
def __init__(self, colour = None, text = None, fontsize = None):
self.set_colour(colour)
self.set_text(text)
self.set_fontsize(fontsize)
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_text(self, text):
if text:
self['text'] = text
def set_fontsize(self, fontsize):
if fontsize:
self['font-size'] = fontsize
##########################################
# value class for custom data points
class value(dict):
def __init__(self, val = None, colour = None, tooltip = None):
self.set_val(val)
self.set_colour(colour)
self.set_tooltip(tooltip)
def set_val(self, val):
if val:
self['value'] = val
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_tooltip(self, tooltip):
if tooltip:
self['tip'] = tooltip
class dot_value(value):
pass
class bar_value(value):
def __init__(self, (top, bottom), colour = None, tooltip = None):
value.__init__(self, colour = colour, tooltip = tooltip)
self.set_top_value(top)
self.set_bottom_value(bottom)
def set_top_value(self, top):
self['top'] = top
def set_bottom_value(self, bottom):
self['bottom'] = bottom
class bar_3d_value(bar_value):
def __init__(self, top, colour = None, tooltip = None):
value.__init__(self, colour = colour, tooltip = tooltip)
self.set_top_value(top)
def set_top_value(self, top):
self['top'] = top
class bar_glass_value(bar_3d_value):
pass
class bar_sketch_value(bar_3d_value):
pass
class hbar_value(value):
def __init__(self, (left, right), colour = None, tooltip = None):
value.__init__(self, colour = colour, tooltip = tooltip)
self.set_left_value(left)
self.set_right_value(right)
def set_left_value(self, left):
self['left'] = left
def set_right_value(self, right):
self['right'] = right
class bar_stack_value(value):
def __init__(self, val, colour = None, tooltip = None):
value.__init__(self, colour = colour, tooltip = tooltip)
self.set_value(val)
def set_value(self, val):
if val:
self['val'] = val
class pie_value(value):
def __init__(self, val, label = None, colour = None, tooltip = None):
value.__init__(self, val, colour, tooltip)
self.set_label(label)
def set_label(self, (label, colour, fontsize)):
if label:
self['label'] = label
if colour:
self['label-colour'] = colour
if fontsize:
self['font-size'] = fontsize
class scatter_value(value):
def __init__(self, (x, y), colour = None, tooltip = None):
value.__init__(self, colour = colour, tooltip = tooltip)
self.set_x_value(x)
self.set_y_value(y)
def set_x_value(self, x):
self['x'] = x
def set_y_value(self, y):
self['y'] = y
##########################################
# line style class for charts with lines
class line_style(dict):
def __init__(self, on = None, off = None, style = 'dash'):
self.set_style(style)
self.set_on(on)
self.set_off(off)
def set_style(self, style):
self['style'] = style
def set_on(self, on):
if on:
self['on'] = on
def set_off(self, off):
if off:
self['off'] = off
##########################################
# label classes
class x_axis_labels(dict):
def __init__(self, labels = None, steps = None, colour = None, size = None, rotate = None):
self.set_labels(labels)
self.set_steps(steps)
self.set_colour(colour)
self.set_size(size)
self.set_rotate(rotate)
def set_labels(self, labels):
if labels:
self['labels'] = labels
def set_steps(self, steps):
if steps:
self['steps'] = steps
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_size(self, size):
if size:
self['size'] = size
def set_rotate(self, rotate):
if rotate:
self['rotate'] = rotate
class x_axis_label(dict):
def __init__(self, text = None, colour = None, size = None, rotate = None):
self.set_text(text)
self.set_colour(colour)
self.set_size(size)
self.set_rotate(rotate)
def set_text(self, text):
if text:
self['text'] = text
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_size(self, size):
if size:
self['size'] = size
def set_rotate(self, rotate):
if rotate:
self['rotate'] = rotate
##########################################
# Chart classes
# Line Charts
class Line(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'line', alpha, colour, text, fontsize, values)
class Line_Dot(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'line_dot', alpha, colour, text, fontsize, values)
class Line_Hollow(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'line_hollow', alpha, colour, text, fontsize, values)
# Bar Charts
class Bar(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'bar', alpha, colour, text, fontsize, values)
class Bar_Filled(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, outline = None):
varieties.__init__(self, 'bar_filled', alpha, colour, text, fontsize, values)
if outline:
self['outline-colour'] = outline
class Bar_Glass(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'bar_glass', alpha, colour, text, fontsize, values)
class Bar_3d(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'bar_3d', alpha, colour, text, fontsize, values)
class Bar_Sketch(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, outline = None):
varieties.__init__(self, 'bar_sketch', alpha, colour, text, fontsize, values)
if outline:
self['outline-colour'] = outline
class HBar(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None):
varieties.__init__(self, 'hbar', alpha, colour, text, fontsize, values)
class Bar_Stack(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, colours = None):
varieties.__init__(self, 'bar_stack', alpha, colour, text, fontsize, values)
if colours:
self['colours'] = colours
# Area Charts
class Area_Line(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, fill = None, fill_alpha = None):
varieties.__init__(self, 'area_line', alpha, colour, text, fontsize, values)
if fill_alpha:
self['fill-alpha'] = fill_alpha
if fill:
self['fill'] = fill
class Area_Hollow(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, fill = None, fill_alpha = None):
varieties.__init__(self, 'area_hollow', alpha, colour, text, fontsize, values)
if fill_alpha:
self['fill-alpha'] = fill_alpha
if fill:
self['fill'] = fill
# Pie Chart
class Pie(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, start_angle = None, animate = None, colours = None, label_colour = None):
varieties.__init__(self, 'pie', alpha, colour, text, fontsize, values)
if start_angle:
self['start-angle'] = start_angle
if animate:
self['animate'] = animate
if colours:
self['colours'] = colours
if label_colour:
self['label-colour'] = label_colour
# Scatter Charts
class Scatter(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, colours = None):
varieties.__init__(self, 'scatter', alpha, colour, text, fontsize, values)
class Scatter_Line(varieties):
def __init__(self, alpha = None, colour = None, text = None, fontsize = None, values = None, colours = None):
varieties.__init__(self, 'scatter_line', alpha, colour, text, fontsize, values)

View File

@@ -0,0 +1,136 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Emanuel Fonseca
# Email: emdfonseca<at>gmail<dot>com
# Date: 25 August 2008
import cherrypy
from string import Template
import datetime
import ofc2
from ofc2_element import Line, Bar, BarStack
xhtml_template = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<script type="text/javascript" src="/static/swfobject.js"></script>
<script type="text/javascript">
$js
</script>
<head>
<title>$title</title>
</head>
<body>
$body
</body>
</html>
"""
jstpl = Template('swfobject.embedSWF("/static/open-flash-chart.swf","$title", "$width", "$height", "$flash_ver", "expressInstall.swf", {"data-file": "$data_src"});\n')
dvtpl = Template('<h1>$title</h1><div id="$title"></div><br/>\n')
class Chart(object):
type = ''
title = 'chart'
width=400
height=200
flash_ver='9.0.0'
data_src = None
chart = None
def index(self):
return self.chart.encode()
index.exposed = True
def __init__(self, type, name):
self.title = name
self.data_src='/'+name
def get_js(self):
return jstpl.substitute(title=self.title, width=self.width, height=self.height, flash_ver = self.flash_ver, data_src = self.data_src)
def get_div(self):
return dvtpl.substitute(title=self.title)
class BarChart(Chart):
def __init__(self, type, name):
Chart.__init__(self, type, name)
# create the bar element and set its values
element = Bar(values=[9,8,7,6,5,4,3,2,1])
# create the chart and set its title
self.chart = ofc2.open_flash_chart(title=str(datetime.datetime.now()))
self.chart.add_element(element)
class BarStackChart(Chart):
def __init__(self, type, name):
Chart.__init__(self, type, name)
# create the bar element and set its values
element = BarStack(values=[ [ 2.5, 5 ], [ 7.5 ], [ 5, { 'val': 5, 'colour': '#ff0000' } ], [ 2, 2, 2, 2, { "val": 2, 'colour': '#ff00ff' } ] ])
# create the chart and set its title
self.chart = ofc2.open_flash_chart(title=str(datetime.datetime.now()))
self.chart.set_y_axis(min=0, max=14, steps=7)
self.chart.set_x_axis(labels=['a', 'b', 'c', 'd'])
self.chart.add_element(element)
class LineChart(Chart):
def __init__(self, type, name):
Chart.__init__(self, type, name)
# create the bar element and set its values
element = Line(values=[9,8,7,6,5,4,3,2,1])
# create the chart and set its title
self.chart = ofc2.open_flash_chart(title=str(datetime.datetime.now()))
self.chart.add_element(element)
class OFC2Demo(object):
tpl = Template(xhtml_template)
swfobjs = ''
divs = ''
linechart = LineChart('line_chart', 'linechart') # var name must be the same as the second param
barchart = BarChart('bar_chart', 'barchart') # var name must be the same as the second param
barstackchart = BarStackChart('bar_stack', 'barstackchart') # var name must be the same as the second param
def index(self):
self.load_charts()
response = self.tpl.substitute(title='Open Flash Charts 2 - Python Library Demo', js=self.swfobjs, body=self.divs)
return response
def load_charts(self):
self.swfobjs = ''
self.divs = ''
self.add_chart(self.linechart)
self.add_chart(self.barchart)
self.add_chart(self.barstackchart)
def add_chart(self, chart):
self.swfobjs += chart.get_js()
self.divs += chart.get_div()
# Expose methods
index.exposed = True
cherrypy.quickstart(OFC2Demo(), '/', 'cherrypy.conf')

View File

@@ -0,0 +1,167 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Emanuel Fonseca
# Email: emdfonseca<at>gmail<dot>com
# Date: 25 August 2008
import cjson
class Title(dict):
def __init__(self, title, style=None):
self['text'] = title
self.set_style(style)
def set_style(self, style):
if style:
self['style'] = style
class y_legend(Title):
pass
class x_legend(Title):
pass
##########################################
# axis classes
class axis(dict):
def __init__(self, stroke=None, tick_height=None, colour=None, grid_colour=None, steps=None):
self.set_stroke(stroke)
self.set_tick_height(tick_height)
self.set_colour(colour)
self.set_grid_colour(grid_colour)
self.set_steps(steps)
def set_stroke(self, stroke):
if stroke:
self['stroke'] = stroke
def set_tick_height(self, tick_height):
if tick_height:
self['tick_height'] = tick_height
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_grid_colour(self, grid_colour):
if grid_colour:
self['grid_colour'] = grid_colour
def set_steps(self, steps):
if steps:
self['steps'] = steps
class x_axis(axis):
def __init__(self, stroke=None, tick_height=None, colour=None, grid_colour=None, labels=None, steps=None):
axis.__init__(self, stroke, tick_height, colour, grid_colour, steps)
self.set_labels(labels)
self['orientation'] = 2
def set_labels(self, labels):
if labels:
self['labels'] = labels
class y_axis(axis):
def __init__(self, stroke=None, tick_height=None, colour=None, grid_colour=None, offset=None, max=None, min=None, steps=None):
axis.__init__(self, stroke, tick_height, colour, grid_colour, steps)
self.set_offset(offset)
self.set_max(max)
self.set_min(min)
def set_offset(self, offset):
if offset:
self['offset'] = offset
def set_max(self, max):
if max:
self['max'] = max
def set_min(self, min):
if min:
self['min'] = min
##########################################
# open_flash_chart class
class tooltip(dict):
def __init__(self, shadow=None, stroke=None, colour=None, bg_colour=None, title_style=None, body_style=None):
self.set_shadow(shadow)
self.set_stroke(stroke)
self.set_colour(colour)
self.set_background(bg_colour)
self.set_title(title_style)
self.set_body(body_style)
def set_shadow(self, shadow):
if shadow:
self['shadow'] = shadow
def set_stroke(self, stroke):
if stroke:
self['stroke'] = stroke
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_background(self, background):
if background:
self['background'] = background
def set_title(self, title):
if title:
self['title'] = title
def set_body(self, body):
if body:
self['body'] = body
##########################################
# open_flash_chart class
class open_flash_chart(dict):
def __init__(self, title, style=None):
self['title'] = Title(title, style)
def set_x_legend(self, legend):
self['x_legend'] = x_legend(legend)
def set_y_legend(self, legend):
self['y_legend'] = y_legend(legend)
def set_x_axis(self, stroke=None, tick_height=None, colour=None, grid_colour=None, labels=None, steps=None):
self['x_axis'] = x_axis(stroke, tick_height, colour, grid_colour, labels, steps)
def set_y_axis(self, stroke=None, tick_height=None, colour=None, grid_colour=None, offset=None, max=None, min=None, steps=None):
self['y_axis'] = y_axis(stroke, tick_height, colour, grid_colour, offset, max, min, steps)
def set_y_axis_right(self, stroke=None, tick_height=None, colour=None, grid_colour=None, offset=None, max=None, min=None, steps=None):
self['y_axis_right'] = y_axis(stroke, tick_height, colour, grid_colour, offset, max, min, steps)
def set_bg_colour(self, colour):
self['bg_colour'] = colour
def set_tooltip(self, shadow=None, stroke=None, colour=None, bg_colour=None, title_style=None, body_style=None):
self['tooltip'] = tooltip(shadow, stroke, colour, bg_colour, title_style, body_style)
def add_element(self, element):
try:
self['elements'].append(element)
except:
self['elements'] = [element]
def encode(self):
return cjson.encode(self)
#ofc = open_flash_chart('Example JSON')
#ofc.set_y_legend('Example Y Legend')
#ofc.set_x_legend('Example X Legend')
#ofc.set_x_axis(1, 1, '#ff0000', '#00ff00', ['sun', 'mon', 'tue'])
#ofc.set_x_axis(labels=['sun', 'mon', 'tue'])
#print cjson.encode(ofc)

View File

@@ -0,0 +1,57 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Author: Emanuel Fonseca
# Email: emdfonseca<at>gmail<dot>com
# Date: 25 August 2008
class element(dict):
def __init__(self, type=None, alpha=None, colour=None, text=None, fontsize=None, values=None):
self.set_type(type)
self.set_alpha(alpha)
self.set_colour(colour)
self.set_text(text)
self.set_fontsize(fontsize)
self.set_values(values)
def set_type(self, type):
if type:
self['type'] = type
def set_alpha(self, alpha):
if alpha:
self['alpha'] = alpha
def set_colour(self, colour):
if colour:
self['colour'] = colour
def set_text(self, text):
if text:
self['text'] = text
def set_fontsize(self, fontsize):
if fontsize:
self['font-size'] = fontsize
def set_values(self, values):
if values:
self['values'] = values
class Line(element):
def __init__(self, type=None, alpha=None, colour=None, text=None, fontsize=None, values=None):
element.__init__(self, 'line', alpha, colour, text, fontsize, values)
class Bar(element):
def __init__(self, type=None, alpha=None, colour=None, text=None, fontsize=None, values=None):
element.__init__(self, 'bar', alpha, colour, text, fontsize, values)
class BarStack(element):
def __init__(self, type=None, alpha=None, colour=None, text=None, fontsize=None, values=None):
element.__init__(self, 'bar_stack', alpha, colour, text, fontsize, values)

View File

@@ -65,6 +65,7 @@ class LoggerManager {
'error' => 25, 'error' => 25,
'fatal' => 10, 'fatal' => 10,
'security' => 5, 'security' => 5,
'bimit' => 2,
'off' => 0, 'off' => 0,
); );

View File

@@ -182,10 +182,6 @@ class MssqlManager extends DBManager
} }
} }
if($this->checkError('Could Not Connect:', $dieOnError))
$GLOBALS['log']->info("connected to db");
$GLOBALS['log']->info("Connect:".$this->database);
} }
/** /**
@@ -218,7 +214,6 @@ class MssqlManager extends DBManager
if (empty($app_strings) or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])) { if (empty($app_strings) or !isset($app_strings['ERR_MSSQL_DB_CONTEXT'])) {
//ignore the message from sql-server if $app_strings array is empty. This will happen //ignore the message from sql-server if $app_strings array is empty. This will happen
//only if connection if made before languge is set. //only if connection if made before languge is set.
$GLOBALS['log']->debug("Ignoring this database message: " . $sqlmsg);
$sqlmsg = ''; $sqlmsg = '';
} }
else { else {
@@ -229,7 +224,6 @@ class MssqlManager extends DBManager
} }
if ( strlen($sqlmsg) > 2 ) { if ( strlen($sqlmsg) > 2 ) {
$GLOBALS['log']->fatal("SQL Server error: " . $sqlmsg);
return true; return true;
} }
@@ -246,12 +240,8 @@ class MssqlManager extends DBManager
$suppress = false $suppress = false
) )
{ {
// Flag if there are odd number of single quotes
if ((substr_count($sql, "'") & 1))
$GLOBALS['log']->error("SQL statement[" . $sql . "] has odd number of single quotes.");
$this->countQuery($sql); $this->countQuery($sql);
$GLOBALS['log']->info('Query:' . $sql);
$this->checkConnection(); $this->checkConnection();
$this->query_time = microtime(true); $this->query_time = microtime(true);
@@ -268,7 +258,6 @@ class MssqlManager extends DBManager
$this->lastmysqlrow = -1; $this->lastmysqlrow = -1;
$this->query_time = microtime(true) - $this->query_time; $this->query_time = microtime(true) - $this->query_time;
$GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
$this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError); $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
@@ -295,8 +284,6 @@ class MssqlManager extends DBManager
if ($start < 0) if ($start < 0)
$start=0; $start=0;
$GLOBALS['log']->debug(print_r(func_get_args(),true));
$this->lastsql = $sql; $this->lastsql = $sql;
//change the casing to lower for easier string comparison, and trim whitespaces //change the casing to lower for easier string comparison, and trim whitespaces
@@ -392,7 +379,6 @@ class MssqlManager extends DBManager
else { else {
if ($start < 0) if ($start < 0)
$start = 0; $start = 0;
$GLOBALS['log']->debug(print_r(func_get_args(),true));
$this->lastsql = $sql; $this->lastsql = $sql;
$matches = array(); $matches = array();
preg_match('/^(.*SELECT )(.*?FROM.*WHERE)(.*)$/isU',$sql, $matches); preg_match('/^(.*SELECT )(.*?FROM.*WHERE)(.*)$/isU',$sql, $matches);
@@ -547,8 +533,6 @@ class MssqlManager extends DBManager
} }
} }
} }
$GLOBALS['log']->debug('Limit Query: ' . $newSQL);
$result = $this->query($newSQL, $dieOnError, $msg); $result = $this->query($newSQL, $dieOnError, $msg);
$this->dump_slow_queries($newSQL); $this->dump_slow_queries($newSQL);
return $result; return $result;
@@ -779,13 +763,11 @@ class MssqlManager extends DBManager
return $col_name; return $col_name;
} }
//break out of here, log this //break out of here, log this
$GLOBALS['log']->debug("No match was found for order by, pass string back untouched as: $orig_order_match");
return $orig_order_match; return $orig_order_match;
} }
else { else {
//if found, then parse and return //if found, then parse and return
//grab string up to the aliased column //grab string up to the aliased column
$GLOBALS['log']->debug("order by found, process sql string");
$psql = (trim($this->getAliasFromSQL($sql, $orderMatch ))); $psql = (trim($this->getAliasFromSQL($sql, $orderMatch )));
if (empty($psql)) if (empty($psql))
@@ -810,7 +792,6 @@ class MssqlManager extends DBManager
$col_name = $col_name. " ". $asc_desc; $col_name = $col_name. " ". $asc_desc;
//pass in new order by //pass in new order by
$GLOBALS['log']->debug("order by being returned is " . $col_name);
return $col_name; return $col_name;
} }
} }
@@ -829,7 +810,6 @@ class MssqlManager extends DBManager
{ {
global $beanList, $beanFiles; global $beanList, $beanFiles;
$GLOBALS['log']->debug("Module being processed is " . $module_str);
//get the right module files //get the right module files
//the module string exists in bean list, then process bean for correct table name //the module string exists in bean list, then process bean for correct table name
//note that we exempt the reports module from this, as queries from reporting module should be parsed for //note that we exempt the reports module from this, as queries from reporting module should be parsed for
@@ -847,14 +827,12 @@ class MssqlManager extends DBManager
$tbl_name = trim($tbl_name); $tbl_name = trim($tbl_name);
if(empty($tbl_name)){ if(empty($tbl_name)){
$GLOBALS['log']->debug("Could not find table name for module $module_str. ");
$tbl_name = $module_str; $tbl_name = $module_str;
} }
} }
else { else {
//since the module does NOT exist in beanlist, then we have to parse the string //since the module does NOT exist in beanlist, then we have to parse the string
//and grab the table name from the passed in sql //and grab the table name from the passed in sql
$GLOBALS['log']->debug("Could not find table name from module in request, retrieve from passed in sql");
$tbl_name = $module_str; $tbl_name = $module_str;
$sql = strtolower($sql); $sql = strtolower($sql);
@@ -873,7 +851,6 @@ class MssqlManager extends DBManager
if ($next_space > 0) { if ($next_space > 0) {
$tbl_name= substr($tableEnd,0, $next_space); $tbl_name= substr($tableEnd,0, $next_space);
if(empty($tbl_name)){ if(empty($tbl_name)){
$GLOBALS['log']->debug("Could not find table name sql either, return $module_str. ");
$tbl_name = $module_str; $tbl_name = $module_str;
} }
} }
@@ -911,7 +888,6 @@ class MssqlManager extends DBManager
} }
} }
//return table name //return table name
$GLOBALS['log']->debug("Table name for module $module_str is: ".$tbl_name);
return $tbl_name; return $tbl_name;
} }
@@ -1066,7 +1042,6 @@ class MssqlManager extends DBManager
$tableName $tableName
) )
{ {
$GLOBALS['log']->info("tableExists: $tableName");
$this->checkConnection(); $this->checkConnection();
$result = $this->query( $result = $this->query(
@@ -1135,7 +1110,6 @@ class MssqlManager extends DBManager
*/ */
public function getTablesArray() public function getTablesArray()
{ {
$GLOBALS['log']->debug('MSSQL fetching table list');
if($this->getDatabase()) { if($this->getDatabase()) {
$tables = array(); $tables = array();
@@ -1159,7 +1133,6 @@ class MssqlManager extends DBManager
*/ */
public function wakeupFTS() public function wakeupFTS()
{ {
$GLOBALS['log']->debug('MSSQL about to wakeup FTS');
if($this->getDatabase()) { if($this->getDatabase()) {
//create wakup catalog //create wakup catalog

View File

@@ -120,13 +120,10 @@ class MysqlManager extends DBManager
if (mysql_errno($this->getDatabase())) { if (mysql_errno($this->getDatabase())) {
if ($this->dieOnError || $dieOnError){ if ($this->dieOnError || $dieOnError){
$GLOBALS['log']->fatal("MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database));
sugar_die ($msg."MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database)); sugar_die ($msg."MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database));
} }
else { else {
$this->last_error = $msg."MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database); $this->last_error = $msg."MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database);
$GLOBALS['log']->error("MySQL error ".mysql_errno($this->database).": ".mysql_error($this->database));
} }
return true; return true;
} }
@@ -152,7 +149,6 @@ class MysqlManager extends DBManager
) )
{ {
parent::countQuery($sql); parent::countQuery($sql);
$GLOBALS['log']->info('Query:' . $sql);
$this->checkConnection(); $this->checkConnection();
//$this->freeResult(); //$this->freeResult();
$this->query_time = microtime(true); $this->query_time = microtime(true);
@@ -165,8 +161,6 @@ class MysqlManager extends DBManager
$this->lastmysqlrow = -1; $this->lastmysqlrow = -1;
$this->query_time = microtime(true) - $this->query_time; $this->query_time = microtime(true) - $this->query_time;
$GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
$this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError); $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
if($autofree) if($autofree)
@@ -187,7 +181,6 @@ class MysqlManager extends DBManager
{ {
if ($start < 0) if ($start < 0)
$start = 0; $start = 0;
$GLOBALS['log']->debug('Limit Query:' . $sql. ' Start: ' .$start . ' count: ' . $count);
$sql = "$sql LIMIT $start,$count"; $sql = "$sql LIMIT $start,$count";
$this->lastsql = $sql; $this->lastsql = $sql;
@@ -225,20 +218,7 @@ class MysqlManager extends DBManager
if ( empty($badQuery) ) if ( empty($badQuery) )
return true; return true;
foreach($badQuery as $table=>$data ){
if(!empty($data)){
$warning = ' Table:' . $table . ' Data:' . $data;
if(!empty($GLOBALS['sugar_config']['check_query_log'])){
$GLOBALS['log']->fatal($sql);
$GLOBALS['log']->fatal('CHECK QUERY:' .$warning);
}
else{
$GLOBALS['log']->warn('CHECK QUERY:' .$warning);
}
}
}
return false; return false;
} }
@@ -331,7 +311,6 @@ class MysqlManager extends DBManager
public function getTablesArray() public function getTablesArray()
{ {
global $sugar_config; global $sugar_config;
$GLOBALS['log']->debug('Fetching table list');
if ($this->getDatabase()) { if ($this->getDatabase()) {
$tables = array(); $tables = array();
@@ -363,7 +342,6 @@ class MysqlManager extends DBManager
$tableName $tableName
) )
{ {
$GLOBALS['log']->info("tableExists: $tableName");
if ($this->getDatabase()) { if ($this->getDatabase()) {
$result = $this->query("SHOW TABLES LIKE '".$tableName."'"); $result = $this->query("SHOW TABLES LIKE '".$tableName."'");
@@ -441,10 +419,6 @@ class MysqlManager extends DBManager
mysql_query($charset, $this->database); // no quotes around "[charset]" mysql_query($charset, $this->database); // no quotes around "[charset]"
mysql_query("SET NAMES 'utf8'", $this->database); mysql_query("SET NAMES 'utf8'", $this->database);
if($this->checkError('Could Not Connect:', $dieOnError))
$GLOBALS['log']->info("connected to db");
$GLOBALS['log']->info("Connect:".$this->database);
} }
/** /**

View File

@@ -123,13 +123,10 @@ class MysqliManager extends MysqlManager
if (mysqli_errno($this->getDatabase())){ if (mysqli_errno($this->getDatabase())){
if($this->dieOnError || $dieOnError){ if($this->dieOnError || $dieOnError){
$GLOBALS['log']->fatal("MySQL error ".mysqli_errno($this->database).": ".mysqli_error($this->database));
sugar_die ($msg."MySQL error ".mysqli_errno($this->database).": ".mysqli_error($this->database)); sugar_die ($msg."MySQL error ".mysqli_errno($this->database).": ".mysqli_error($this->database));
} }
else{ else{
$this->last_error = $msg."MySQL error ".mysqli_errno($this->database).": ".mysqli_error($this->database); $this->last_error = $msg."MySQL error ".mysqli_errno($this->database).": ".mysqli_error($this->database);
$GLOBALS['log']->error("MySQL error ".mysqli_errno($this->database).": ".mysqli_error($this->database));
} }
return true; return true;
} }
@@ -149,7 +146,6 @@ class MysqliManager extends MysqlManager
{ {
static $queryMD5 = array(); static $queryMD5 = array();
parent::countQuery($sql); parent::countQuery($sql);
$GLOBALS['log']->info('Query:' . $sql);
$this->checkConnection(); $this->checkConnection();
//$this->freeResult(); //$this->freeResult();
$this->query_time = microtime(true); $this->query_time = microtime(true);
@@ -166,8 +162,6 @@ class MysqliManager extends MysqlManager
$this->lastmysqlrow = -1; $this->lastmysqlrow = -1;
$this->query_time = microtime(true) - $this->query_time; $this->query_time = microtime(true) - $this->query_time;
$GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
$this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError); $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
if($autofree) if($autofree)
@@ -318,9 +312,5 @@ class MysqliManager extends MysqlManager
mysqli_query($this->database,"SET CHARACTER SET utf8"); // no quotes around "[charset]" mysqli_query($this->database,"SET CHARACTER SET utf8"); // no quotes around "[charset]"
mysqli_query($this->database,"SET NAMES 'utf8'"); mysqli_query($this->database,"SET NAMES 'utf8'");
if($this->checkError('Could Not Connect:', $dieOnError))
$GLOBALS['log']->info("connected to db");
} }
} }
?>

View File

@@ -504,7 +504,6 @@ function get_user_array($add_blank = true, $status = "Active", $assigned_user =
$query .= " OR id='$assigned_user'"; $query .= " OR id='$assigned_user'";
} }
$query = $query . ' ORDER BY user_name ASC'; $query = $query . ' ORDER BY user_name ASC';
$GLOBALS['log']->debug("get_user_array query: $query");
$result = $db->query($query, true, "Error filling in user array: "); $result = $db->query($query, true, "Error filling in user array: ");
if ($add_blank == true) { if ($add_blank == true) {
@@ -623,7 +622,6 @@ function safe_map($request_var, & $focus, $always_copy = false) {
*/ */
function safe_map_named($request_var, & $focus, $member_var, $always_copy) { function safe_map_named($request_var, & $focus, $member_var, $always_copy) {
if (isset($_REQUEST[$request_var]) && ($always_copy || is_null($focus->$member_var))) { if (isset($_REQUEST[$request_var]) && ($always_copy || is_null($focus->$member_var))) {
$GLOBALS['log']->debug("safe map named called assigning '{$_REQUEST[$request_var]}' to $member_var");
$focus->$member_var = $_REQUEST[$request_var]; $focus->$member_var = $_REQUEST[$request_var];
} }
} }
@@ -688,32 +686,26 @@ function return_app_list_strings_language($language) {
if ($language_used != $default_language) { if ($language_used != $default_language) {
if (file_exists("custom/application/Ext/Language/$default_language.lang.ext.php")) { if (file_exists("custom/application/Ext/Language/$default_language.lang.ext.php")) {
$app_list_strings = _mergeCustomAppListStrings("custom/application/Ext/Language/" . $default_language . ".lang.ext.php", $app_list_strings); $app_list_strings = _mergeCustomAppListStrings("custom/application/Ext/Language/" . $default_language . ".lang.ext.php", $app_list_strings);
$GLOBALS['log']->info("Found extended language file: " . $default_language . ".lang.ext.php");
} }
if (file_exists("custom/include/language/" . $default_language . ".lang.php")) { if (file_exists("custom/include/language/" . $default_language . ".lang.php")) {
include("custom/include/language/" . $default_language . ".lang.php"); include("custom/include/language/" . $default_language . ".lang.php");
$GLOBALS['log']->info("Found custom language file: " . $default_language . ".lang.php");
} }
} }
if (file_exists("custom/application/Ext/Language/" . $language . ".lang.ext.php")) { if (file_exists("custom/application/Ext/Language/" . $language . ".lang.ext.php")) {
$app_list_strings = _mergeCustomAppListStrings("custom/application/Ext/Language/" . $language . ".lang.ext.php", $app_list_strings); $app_list_strings = _mergeCustomAppListStrings("custom/application/Ext/Language/" . $language . ".lang.ext.php", $app_list_strings);
$GLOBALS['log']->info("Found extended language file: " . $language . ".lang.ext.php");
} }
if (file_exists("custom/include/language/" . $language . ".lang.php")) { if (file_exists("custom/include/language/" . $language . ".lang.php")) {
include("custom/include/language/" . $language . ".lang.php"); include("custom/include/language/" . $language . ".lang.php");
$GLOBALS['log']->info("Found custom language file: " . $language . ".lang.php");
} }
if (!isset($app_list_strings)) { if (!isset($app_list_strings)) {
$GLOBALS['log']->warn("Unable to find the application language file for language: " . $language);
$language_used = $default_language; $language_used = $default_language;
$app_list_strings = $en_app_list_strings; $app_list_strings = $en_app_list_strings;
} }
if (!isset($app_list_strings)) { if (!isset($app_list_strings)) {
$GLOBALS['log']->fatal("Unable to load the application language file for the selected language(" . $language . ") or the default language(" . $default_language . ")");
return null; return null;
} }
//add mz 2015-01-07 //add mz 2015-01-07
@@ -824,16 +816,13 @@ function return_application_language($language) {
} }
if (file_exists("custom/application/Ext/Language/$language.lang.ext.php")) { if (file_exists("custom/application/Ext/Language/$language.lang.ext.php")) {
include("custom/application/Ext/Language/$language.lang.ext.php"); include("custom/application/Ext/Language/$language.lang.ext.php");
$GLOBALS['log']->info("Found extended language file: $language.lang.ext.php");
} }
if (file_exists("custom/include/language/$language.lang.php")) { if (file_exists("custom/include/language/$language.lang.php")) {
include("custom/include/language/$language.lang.php"); include("custom/include/language/$language.lang.php");
$GLOBALS['log']->info("Found custom language file: $language.lang.php");
} }
if (!isset($app_strings)) { if (!isset($app_strings)) {
$GLOBALS['log']->warn("Unable to find the application language file for language: " . $language);
require("include/language/$default_language.lang.php"); require("include/language/$default_language.lang.php");
if (file_exists("include/language/$default_language.lang.override.php")) { if (file_exists("include/language/$default_language.lang.override.php")) {
include("include/language/$default_language.lang.override.php"); include("include/language/$default_language.lang.override.php");
@@ -844,13 +833,11 @@ function return_application_language($language) {
if (file_exists("custom/application/Ext/Language/$default_language.lang.ext.php")) { if (file_exists("custom/application/Ext/Language/$default_language.lang.ext.php")) {
include("custom/application/Ext/Language/$default_language.lang.ext.php"); include("custom/application/Ext/Language/$default_language.lang.ext.php");
$GLOBALS['log']->info("Found extended language file: $default_language.lang.ext.php");
} }
$language_used = $default_language; $language_used = $default_language;
} }
if (!isset($app_strings)) { if (!isset($app_strings)) {
$GLOBALS['log']->fatal("Unable to load the application language file for the selected language($language) or the default language($default_language)");
return null; return null;
} }
@@ -892,7 +879,6 @@ function return_module_language($language, $module, $refresh = false) {
// Jenny - Bug 8119: Need to check if $module is not empty // Jenny - Bug 8119: Need to check if $module is not empty
if (empty($module)) { if (empty($module)) {
$stack = debug_backtrace(); $stack = debug_backtrace();
$GLOBALS['log']->warn("Variable module is not in return_module_language " . var_export($stack, true));
return array(); return array();
} }
@@ -991,7 +977,6 @@ function return_mod_list_strings_language($language, $module) {
// if we still don't have a language pack, then log an error // if we still don't have a language pack, then log an error
if (!isset($mod_list_strings)) { if (!isset($mod_list_strings)) {
$GLOBALS['log']->fatal("Unable to load the application list language file for the selected language($language) or the default language($default_language) for module({$module})");
return null; return null;
} }
@@ -1022,13 +1007,11 @@ function return_theme_language($language, $theme) {
include(SugarThemeRegistry::get($theme)->getFilePath() . "/language/$current_language.lang.php.override"); include(SugarThemeRegistry::get($theme)->getFilePath() . "/language/$current_language.lang.php.override");
} }
if (!isset($theme_strings)) { if (!isset($theme_strings)) {
$GLOBALS['log']->warn("Unable to find the theme file for language: " . $language . " and theme: " . $theme);
require(SugarThemeRegistry::get($theme)->getFilePath() . "/language/$default_language.lang.php"); require(SugarThemeRegistry::get($theme)->getFilePath() . "/language/$default_language.lang.php");
$language_used = $default_language; $language_used = $default_language;
} }
if (!isset($theme_strings)) { if (!isset($theme_strings)) {
$GLOBALS['log']->fatal("Unable to load the theme($theme) language file for the selected language($language) or the default language($default_language)");
return null; return null;
} }
@@ -1091,7 +1074,6 @@ function generate_where_statement($where_clauses) {
$where .= $clause; $where .= $clause;
} }
$GLOBALS['log']->info("Here is the where clause for the list view: $where");
return $where; return $where;
} }
@@ -1697,9 +1679,6 @@ function clean_string($str, $filter = "STANDARD") {
); );
if (preg_match($filters[$filter], $str)) { if (preg_match($filters[$filter], $str)) {
if (isset($GLOBALS['log']) && is_object($GLOBALS['log'])) {
$GLOBALS['log']->fatal("SECURITY: bad data passed in; string: {$str}");
}
die("Bad data passed in; <a href=\"{$sugar_config['site_url']}\">Return to Home</a>"); die("Bad data passed in; <a href=\"{$sugar_config['site_url']}\">Return to Home</a>");
} else { } else {
return $str; return $str;
@@ -2246,7 +2225,6 @@ function get_bean_select_array($add_blank = true, $bean_name, $display_columns,
$query .= ' order by ' . $order_by; $query .= ' order by ' . $order_by;
} }
$GLOBALS['log']->debug("get_user_array query: $query");
$result = $db->query($query, true, "Error filling in user array: "); $result = $db->query($query, true, "Error filling in user array: ");
if ($add_blank == true) { if ($add_blank == true) {
@@ -3566,7 +3544,6 @@ function getJavascriptSiteURL() {
$site_url = preg_replace('/^http\:/', 'https:', $site_url); $site_url = preg_replace('/^http\:/', 'https:', $site_url);
} }
} }
$GLOBALS['log']->debug("getJavascriptSiteURL(), site_url=" . $site_url);
return $site_url; return $site_url;
} }
@@ -4105,7 +4082,6 @@ function verify_image_file($path, $jpeg = false) {
$data = fread($fp, 4096); $data = fread($fp, 4096);
fclose($fp); fclose($fp);
if (preg_match("/<(html|!doctype|script|body|head|plaintext|table|img |pre(>| )|frameset|iframe|object|link|base|style|font|applet|meta|center|form|isindex)/i", $data, $m)) { if (preg_match("/<(html|!doctype|script|body|head|plaintext|table|img |pre(>| )|frameset|iframe|object|link|base|style|font|applet|meta|center|form|isindex)/i", $data, $m)) {
$GLOBALS['log']->info("Found {$m[0]} in $path, not allowing upload");
return false; return false;
} }
return true; return true;

View File

@@ -1,4 +1,5 @@
<?php <?php
// die('Przerwa techniczna. Michał Zieliński');
if(!defined('sugarEntry'))define('sugarEntry', true); if(!defined('sugarEntry'))define('sugarEntry', true);
$SHOW_ERRORS = false; // set to true to show errors, false to hide them $SHOW_ERRORS = false; // set to true to show errors, false to hide them
@@ -27,7 +28,7 @@ if ($SHOW_ERRORS) {
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details. * details.
*
* You should have received a copy of the GNU Affero General Public License along with * You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free * this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA

View File

@@ -1,2 +1,2 @@
<?php <?php
phpinfo(); phpinfo();

View File

@@ -1,47 +0,0 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
********************************************************************************/
$searchFields['Documents'] =
array (
'document_name' => array( 'query_type'=>'default'),
'category_id'=> array('query_type'=>'default', 'options' => 'document_category_dom', 'template_var' => 'CATEGORY_OPTIONS'),
'subcategory_id'=> array('query_type'=>'default', 'options' => 'document_subcategory_dom', 'template_var' => 'SUBCATEGORY_OPTIONS'),
'active_date'=> array('query_type'=>'default'),
'exp_date'=> array('query_type'=>'default'),
);
?>

View File

@@ -1,121 +0,0 @@
<?php
/*********************************************************************************
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
********************************************************************************/
$viewdefs['Documents']['EditView'] = array(
'templateMeta' => array('form' => array('enctype'=>'multipart/form-data',
'hidden'=>array('<input type="hidden" name="old_id" value="{$fields.document_revision_id.value}">',
'<input type="hidden" name="parent_id" value="{$smarty.request.parent_id}">',
'<input type="hidden" name="parent_type" value="{$smarty.request.parent_type}">',)),
'maxColumns' => '2',
'widths' => array(
array('label' => '10', 'field' => '30'),
array('label' => '10', 'field' => '30')
),
'javascript' => '<script type="text/javascript" src="include/javascript/popup_parent_helper.js?s={$SUGAR_VERSION}&c={$JS_CUSTOM_VERSION}"></script>
<script type="text/javascript" src="include/jsolait/init.js?s={$SUGAR_VERSION}&c={$JS_CUSTOM_VERSION}"></script>
<script type="text/javascript" src="include/jsolait/lib/urllib.js?s={$SUGAR_VERSION}&c={$JS_CUSTOM_VERSION}"></script>
<script type="text/javascript" src="include/javascript/jsclass_base.js"></script>
<script type="text/javascript" src="include/javascript/jsclass_async.js"></script>
<script type="text/javascript" src="modules/Documents/documents.js?s={$SUGAR_VERSION}&c={$JS_CUSTOM_VERSION}"></script>',
),
'panels' =>array (
'default' =>
array (
array (
'document_name',
),
array (
array('name'=>'uploadfile',
'customCode' => '<input type="hidden" name="escaped_document_name"><input name="uploadfile" type="{$FILE_OR_HIDDEN}" size="30" maxlength="" onchange="setvalue(this);" value="{$fields.filename.value}">{$fields.filename.value}',
'displayParams'=>array('required'=>true),
),
array('name'=>'revision',
'customCode' => '<input name="revision" type="text" value="{$fields.revision.value}" {$DISABLED}>'
),
),
array (
array (
'name' => 'is_template',
'label' => 'LBL_DET_IS_TEMPLATE',
),
array (
'name' => 'template_type',
'label' => 'LBL_DET_TEMPLATE_TYPE',
),
),
array (
'category_id',
'subcategory_id',
),
array (
'status_id',
),
array (
array('name'=>'active_date','displayParams'=>array('required'=>true)),
'exp_date',
),
array (
array('name'=>'related_doc_name',
'customCode' => '<input name="related_document_name" type="text" size="30" maxlength="255" value="{$RELATED_DOCUMENT_NAME}" readonly>' .
'<input name="related_doc_id" type="hidden" value="{$fields.related_doc_id.value}"/>&nbsp;' .
'<input title="{$APP.LBL_SELECT_BUTTON_TITLE}" accessKey="{$APP.LBL_SELECT_BUTTON_KEY}" type="{$RELATED_DOCUMENT_BUTTON_AVAILABILITY}" class="button" value="{$APP.LBL_SELECT_BUTTON_LABEL}" name="btn2" onclick=\'open_popup("Documents", 600, 400, "", true, false, {$encoded_document_popup_request_data}, "single", true);\'/>'),
array('name'=>'related_doc_rev_number',
'customCode' => '<select name="related_doc_rev_id" id="related_doc_rev_id" {$RELATED_DOCUMENT_REVISION_DISABLED}>{$RELATED_DOCUMENT_REVISION_OPTIONS}</select>',
),
),
array (
array('name'=>'description', 'displayParams'=>array('rows'=>10, 'cols'=>120)),
),
),
)
);
?>

View File

@@ -1,57 +0,0 @@
<?php
/*********************************************************************************
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
********************************************************************************/
$searchdefs['Documents'] = array(
'templateMeta' => array('maxColumns' => '3',
'widths' => array('label' => '10', 'field' => '30'),
),
'layout' => array(
'basic_search' => array(
'document_name',
'category_id',
'subcategory_id',
),
'advanced_search' => array(
'document_name',
'category_id',
'subcategory_id',
'active_date',
'exp_date',
),
),
);
?>

View File

@@ -1,77 +0,0 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
********************************************************************************/
global $mod_strings;
$viewdefs['Documents']['SideQuickCreate'] = array(
'templateMeta' => array('form'=>array(
'enctype'=>'multipart/form-data',
'buttons'=>array('SAVE'),
'button_location'=>'bottom',
'headerTpl'=>'include/EditView/header.tpl',
'footerTpl'=>'include/EditView/footer.tpl',
),
'maxColumns' => '1',
'panelClass'=>'none',
'labelsOnTop'=>true,
'widths' => array(
array('label' => '10', 'field' => '30'),
),
),
'panels' =>array (
'DEFAULT' =>
array (
array (
array('name'=>'document_name', 'displayParams'=>array('size'=>20, 'required'=>true)),
),
array (
array('name'=>'uploadfile', 'displayParams'=>array('size'=>11, 'required'=>true)),
),
array (
array('name'=>'revision', 'displayParams'=>array('size'=>11, 'required'=>true)),
),
array (
array('name'=>'active_date', 'displayParams'=>array('size'=>11, 'required'=>true)),
),
),
)
);
?>

View File

@@ -1,65 +0,0 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/**
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
*/
$GLOBALS['studioDefs']['Documents'] = array(
'LBL_DETAILVIEW'=>array(
'template'=>'xtpl',
'template_file'=>'modules/Documents/DetailView.html',
'php_file'=>'modules/Documents/DetailView.php',
'type'=>'DetailView',
),
'LBL_EDITVIEW'=>array(
'template'=>'xtpl',
'template_file'=>'modules/Documents/EditView.html',
'php_file'=>'modules/Documents/EditView.php',
'type'=>'EditView',
),
'LBL_LISTVIEW'=>array(
'template'=>'listview',
'meta_file'=>'modules/Documents/listviewdefs.php',
'type'=>'ListView',
),
'LBL_SEARCHFORM'=>array(
'template'=>'xtpl',
'template_file'=>'modules/Documents/SearchForm.html',
'php_file'=>'modules/Documents/ListView.php',
'type'=>'SearchForm',
),
);

View File

@@ -1,70 +0,0 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/**
* Layout definition for Quotes
*
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
*/
$layout_defs['Documents'] = array(
// list of what Subpanels to show in the DetailView
'subpanel_setup' => array(
'therevisions' => array(
'order' => 10,
'sort_order' => 'desc',
'sort_by' => 'revision',
'module' => 'DocumentRevisions',
'subpanel_name' => 'default',
'title_key' => 'LBL_DOC_REV_HEADER',
'get_subpanel_data' => 'revisions',
'fill_in_additional_fields'=>true,
),
),
);
?>

View File

@@ -1,90 +0,0 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/**
* Subpanel Layout definition for Bugs
*
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
*/
// $Id$
$subpanel_layout = array(
'top_buttons' => array(
array('widget_class' => 'SubPanelTopCreateButton'),
array('widget_class' => 'SubPanelTopSelectButton', 'popup_module' => 'Documents','field_to_name_array'=>array('document_revision_id'=>'REL_ATTRIBUTE_document_revision_id')),
),
'where' => '',
'list_fields'=> array(
'document_name'=> array(
'name' => 'document_name',
'vname' => 'LBL_LIST_DOCUMENT_NAME',
'widget_class' => 'SubPanelDetailViewLink',
'width' => '30%',
),
'is_template'=>array(
'name' => 'is_template',
'vname' => 'LBL_LIST_IS_TEMPLATE',
'width' => '5%',
'widget_type'=>'checkbox',
),
'template_type'=>array(
'name' => 'template_types',
'vname' => 'LBL_LIST_TEMPLATE_TYPE',
'width' => '15%',
),
'latest_revision'=>array(
'name' => 'latest_revision',
'vname' => 'LBL_LATEST_REVISION',
'width' => '10%',
'sortable' => false
),
'edit_button'=>array(
'widget_class' => 'SubPanelEditButton',
'module' => 'Documents',
'width' => '5%',
),
'remove_button'=>array(
'widget_class' => 'SubPanelRemoveButton',
'module' => 'Documents',
'width' => '5%',
),
'document_revision_id'=>array(
'usage'=>'query_only'
),
),
);
?>

View File

@@ -1,109 +0,0 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/**
* Subpanel Layout definition for Bugs
*
* SugarCRM is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
*/
$subpanel_layout = array(
'top_buttons' => array(
array('widget_class' => 'SubPanelTopCreateButton'),
array('widget_class' => 'SubPanelTopSelectButton', 'popup_module' => 'Documents','field_to_name_array'=>array('document_revision_id'=>'REL_ATTRIBUTE_document_revision_id')),
),
'where' => '',
'list_fields'=> array(
'object_image'=>array(
'widget_class' => 'SubPanelIcon',
'width' => '2%',
'image2'=>'attachment',
'image2_url_field'=>array('id_field'=>'selected_revision_id','filename_field'=>'selected_revision_filename'),
'attachment_image_only'=>true,
),
'document_name'=> array(
'name' => 'document_name',
'vname' => 'LBL_LIST_DOCUMENT_NAME',
'widget_class' => 'SubPanelDetailViewLink',
'width' => '30%',
),
'is_template'=>array(
'name' => 'is_template',
'vname' => 'LBL_LIST_IS_TEMPLATE',
'width' => '5%',
'widget_type'=>'checkbox',
),
'template_type'=>array(
'name' => 'template_types',
'vname' => 'LBL_LIST_TEMPLATE_TYPE',
'width' => '15%',
),
'selected_revision_name'=>array(
'name' => 'selected_revision_name',
'vname' => 'LBL_LIST_SELECTED_REVISION',
'width' => '10%',
),
'latest_revision_name'=>array(
'name' => 'latest_revision_name',
'vname' => 'LBL_LIST_LATEST_REVISION',
'width' => '10%',
),
'get_latest'=>array(
'widget_class' => 'SubPanelGetLatestButton',
'module' => 'Documents',
'width' => '5%',
),
'load_signed'=>array(
'widget_class' => 'SubPanelLoadSignedButton',
'module' => 'Documents',
'width' => '5%',
),
'edit_button'=>array(
'widget_class' => 'SubPanelEditButton',
'module' => 'Documents',
'width' => '5%',
),
'remove_button'=>array(
'widget_class' => 'SubPanelRemoveButton',
'module' => 'Documents',
'width' => '5%',
),
),
);
?>

View File

@@ -0,0 +1,80 @@
<?php
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
ini_set('display_errors', 1);
$filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/TEMU/files/10-2025.csv';
$handle = fopen($filename, 'r');
if ($handle === false) {
die('Cannot open file: ' . $filename);
}
$invoices = [];
$documentsNo = [];
$isFirstRow = true;
while (($data = fgetcsv($handle, 0, ",")) !== false) {
if ($isFirstRow) {
$isFirstRow = false;
continue;
}
for ($i = 10; $i <= 20; $i++) {
$data[$i] = str_replace(',', '.', $data[$i]);
}
if ($data[6] != '23%') {
die('VAT inny niż 23%! '.$data[6]);
}
if ($data[21] != 'PLN') {
die('Waluta inna niż PLN! '.$data[21]);
}
if (!isset($invoices[$data[22]])) {
$inv = array();
$inv['document_no'] = $data[22];
$inv['order_no'] = $data[0];
$inv['parent_name'] = 'TemuCustomer';
$inv['parent_hash'] = substr(md5($inv['parent_name']), 0, 20);
$inv['parent_short_name'] = substr($inv['parent_name'], 0, 40);
$inv['parent_address_city'] = '';
$inv['parent_address_postalcode'] = '';
$inv['parent_address_street'] = '';
$inv['parent_nip'] = '';
$inv['category'] = "Sprzedaż";
$inv['register_date'] = $data[3];
$inv['sell_date'] = $data[3];
$inv['total_netto'] = floatval($data[10]) + floatval($data[11]) + floatval($data[12]) + floatval($data[13]);
$inv['total_vat'] = floatval($data[20]);
$inv['vat_calculated'] = round($inv['total_netto'] * 0.23,2);
$inv['total_brutto'] = $inv['total_netto'] + $inv['total_vat'];
if (substr($data[22], 0, 2) == 'CN') {
$inv['type'] = 'correcting';
} else {
$inv['type'] = 'normal';
}
} else {
$inv = $invoices[$data[22]];
$inv['total_netto'] += floatval($data[10]) + floatval($data[11]) + floatval($data[12]) + floatval($data[13]);
$inv['total_vat'] += floatval($data[20]);
$inv['vat_calculated'] = round($inv['total_netto'] * 0.23,2);
$inv['total_brutto'] = $inv['total_netto'] + $inv['total_vat'];
}
$invoices[$data[22]] = $inv;
}
fclose($handle);
$db = $GLOBALS['db'];
$db->query("SET NAMES utf8mb4");
foreach ($invoices as $inv) {
$query = sprintf("INSERT INTO ecommerce_invoices VALUES ('%s', '%s', '%s', '%s', '%s', 'temu', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%f', '%f', '%f', null, null, null, null, null, null);",
getId(), $inv['document_no'], $inv['type'], $inv['register_date'], $inv['sell_date'],
$inv['order_no'], 'TemuCustomer', $inv['parent_nip'], null, null, '',
'', null, 'PLN', $inv['total_netto'], $inv['total_brutto'], $inv['total_vat']);
$res = $db->query($query);
if (!$res) {
echo "Query error: ".$query.PHP_EOL;
}
}
function getId() {
$db = $GLOBALS['db'];
$res = $db->fetchByAssoc($db->query("SELECT UUID() as id;"));
return $res['id'];
}

View File

@@ -5,11 +5,11 @@ ini_set('display_errors', 1);
/* /*
DELETE FROM ecommerce_invoices_products WHERE invoice_id IN ( DELETE FROM ecommerce_invoices_products WHERE invoice_id IN (
SELECT id FROM ecommerce_invoices WHERE origin='amazon b2b' AND register_date LIKE '2025-04-%'); SELECT id FROM ecommerce_invoices WHERE origin='amazon b2b' AND register_date LIKE '2025-09-%');
DELETE FROM ecommerce_invoices WHERE origin='amazon b2b' AND register_date LIKE '2025-04-%'; DELETE FROM ecommerce_invoices WHERE origin='amazon b2b' AND register_date LIKE '2025-09-%';
*/ */
$filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/07-2025.csv'; $filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/10-2025.csv';
$handle = fopen($filename, 'r'); $handle = fopen($filename, 'r');
if ($handle === false) { if ($handle === false) {
die('Cannot open file: ' . $filename); die('Cannot open file: ' . $filename);
@@ -38,7 +38,7 @@ foreach ($b2b as $el) {
foreach ($groupedB2B as $invoiceNo => $invoice) { foreach ($groupedB2B as $invoiceNo => $invoice) {
if (checkInvoice($invoiceNo)) { if (checkInvoice($invoiceNo)) {
echo 'Invoice already exists' . PHP_EOL; echo 'Invoice already exists: '.$invoiceNo.'<br>' ;
continue; continue;
} }

View File

@@ -1,7 +1,7 @@
<?php <?php
// https://crm.twinpol.com/?module=EcmInvoiceOuts&action=ecommerce&amazon-wz=true // https://crm.twinpol.com/?module=EcmInvoiceOuts&action=ecommerce&amazon-wz=true
$filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/amazon-wz-07-2025.csv'; $filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/amazon-wz-10-2025.csv';
$handle = fopen($filename, 'r'); $handle = fopen($filename, 'r');
if ($handle === false) { if ($handle === false) {
die('Cannot open file: ' . $filename); die('Cannot open file: ' . $filename);
@@ -18,6 +18,17 @@ while (($data = fgetcsv($handle, 0, ";")) !== false) {
if (substr($p['product_code'], -1) == '.' || substr($p['product_code'], -1) == '-') { if (substr($p['product_code'], -1) == '.' || substr($p['product_code'], -1) == '-') {
$p['product_code'] = substr($p['product_code'], 0, -1); $p['product_code'] = substr($p['product_code'], 0, -1);
} }
// ehhhhh
if ($p['product_code'] == 'FR00218_1000_amz_de') {
$p['product_code'] = 'FR00218_1000_amz_de_n';
}
if ($p['product_code'] == 'FR00225_1000_amz_de') {
$p['product_code'] = 'FR00225_1000_amz_de_n';
}
if ($p['product_code'] == 'FR00229_90_amz_de') {
$p['product_code'] = 'FR00229_90_amz_de_n';
}
// end of: ehhhhh
$p['quantity'] = floatval(str_replace(' ', '', $data[3])); $p['quantity'] = floatval(str_replace(' ', '', $data[3]));
$p['total'] = floatval(str_replace(' ', '', str_replace(',', '.', $data[5]))); $p['total'] = floatval(str_replace(' ', '', str_replace(',', '.', $data[5])));
$p['price_netto'] = round($p['total'] / $p['quantity'], 2); $p['price_netto'] = round($p['total'] / $p['quantity'], 2);

View File

@@ -9,7 +9,7 @@ DELETE FROM ecommerce_invoices_products WHERE invoice_id IN (
DELETE FROM ecommerce_invoices WHERE origin='amazon vat local' AND register_date LIKE '2025-02-%'; DELETE FROM ecommerce_invoices WHERE origin='amazon vat local' AND register_date LIKE '2025-02-%';
*/ */
$filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/07-2025.csv'; $filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/10-2025.csv';
$handle = fopen($filename, 'r'); $handle = fopen($filename, 'r');
if ($handle === false) { if ($handle === false) {
die('Cannot open file: ' . $filename); die('Cannot open file: ' . $filename);

View File

@@ -4,7 +4,7 @@ error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
ini_set('display_errors', 1); ini_set('display_errors', 1);
// read csv file tab separated // read csv file tab separated
$filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/07-2025.csv'; $filename = getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/files/10-2025.csv';
$handle = fopen($filename, 'r'); $handle = fopen($filename, 'r');
if ($handle === false) { if ($handle === false) {
die('Cannot open file: ' . $filename); die('Cannot open file: ' . $filename);
@@ -31,12 +31,12 @@ fclose($handle);
//if (count($de) == 0) { //if (count($de) == 0) {
// die('No data for DE'); // die('No data for DE');
//} //}
//createInvoice('DE', $de); //createInvoice('DE', $de, true);
//if (count($cz) == 0) { if (count($cz) == 0) {
// die('No data for CZ'); die('No data for CZ');
//} }
//createInvoice('CZ', $cz); createInvoice('CZ', $cz, true );
function createInvoice($type, $products, $DO_REAL_SAVE = false) function createInvoice($type, $products, $DO_REAL_SAVE = false)
{ {
@@ -57,7 +57,7 @@ function createInvoice($type, $products, $DO_REAL_SAVE = false)
$invoice->parent_address_city = 'Obrowo'; $invoice->parent_address_city = 'Obrowo';
$invoice->type = 'normal'; $invoice->type = 'normal';
$date = '31.07.2025'; $date = '31.10.2025';
$invoice->register_date = $date; $invoice->register_date = $date;
$invoice->sell_date = $date; $invoice->sell_date = $date;
$invoice->validtill_date = $date; $invoice->validtill_date = $date;
@@ -87,6 +87,10 @@ function createInvoice($type, $products, $DO_REAL_SAVE = false)
if ($p[10] == 'FR00148_') { if ($p[10] == 'FR00148_') {
$p[10] = 'FR00148_1000_amz_de_3pcs'; $p[10] = 'FR00148_1000_amz_de_3pcs';
} }
// (08.2025)
if ($p[10] == 'F3-W9PW-O239') {
$p[10] = 'FR00148_1000_amz_de';
}
$fromDb = $db->fetchByAssoc($db->query("SELECT id, name, code, product_category_id, unit_id FROM ecmproducts WHERE code = '$p[10]' OR amazon_code = '$p[10]'")); $fromDb = $db->fetchByAssoc($db->query("SELECT id, name, code, product_category_id, unit_id FROM ecmproducts WHERE code = '$p[10]' OR amazon_code = '$p[10]'"));
if ($fromDb == false) { if ($fromDb == false) {

View File

@@ -0,0 +1,44 @@
<?php
$id = $_REQUEST['apilo_details'];
$apilo_config = loadApiloConfiguration();
brecho('halo');
brecho(loadApiloOrder($apilo_config['token'], $id));
function loadApiloOrder($token, $orderId) {
brecho($token);
$url = "https://twinpol.apilo.com/rest/api/orders/".$orderId."/";
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json'
];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
brecho($response);
if ($httpCode !== 200) {
return false;
}
return json_decode($response);
}
function loadApiloConfiguration()
{
global $db;
$dbRes = $db->query("SELECT * FROM config WHERE category='apilo'");
$config = [];
while ($row = $db->fetchByAssoc($dbRes)) {
$config[$row['name']] = $row['value'];
}
return $config;
}
function brecho($msg)
{
echo '<br><pre>';
var_dump($msg);
echo PHP_EOL;
echo '</pre><br>';
}

View File

@@ -0,0 +1,45 @@
<?php
$id = $_REQUEST['baselinker_details'];
brecho($id);
$baselinker_config = loadConfiguration();
$order = getOrder($baselinker_config['token'], $id);
brecho($order);
function loadConfiguration()
{
global $db;
$dbRes = $db->query("SELECT * FROM config WHERE category='baselinker'");
$config = [];
while ($row = $db->fetchByAssoc($dbRes)) {
$config[$row['name']] = $row['value'];
}
return $config;
}
function getOrder($token, $orderId)
{
$methodParams = '{
"order_id": ' . $orderId . '
}';
$apiParams = [
"method" => "getOrders",
"parameters" => $methodParams
];
$curl = curl_init("https://api.baselinker.com/connector.php");
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_HTTPHEADER, ["X-BLToken: " . $token]);
curl_setopt($curl, CURLOPT_VERBOSE, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($apiParams));
$res = json_decode(curl_exec($curl));
if ($res->orders[0]->order_source != '') {
return $res->orders[0];
}
}
function brecho($msg)
{
echo '<br><pre>';
print_r($msg);
echo PHP_EOL;
echo '</pre><br>';
}

View File

@@ -0,0 +1,57 @@
<?php
echo 'halo';
$apilo_config = loadApiloConfiguration();
$products = loadApiloProducts($apilo_config['token']);
$db = $GLOBALS['db'];
foreach ($products->products as $product) {
$p = $db->fetchByAssoc($db->query(sprintf("SELECT * FROM ecmproducts WHERE code = '%s' AND deleted = 0", trim($db->quote($product->sku)))));
if (!$p) {
echo 'Brak produktu: '.$product->sku.'<br>';
}
}
echo '---------------------<br><br><br>';
brecho($products);
function loadApiloConfiguration()
{
global $db;
$dbRes = $db->query("SELECT * FROM config WHERE category='apilo'");
$config = [];
while ($row = $db->fetchByAssoc($dbRes)) {
$config[$row['name']] = $row['value'];
}
return $config;
}
function loadApiloProducts($token) {
$url = "https://twinpol.apilo.com/rest/api/warehouse/product/";
$params = [
'limit' => 300,
'offset' => 0
];
$url .= '?' . http_build_query($params);
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json'
];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 200) {
return false;
}
return json_decode($response);
}
function brecho($msg)
{
echo '<br><pre>';
var_dump($msg);
echo PHP_EOL;
echo '</pre><br>';
}

View File

@@ -106,7 +106,15 @@ function blockUI($msg) {
message: $msg message: $msg
}); });
} }
function copyToClipboard(text) { function copyToClipboard(text) {
navigator.clipboard.writeText(text); navigator.clipboard.writeText(text);
}
function openDetails(id, origin) {
window.console.log(origin);
if (origin.startsWith('Apilo')) {
window.open("index.php?module=EcmInvoiceOuts&action=ecommerce&apilo_details="+id, "_blank");
} else {
window.open("index.php?module=EcmInvoiceOuts&action=ecommerce&baselinker_details="+id, "_blank");
}
} }

View File

@@ -50,36 +50,129 @@ function show()
echo $smarty->display(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/ecommerceInvoicesListView.tpl'); echo $smarty->display(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/ecommerceInvoicesListView.tpl');
} }
function getInvoices($source, $date, $type) function getInvoices($source, $date, $type) {
{
$db = $GLOBALS['db']; $db = $GLOBALS['db'];
$date .= '%'; $date .= '%';
$query = "SELECT i.*, wz.document_no AS wz_document_no, wz.id AS wz_id FROM ecommerce_invoices AS i
LEFT JOIN ecmstockdocouts AS wz on wz.id = i.ecmstockdocout_id $query = "SELECT
"; i.id,
$query .= " WHERE i.register_date LIKE '$date'"; i.document_no,
i.type,
i.register_date,
i.sell_date,
i.origin,
i.order_no,
i.parent_name,
i.parent_nip,
i.parent_address_city,
i.parent_address_postalcode,
i.parent_address_street,
i.parent_address_country,
i.parent_address_country_code,
i.currency,
i.total_netto,
i.total_brutto,
i.total_vat,
i.url,
i.corrected_invoice_id,
i.ecmstockdocout_id,
i.ecmstockdoccorrect_id,
i.ecmsale_id,
i.series_id,
wz.document_no AS wz_document_no,
wz.id AS wz_id,
p.id as product_id,
p.code as product_code,
p.name as product_name,
ip.quantity,
ip.price_netto,
ip.price_brutto,
ip.price_vat,
ip.vat_value,
ip.code as ecommerce_code
FROM ecommerce_invoices AS i
LEFT JOIN ecmstockdocouts AS wz ON wz.id = i.ecmstockdocout_id
LEFT JOIN ecommerce_invoices_products AS ip ON i.id = ip.invoice_id
LEFT JOIN ecmproducts AS p ON ip.ecmproduct_id = p.id
WHERE i.register_date LIKE '$date'";
if ($source != '') { if ($source != '') {
if ($source == 'baselinker') { if ($source == 'baselinker') {
$query .= " AND i.origin IN ('allegro', 'shop')"; $query .= " AND i.origin IN ('allegro', 'shop')";
} else if ($source == 'apilo') {
$query .= " AND i.origin LIKE 'apilo%'";
} else { } else {
$query .= " AND i.origin = '$source'"; $query .= " AND i.origin = '" . $db->quote($source) . "'";
} }
} }
if ($type != '') { if ($type != '') {
$query .= " AND i.type='$type'"; $query .= " AND i.type = '" . $db->quote($type) . "'";
} }
$query .= " ORDER BY i.register_date"; $query .= " ORDER BY i.register_date";
$result = $db->query($query); $result = $db->query($query);
$invoices = array(); $invoices = array();
while ($row = $db->fetchByAssoc($result)) { while ($row = $db->fetchByAssoc($result)) {
// format date in row register_date from mysql format into d.m.Y if (!isset($invoices[$row['id']])) {
$row['register_date'] = date('d.m.Y', strtotime($row['register_date'])); $invoices[$row['id']] = array(
$row['sell_date'] = date('d.m.Y', strtotime($row['sell_date'])); 'id' => $row['id'],
$row['products'] = getInvoicProducts($row['id']); 'document_no' => $row['document_no'],
$invoices[] = $row; 'type' => $row['type'],
'register_date' => date('d.m.Y', strtotime($row['register_date'])),
'sell_date' => date('d.m.Y', strtotime($row['sell_date'])),
'origin' => $row['origin'],
'order_no' => $row['order_no'],
'parent_name' => $row['parent_name'],
'parent_nip' => $row['parent_nip'],
'parent_address_city' => $row['parent_address_city'],
'parent_address_postalcode' => $row['parent_address_postalcode'],
'parent_address_street' => $row['parent_address_street'],
'parent_address_country' => $row['parent_address_country'],
'parent_address_country_code' => $row['parent_address_country_code'],
'currency' => $row['currency'],
'total_netto' => $row['total_netto'],
'total_brutto' => $row['total_brutto'],
'total_vat' => $row['total_vat'],
'url' => $row['url'],
'corrected_invoice_id' => $row['corrected_invoice_id'],
'ecmstockdocout_id' => $row['ecmstockdocout_id'],
'ecmstockdoccorrect_id' => $row['ecmstockdoccorrect_id'],
'ecmsale_id' => $row['ecmsale_id'],
'series_id' => $row['series_id'],
'wz_document_no' => $row['wz_document_no'],
'wz_id' => $row['wz_id'],
'products' => array(),
'sum_by_products' => 0
);
}
if ($row['product_id']) {
$invoices[$row['id']]['products'][] = array(
'id' => $row['product_id'],
'code' => $row['product_code'],
'name' => $row['product_name'],
'price_netto' => $row['price_netto'],
'price_brutto' => $row['price_brutto'],
'quantity' => $row['quantity'],
'ecommerce_code' => $row['ecommerce_code'],
'vat_value' => str_replace(".00", "", $row['vat_value'])
);
$invoices[$row['id']]['sum_by_products'] += ($row['price_netto'] * $row['quantity']);
}
} }
return $invoices;
return array_values($invoices);
}
function getInvoiceSumByProducts($invoiceId) {
$db = $GLOBALS['db'];
$query = sprintf("SELECT SUM(ip.price_netto * ip.quantity) as sum FROM ecommerce_invoices_products as ip WHERE ip.invoice_id='%s'", $invoiceId);
$result = $db->query($query);
$row = $db->fetchByAssoc($result);
return $row['sum'];
} }
function getInvoicProducts($invoiceId) { function getInvoicProducts($invoiceId) {
$db = $GLOBALS['db']; $db = $GLOBALS['db'];
@@ -110,6 +203,7 @@ function getSources() {
while ($row = $db->fetchByAssoc($res)) { while ($row = $db->fetchByAssoc($res)) {
$sources[]= $row['origin']; $sources[]= $row['origin'];
} }
$sources[] = 'apilo';
$sources[] = 'baselinker'; $sources[] = 'baselinker';
return $sources; return $sources;
} }

View File

@@ -109,12 +109,17 @@
{$ROW.document_no} {$ROW.document_no}
{if $ROW.origin == 'amazon'} {if $ROW.origin == 'amazon'}
<img src="modules/EcmSales/images/pdf.gif" onclick="copyToClipboard('{$ROW.url}')" <img src="modules/EcmSales/images/pdf.gif" onclick="copyToClipboard('{$ROW.url}')"
style="cursor:pointer;"></a> style="cursor:pointer;"/>
{/if} {/if}
{if $ROW.origin == 'allegro'} {if $ROW.origin == 'allegro'}
<img src="modules/EcmSales/images/pdf.gif" <img src="modules/EcmSales/images/pdf.gif"
onclick="window.open('https://panel-g.baselinker.com/printouts/printout_invoices.php?id={$ROW.id}')" onclick="window.open('https://panel-g.baselinker.com/printouts/printout_invoices.php?id={$ROW.id}')"
style="cursor:pointer;" {/if} </td> style="cursor:pointer;"/>
{/if}
<img src="modules/EcmSales/images/convert.gif"
onclick="openDetails('{$ROW.order_no}', '{$ROW.origin}')"
style="cursor:pointer;"/>
</td>
<td> <td>
<a target="_blank" <a target="_blank"
href="index.php?module=EcmStockDocOuts&action=DetailView&record={$ROW.wz_id}">{$ROW.wz_document_no}</a> href="index.php?module=EcmStockDocOuts&action=DetailView&record={$ROW.wz_id}">{$ROW.wz_document_no}</a>
@@ -149,7 +154,12 @@
{$ROW.currency} {$ROW.currency}
</td> </td>
<td> <td>
{$ROW.total_netto} {$ROW.total_netto} <br>
{$ROW.sum_by_products}
{math equation="abs(x-y)" x=$ROW.total_netto y=$ROW.sum_by_products assign="price_difference"}
{if $price_difference > 0.02}
<span style="color: red; font-weight: bold; cursor: pointer" onclick="openDetails('{$ROW.order_no}', '{$ROW.origin}')">!!!</span>
{/if}
</td> </td>
<td> <td>
{$ROW.total_brutto} {$ROW.total_brutto}

View File

@@ -0,0 +1,334 @@
<?php
// enable error reporting
//error_reporting(LC_ALL);
//ini_set('display_errors', 1);
// ?XDEBUG_SESSION_START=PHPSTORM
//apilo_importInvoices();
function apilo_importInvoices()
{
$apilo_config = apilo_loadConfiguration();
$db = $GLOBALS['db'];
$dbRes = $db->query("SELECT COUNT(id) as last_id FROM ecommerce_invoices WHERE origin LIKE 'apilo%'");
$offset = intval($db->fetchByAssoc($dbRes)['last_id']);
$invoices = apilo_loadInvoices($apilo_config['token'], $offset);
if (isset($invoices->error)) {
if (apilo_refreshToken($apilo_config['refreshToken'], $apilo_config['clientId'], $apilo_config['clientSecret']) == true) {
$apilo_config = apilo_loadConfiguration();
$invoices = apilo_loadInvoices($apilo_config['token'], $offset);
} else {
return false;
}
}
brecho(count($invoices->documents));
$GLOBALS['log']->bimit('----- Importing invoices from Apilo, documents count', count($invoices->documents));
$platforms = apilo_loadPlatformsList($apilo_config['token']);
if (!$platforms) {
return false;
}
if (isset($invoices->documents) && count($invoices->documents) > 0) {
foreach ($invoices->documents as $invoice) {
apilo_addInvoice($invoice, $platforms, $apilo_config['token']);
}
}
$GLOBALS['log']->bimit('----- Importing invoices from Apilo, all processed, return TRUE');
return true;
}
function apilo_loadConfiguration()
{
$db = $GLOBALS['db'];
$dbRes = $db->query("SELECT * FROM config WHERE category='apilo'");
$config = [];
while ($row = $db->fetchByAssoc($dbRes)) {
$config[$row['name']] = $row['value'];
}
return $config;
}
function apilo_loadInvoices($token, $offset)
{
$url = "https://twinpol.apilo.com/rest/api/finance/documents/";
$params = [
'type' => 1,
'limit' => 100,
'offset' => $offset
];
$url .= '?' . http_build_query($params);
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json'
];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 200) {
return (object)['error' => 'HTTP Error: ' . $httpCode];
}
return json_decode($response);
}
function apilo_refreshToken($refreshToken, $clientId, $clientSecret)
{
$url = "https://twinpol.apilo.com/rest/auth/token/";
$data = [
'grantType' => 'refresh_token',
'token' => $refreshToken,
];
$headers = [
'Authorization: Basic ' . base64_encode($clientId . ':' . $clientSecret),
'Content-Type: application/json',
'Accept: application/json'
];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$err = curl_error($curl);
curl_close($curl);
if ($httpCode !== 201) {
return false;
}
$tokenData = json_decode($response, true);
var_dump($tokenData);
if (isset($tokenData['accessToken'])) {
global $db;
$db->query("UPDATE config SET value='" . $tokenData['accessToken'] . "' WHERE category='apilo' AND name='token'");
if (isset($tokenData['refreshToken'])) {
$db->query("UPDATE config SET value='" . $tokenData['refreshToken'] . "' WHERE category='apilo' AND name='refreshToken'");
}
return true;
}
return false;
}
function apilo_addInvoice($invoice, $platforms, $token)
{
$db = $GLOBALS['db'];
$platformId = apilo_loadOrderPlatformId($token, $invoice->orderId);
if (!$platformId) {
return false;
}
$platformDescription = 'ERROR';
foreach ($platforms as $platform) {
if ($platform->id == $platformId) {
$platformDescription = $platform->description;
break;
}
}
$orderSource = 'Apilo | '.$platformDescription;
$invoiceType = 'normal';
if (isset($invoice->type)) {
switch ($invoice->type) {
case 31:
$invoiceType = 'correcting';
break;
case 1:
default:
$invoiceType = 'normal';
break;
}
}
$customerName = '';
$customerNip = '';
$customerCity = '';
$customerPostcode = '';
$customerAddress = '';
$customerCountry = '';
$customerCountryCode = '';
if (isset($invoice->documentReceiver)) {
$customer = $invoice->documentReceiver;
$customerName = isset($customer->companyName) ? $customer->companyName : $customer->name;
if (isset($customer->companyTaxNumber)) {
$customerNip = $customer->companyTaxNumber;
}
$customerCity = isset($customer->city) ? $customer->city : '';
$customerPostcode = isset($customer->zipCode) ? $customer->zipCode : '';
$customerAddress = isset($customer->streetName) ? $customer->streetName : '';
if (isset($customer->streetNumber)) {
$customerAddress .= ' ' . $customer->streetNumber;
}
$customerCountry = isset($customer->country) ? $customer->country : '';
$customerCountryCode = isset($customer->country) ? $customer->country : '';
}
$totalNetto = isset($invoice->originalAmountTotalWithoutTax) ? floatval($invoice->originalAmountTotalWithoutTax) : 0;
$totalBrutto = isset($invoice->originalAmountTotalWithTax) ? floatval($invoice->originalAmountTotalWithTax) : 0;
$totalVat = $totalBrutto - $totalNetto;
$currency = isset($invoice->originalCurrency) ? $invoice->originalCurrency : 'PLN';
$issueDate = isset($invoice->invoicedAt) ? date("Y-m-d", strtotime(substr($invoice->invoicedAt, 0, 10))) : date("Y-m-d");
$saleDate = isset($invoice->soldAt) ? date("Y-m-d", strtotime(substr($invoice->soldAt,0,10))) : $issueDate;
$correctedInvoiceId = ''; //isset($invoice->original_invoice_id) ? $invoice->original_invoice_id : '';
$db->query("SET NAMES utf8mb4");
$query = sprintf(
"INSERT INTO ecommerce_invoices VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%f', '%f', '%f', '%s', '%s', null, null, null, null);",
$db->quote($invoice->id),
$db->quote($invoice->documentNumber),
$invoiceType,
$issueDate,
$saleDate,
$orderSource,
isset($invoice->orderId) ? $invoice->orderId : '',
$db->quote($customerName),
$customerNip,
$db->quote($customerCity),
$customerPostcode,
$db->quote($customerAddress),
$db->quote($customerCountry),
$customerCountryCode,
$currency,
$totalNetto,
$totalBrutto,
$totalVat,
'',
$correctedInvoiceId
);
$db->query($query);
if ($db->last_error) {
return false;
}
if (isset($invoice->documentItems) && is_array($invoice->documentItems)) {
$db->query(sprintf("DELETE FROM ecommerce_invoices_products WHERE invoice_id='%s'", $db->quote($invoice->id)));
foreach ($invoice->documentItems as $item) {
apilo_addInvoiceProduct($invoice->id, $item);
}
}
return true;
}
function apilo_addInvoiceProduct($invoiceId, $item)
{
$db = $GLOBALS['db'];
$productId = '';
if (isset($item->sku) && !empty($item->sku)) {
$productResult = $db->query(sprintf("SELECT id FROM ecmproducts WHERE code = '%s'", trim($db->quote($item->sku))));
$productRow = $db->fetchByAssoc($productResult);
if ($productRow) {
$productId = $productRow['id'];
}
} else { // shipping
$productId = '165f364e-9301-25ac-5906-58e38f1de4ca';
}
$uuid = sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
);
$quantity = isset($item->quantity) ? intval($item->quantity) : 1;
$priceNetto = isset($item->originalPriceWithoutTax) ? floatval($item->originalPriceWithoutTax) : 0;
$priceBrutto = isset($item->originalPriceWithTax) ? floatval($item->originalPriceWithTax) : 0;
$priceVat = $priceBrutto - $priceNetto;
$taxRate = isset($item->tax) ? floatval($item->tax) : 0;
$sku = isset($item->sku) ? $item->sku : $item->name;;
$query = sprintf(
"INSERT INTO ecommerce_invoices_products VALUES('%s', '%s', '%s', '%s', '%d', '%f', '%f', '%f', '%f', '%s')",
$uuid,
$productId,
$db->quote($invoiceId),
isset($item->id) ? $db->quote($item->id) : '',
$quantity,
$priceNetto,
$priceBrutto,
$priceVat,
$taxRate,
$db->quote($sku)
);
$db->query($query);
if ($db->last_error) {
return false;
}
}
function apilo_loadPlatformsList($token) {
$url = "https://twinpol.apilo.com/rest/api/orders/platform/map/";
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json'
];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 200) {
return false;
}
return json_decode($response);
}
function apilo_loadOrderPlatformId($token, $orderId) {
$url = "https://twinpol.apilo.com/rest/api/orders/".$orderId."/";
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: application/json'
];
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($httpCode !== 200) {
return false;
}
return json_decode($response)->platformAccountId;
}
function brecho($msg)
{
echo '<br><pre>';
var_dump($msg);
echo PHP_EOL;
echo '</pre><br>';
}

View File

@@ -12,6 +12,7 @@ FROM ecommerce_invoices_products p
LEFT JOIN ecommerce_invoices i ON p.invoice_id = i.id LEFT JOIN ecommerce_invoices i ON p.invoice_id = i.id
WHERE i.id IS NULL; WHERE i.id IS NULL;
*/ */
//importFVKOR('7689');
function importFV($seriesId) function importFV($seriesId)
{ {
$baselinker_config = loadConfiguration(); $baselinker_config = loadConfiguration();
@@ -32,6 +33,8 @@ function importFV($seriesId)
$invoices = $invoicesRes->invoices; $invoices = $invoicesRes->invoices;
brecho($invoices);
usort($invoices, function ($a, $b) { usort($invoices, function ($a, $b) {
return $a->date_add - $b->date_add; return $a->date_add - $b->date_add;
}); });
@@ -54,7 +57,7 @@ function importFVKOR($seriesId)
$IMPORT_CORRECTION_START_ID = 2106464; $IMPORT_CORRECTION_START_ID = 2106464;
$dbRes = $db->query("SELECT id FROM ecommerce_invoices WHERE origin = 'Allegro' AND type = 'correcting' AND series_id = '$seriesId' ORDER BY id DESC LIMIT 1"); $dbRes = $db->query("SELECT id FROM ecommerce_invoices WHERE type = 'correcting' AND series_id = '$seriesId' ORDER BY CAST(id AS UNSIGNED) DESC LIMIT 1");
$lastImportId = $db->fetchByAssoc($dbRes)['id']; $lastImportId = $db->fetchByAssoc($dbRes)['id'];
if ($lastImportId == null) { if ($lastImportId == null) {
$lastImportId = $IMPORT_CORRECTION_START_ID; $lastImportId = $IMPORT_CORRECTION_START_ID;
@@ -62,10 +65,13 @@ function importFVKOR($seriesId)
$lastImportId++; //get next $lastImportId++; //get next
} }
brecho($lastImportId);
$invoicesRes = loadInvoices($baselinker_config['token'], $lastImportId, $seriesId); $invoicesRes = loadInvoices($baselinker_config['token'], $lastImportId, $seriesId);
$invoices = $invoicesRes->invoices; $invoices = $invoicesRes->invoices;
brecho($invoices);
usort($invoices, function ($a, $b) { usort($invoices, function ($a, $b) {
return $a->date_add - $b->date_add; return $a->date_add - $b->date_add;
}); });
@@ -118,6 +124,7 @@ function addInvoice($i)
if ($db->last_error) { if ($db->last_error) {
brecho($db->last_error);
return; return;
} }
// delete products for this invoice if exists // delete products for this invoice if exists

View File

@@ -0,0 +1,164 @@
<?php
$APP_KEY = '02cbeff8afd35247294810033b036cfe';
$APP_SECRET = '8ecfa21b55cddf66c66043cbb80756efc4ba6596';
$ACCESS_TOKEN = 'eplugtijmiyppv7ctra6iu7sjbgbvnlixcunsrigd918tl5awdl3ugmxno7';
$BASE_URL = 'https://openapi-b-eu.temu.com/openapi/router';
$dateFromStr = '2025-10-01';
$dateToStr = '2025-10-31';
$parentOrderStatus = 4;
$pageSize = 50;
function noquote($v){
$j = json_encode($v, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
return (is_string($v) && $j[0]==='"' && substr($j,-1)==='"') ? substr($j,1,-1) : $j;
}
function sign_md5($p, $secret){
ksort($p, SORT_STRING);
$s = $secret;
foreach($p as $k=>$v){ if($v!=='' && $v!==null) $s .= $k . noquote($v); }
$s .= $secret;
return strtoupper(md5($s));
}
function parse_date_to_ts_start($s){
$s = trim($s);
$fmts = array('Y-m-d','d.m.Y','Y/m/d','d-m-Y','d/m/Y');
foreach ($fmts as $f){
$dt = DateTime::createFromFormat($f, $s);
if ($dt && $dt->format($f) === $s){
$dt->setTime(0,0,0);
return (int)$dt->format('U');
}
}
// fallback
$t = strtotime($s);
if ($t !== false){ return (int)strtotime(date('Y-m-d 00:00:00', $t)); }
return null;
}
function parse_date_to_ts_end($s){
$ts = parse_date_to_ts_start($s);
if ($ts === null) return null;
return $ts + 86399; // 23:59:59
}
$fromTs = parse_date_to_ts_start($dateFromStr);
$toTs = parse_date_to_ts_end($dateToStr);
if ($fromTs === null || $toTs === null){
die('<pre>Błędny format daty od/do</pre>');
}
$totalMatched = 0;
$page = 1;
$maxPages = 500; // bezpiecznik
echo '<div style="font-family:monospace; font-size:13px">';
while ($page <= $maxPages) {
// 1) request listy
$biz = array(
"type" => "bg.order.list.v2.get",
//"regionId" => 162,
"pageNumber" => $page,
"pageSize" => $pageSize,
// "parentOrderStatus" => $parentOrderStatus
);
$common = array(
'app_key' => $APP_KEY,
'access_token' => $ACCESS_TOKEN,
'data_type' => 'JSON',
'timestamp' => (int)floor(microtime(true)),
);
$req = $biz + $common;
$req['sign'] = sign_md5($req, $APP_SECRET);
$payload = json_encode($req, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $BASE_URL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => array('Content-Type: application/json'),
CURLOPT_POSTFIELDS => $payload
));
$resp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$cerr = curl_error($ch);
curl_close($ch);
var_dump($resp);
if ($resp === false) {
echo "<pre>cURL error: " . htmlspecialchars($cerr, ENT_QUOTES, 'UTF-8') . "</pre>";
break;
}
$data = json_decode($resp, true);
if (!is_array($data) || !isset($data['result']['pageItems']) || !is_array($data['result']['pageItems'])) {
break;
}
$items = $data['result']['pageItems'];
if (!count($items)) {
break;
}
usort($items, function($a, $b){
$ta = isset($a['parentOrderMap']['parentOrderTime']) ? (int)$a['parentOrderMap']['parentOrderTime'] : 0;
$tb = isset($b['parentOrderMap']['parentOrderTime']) ? (int)$b['parentOrderMap']['parentOrderTime'] : 0;
if ($ta == $tb) return 0;
return ($ta > $tb) ? -1 : 1;
});
$minTsOnPage = null;
foreach ($items as $row) {
if (!isset($row['parentOrderMap']['parentOrderSn'])) continue;
$parentOrderSn = $row['parentOrderMap']['parentOrderSn'];
$orderTs = isset($row['parentOrderMap']['parentOrderTime']) ? (int)$row['parentOrderMap']['parentOrderTime'] : 0;
if ($minTsOnPage === null || $orderTs < $minTsOnPage) $minTsOnPage = $orderTs;
if ($orderTs >= $fromTs && $orderTs <= $toTs) {
// trafienie — drukujemy wybrane pola
$issueHuman = $orderTs ? date('Y-m-d H:i:s', $orderTs) : '(brak)';
echo '<hr style="margin:16px 0;border:0;border-top:2px solid #999;">';
echo '<div><b>Numer zamówienia:</b> ' . htmlspecialchars($parentOrderSn, ENT_QUOTES, 'UTF-8') . '</div>';
echo '<div><b>Status:</b> ' . htmlspecialchars($row['parentOrderMap']['parentOrderStatus'], ENT_QUOTES, 'UTF-8') . '</div>';
echo '<div><b>Data wystawienia:</b> ' . htmlspecialchars($issueHuman, ENT_QUOTES, 'UTF-8')
. ' <span style="color:#888">(epoch: ' . (int)$orderTs . ')</span></div>';
echo '<div style="margin-top:6px;"><b>Produkty (extCode, soldFactor):</b></div>';
if (isset($row['orderList']) && is_array($row['orderList']) && count($row['orderList'])>0) {
echo '<ul style="margin:4px 0 10px 20px; padding:0;">';
foreach ($row['orderList'] as $ol) {
if (!isset($ol['productList']) || !is_array($ol['productList'])) continue;
foreach ($ol['productList'] as $p) {
$extCode = isset($p['extCode']) ? $p['extCode'] : '';
$soldFactor = isset($p['soldFactor']) ? $p['soldFactor'] : '';
echo '<li>extCode: <code>' . htmlspecialchars($extCode, ENT_QUOTES, 'UTF-8') . '</code>'
. ', soldFactor: <code>' . htmlspecialchars($soldFactor, ENT_QUOTES, 'UTF-8') . '</code></li>';
}
}
echo '</ul>';
} else {
echo '<div style="color:#b00">brak productList w orderList</div>';
}
$totalMatched++;
}
}
if ($minTsOnPage !== null && $minTsOnPage < $fromTs) {
break;
}
$page++;
}
echo '<hr style="margin:16px 0;border:0;border-top:2px solid #999;">';
echo '<div><b>Łącznie dopasowanych zamówień:</b> ' . (int)$totalMatched . '</div>';
echo '<div><b>Zakres dat:</b> ' . htmlspecialchars(date('Y-m-d H:i:s', $fromTs), ENT_QUOTES, 'UTF-8')
. ' — ' . htmlspecialchars(date('Y-m-d H:i:s', $toTs), ENT_QUOTES, 'UTF-8') . '</div>';
echo '</div>';

View File

@@ -1,108 +1,60 @@
<?php <?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point'); if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/***************************************************************************** /*****************************************************************************
* The contents of this file are subject to the RECIPROCAL PUBLIC LICENSE * The contents of this file are subject to the RECIPROCAL PUBLIC LICENSE
* Version 1.1 ("License"); You may not use this file except in compliance * Version 1.1 ("License"); You may not use this file except in compliance
* with the License. You may obtain a copy of the License at * with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/rpl.php. Software distributed under the * http://opensource.org/licenses/rpl.php. Software distributed under the
* License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, * License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
* either express or implied. * either express or implied.
* *
* You may: * You may:
* a) Use and distribute this code exactly as you received without payment or * a) Use and distribute this code exactly as you received without payment or
* a royalty or other fee. * a royalty or other fee.
* b) Create extensions for this code, provided that you make the extensions * b) Create extensions for this code, provided that you make the extensions
* publicly available and document your modifications clearly. * publicly available and document your modifications clearly.
* c) Charge for a fee for warranty or support or for accepting liability * c) Charge for a fee for warranty or support or for accepting liability
* obligations for your customers. * obligations for your customers.
* *
* You may NOT: * You may NOT:
* a) Charge for the use of the original code or extensions, including in * a) Charge for the use of the original code or extensions, including in
* electronic distribution models, such as ASP (Application Service * electronic distribution models, such as ASP (Application Service
* Provider). * Provider).
* b) Charge for the original source code or your extensions other than a * b) Charge for the original source code or your extensions other than a
* nominal fee to cover distribution costs where such distribution * nominal fee to cover distribution costs where such distribution
* involves PHYSICAL media. * involves PHYSICAL media.
* c) Modify or delete any pre-existing copyright notices, change notices, * c) Modify or delete any pre-existing copyright notices, change notices,
* or License text in the Licensed Software * or License text in the Licensed Software
* d) Assert any patent claims against the Licensor or Contributors, or * d) Assert any patent claims against the Licensor or Contributors, or
* which would in any way restrict the ability of any third party to use the * which would in any way restrict the ability of any third party to use the
* Licensed Software. * Licensed Software.
* *
* You must: * You must:
* a) Document any modifications you make to this code including the nature of * a) Document any modifications you make to this code including the nature of
* the change, the authors of the change, and the date of the change. * the change, the authors of the change, and the date of the change.
* b) Make the source code for any extensions you deploy available via an * b) Make the source code for any extensions you deploy available via an
* Electronic Distribution Mechanism such as FTP or HTTP download. * Electronic Distribution Mechanism such as FTP or HTTP download.
* c) Notify the licensor of the availability of source code to your extensions * c) Notify the licensor of the availability of source code to your extensions
* and include instructions on how to acquire the source code and updates. * and include instructions on how to acquire the source code and updates.
* d) Grant Licensor a world-wide, non-exclusive, royalty-free license to use, * d) Grant Licensor a world-wide, non-exclusive, royalty-free license to use,
* reproduce, perform, modify, sublicense, and distribute your extensions. * reproduce, perform, modify, sublicense, and distribute your extensions.
* *
* The Original Code is: CommuniCore * The Original Code is: CommuniCore
* Olavo Farias * Olavo Farias
* 2006-04-7 olavo.farias@gmail.com * 2006-04-7 olavo.farias@gmail.com
* *
* The Initial Developer of the Original Code is CommuniCore. * The Initial Developer of the Original Code is CommuniCore.
* Portions created by CommuniCore are Copyright (C) 2005 CommuniCore Ltda * Portions created by CommuniCore are Copyright (C) 2005 CommuniCore Ltda
* All Rights Reserved. * All Rights Reserved.
********************************************************************************/ ********************************************************************************/
global $mod_strings, $current_user; global $mod_strings, $current_user;
if(ACLController::checkAccess('EcmInvoiceOuts', "edit", true)) $module_menu [] = Array("index.php?module=".'EcmInvoiceOuts'."&action=EditView&return_module=".'EcmInvoiceOuts'."&return_action=DetailView", translate('LNK_NEW_'.'ECMQUOTE', 'EcmInvoiceOuts'),"CreateEcmInvoiceOuts", 'EcmInvoiceOuts'); if (ACLController::checkAccess('EcmInvoiceOuts', "edit", true)) $module_menu [] = array("index.php?module=" . 'EcmInvoiceOuts' . "&action=EditView&return_module=" . 'EcmInvoiceOuts' . "&return_action=DetailView", translate('LNK_NEW_' . 'ECMQUOTE', 'EcmInvoiceOuts'), "CreateEcmInvoiceOuts", 'EcmInvoiceOuts');
if(ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = Array("index.php?module=EcmInvoiceOuts&action=index&return_module=EcmInvoiceOuts&return_action=DetailView", translate('LNK_ECMQUOTES_LIST','EcmInvoiceOuts'),"EcmInvoiceOuts", 'EcmInvoiceOuts'); if (ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = array("index.php?module=EcmInvoiceOuts&action=index&return_module=EcmInvoiceOuts&return_action=DetailView", translate('LNK_ECMQUOTES_LIST', 'EcmInvoiceOuts'), "EcmInvoiceOuts", 'EcmInvoiceOuts');
if(ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = Array("index.php?module=EcmInvoiceOuts&action=Report_INTRASTAT", "Raport INTRASTAT","EcmInvoiceOuts", 'EcmInvoiceOuts'); if (ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = array("index.php?module=EcmInvoiceOuts&action=Report_INTRASTAT", "Raport INTRASTAT", "EcmInvoiceOuts", 'EcmInvoiceOuts');
if (ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = array("index.php?module=EcmInvoiceOuts&action=ecommerce", "Faktury E-Commerce", "EcmInvoiceOuts", 'EcmInvoiceOuts');
if(ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = Array("index.php?module=EcmInvoiceOuts&action=ecommerce", "Faktury E-Commerce","EcmInvoiceOuts", 'EcmInvoiceOuts'); if (ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = array("index.php?module=EcmInvoiceOuts&action=bimit_invoiceSummary", "Analiza faktur", "EcmInvoiceOuts", 'EcmInvoiceOuts');
if(ACLController::checkAccess('EcmInvoiceOuts', "list", true)) $module_menu [] = Array("index.php?module=EcmInvoiceOuts&action=bimit_invoiceSummary", "Analiza faktur","EcmInvoiceOuts", 'EcmInvoiceOuts');

View File

@@ -1,713 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
analysisAI.py — pobiera dane z MySQL, liczy wyłącznie WSKAZANE preagregaty,
renderuje HTML i (opcjonalnie) dodaje analizę AI — tylko jeśli ją zaznaczysz.
Parametry CLI (z formularza PHP):
--date-from YYYY-MM-DD
--date-to YYYY-MM-DD (zamieniane wewnętrznie na +1 dzień, bo SQL ma warunek '< date_to')
--metric NAZWA (można podać wiele razy: --metric a --metric b ...)
--metrics CSV (opcjonalnie alternatywnie: --metrics a,b,c)
--ai true|false (czy uruchomić analizę AI — tylko gdy są preagregaty z danymi)
Preagregaty:
- kpis (aliasy: basic, basic_totals) — podstawowe KPI: sprzedaż, ilość, dokumenty, ASP
- daily_sales, product_summary, customer_summary, product_daily,
top10_products_by_sales, top10_customers_by_sales (z preaggregates.py)
"""
import os, sys, json, math, time, warnings, argparse, traceback, html
from datetime import date, timedelta, datetime
# (1) Wycisza ostrzeżenia urllib3 (LibreSSL / stary OpenSSL)
try:
from urllib3.exceptions import NotOpenSSLWarning
warnings.filterwarnings("ignore", category=NotOpenSSLWarning)
except Exception:
pass
# (2) Importy zewnętrzne
import requests
import mysql.connector
import pandas as pd
LOOKER_URL = "https://lookerstudio.google.com/u/0/reporting/107d4ccc-e7eb-4c38-8dce-00700b44f60e/page/ba1YF"
# ========== KONFIGURACJA KLUCZA AI ==========
API_KEY = "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"
API_KEY_HARDCODE = API_KEY
# === Import preagregatów ===
from preaggregates import serialize_for_ai
import preaggregates as pre # pre.AGGREGATORS, pre.to_df
# ========== UTILKI ==========
def html_fatal(msg, title="Błąd"):
sys.stdout.write(
'<div style="font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;'
'max-width:900px;margin:24px auto;padding:16px 20px;border:1px solid #fecaca;'
'border-radius:12px;background:#fff5f5;color:#991b1b;">'
f'<h3 style="margin:0 0 8px;font-size:18px;">{html.escape(title)}</h3>'
f'<pre style="white-space:pre-wrap;margin:0;">{html.escape(msg)}</pre>'
'</div>'
)
sys.exit(1)
def connect_html_or_die(cfg, label="MySQL"):
try:
return mysql.connector.connect(**cfg)
except mysql.connector.Error as e:
host = cfg.get("host"); port = cfg.get("port"); user = cfg.get("user")
base = (f"[{label}] Błąd połączenia ({host}:{port} jako '{user}').\n"
f"errno={getattr(e,'errno',None)} sqlstate={getattr(e,'sqlstate',None)}\n"
f"msg={getattr(e,'msg',str(e))}")
if os.environ.get("DEBUG"):
base += "\n\n" + traceback.format_exc()
html_fatal(base, title="Błąd połączenia MySQL")
def getenv(k, d=None):
return os.environ.get(k, d)
def last_full_month_bounds():
today_first = date.today().replace(day=1)
to_dt = today_first
prev_last = today_first - timedelta(days=1)
from_dt = prev_last.replace(day=1)
return from_dt.isoformat(), to_dt.isoformat()
def add_one_day(iso_date):
try:
return (datetime.strptime(iso_date, "%Y-%m-%d") + timedelta(days=1)).strftime("%Y-%m-%d")
except Exception:
return iso_date # w razie czego oddaj wejście
def safe_num(v, ndigits=None):
try:
f = float(v)
if not math.isfinite(f):
return None
return round(f, ndigits) if ndigits is not None else f
except Exception:
return None
def safe_date(v):
if v is None:
return None
try:
if hasattr(v, "date"):
return str(v.date())
s = str(v)
if len(s) >= 10 and s[4] == '-' and s[7] == '-':
return s[:10]
return s
except Exception:
return None
def fmt_money(v):
try:
return "{:,.2f}".format(float(v)).replace(",", " ").replace(".", ",")
except Exception:
return str(v)
def compact_table(table, limit=30):
out = []
if not table:
return out
lim = int(limit)
for i, row in enumerate(table):
if i >= lim: break
new = {}
for k, v in row.items():
if isinstance(v, float):
new[k] = round(v, 6) if math.isfinite(v) else None
else:
new[k] = v
out.append(new)
return out
def call_openai_chat(api_key, model, system_prompt, user_payload_json,
temperature=0.3, connect_timeout=10, read_timeout=90, max_retries=3):
url = "https://api.openai.com/v1/chat/completions"
headers = {"Authorization": "Bearer " + api_key, "Content-Type": "application/json"}
body = {
"model": model,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": "Dane (JSON):\n\n" + user_payload_json},
],
"temperature": temperature,
}
last_err = None
for attempt in range(1, int(max_retries) + 1):
try:
r = requests.post(url, headers=headers, json=body, timeout=(connect_timeout, read_timeout))
if 200 <= r.status_code < 300:
data = r.json()
return data.get("choices", [{}])[0].get("message", {}).get("content", "")
last_err = RuntimeError("OpenAI HTTP {}: {}".format(r.status_code, r.text))
except requests.exceptions.RequestException as e:
last_err = e
time.sleep(min(2 ** attempt, 10))
raise RuntimeError("OpenAI request failed: {}".format(last_err))
def html_table(records, title=None, max_rows=20):
if not records:
return '<div class="empty">Brak danych</div>'
cols = list(records[0].keys())
body_rows = records[:max_rows]
thead = "".join("<th>{}</th>".format(c) for c in cols)
trs = []
for r in body_rows:
tds = []
for c in cols:
val = r.get(c, "")
if isinstance(val, (int, float)):
if any(x in c.lower() for x in ("sales", "total", "netto", "value", "asp", "qty", "quantity", "share", "change")):
tds.append('<td class="num">{}</td>'.format(fmt_money(val) if "sales" in c.lower() or "total" in c.lower() or "netto" in c.lower() else val))
else:
tds.append('<td class="num">{}</td>'.format(val))
else:
tds.append('<td>{}</td>'.format(val))
trs.append("<tr>{}</tr>".format("".join(tds)))
cap = '<div class="tbl-title">{}</div>'.format(title) if title else ""
return (
cap +
'<div class="tbl-wrap"><table class="tbl">'
'<thead><tr>{}</tr></thead><tbody>{}</tbody></table></div>'.format(thead, "".join(trs))
)
def render_report_html(period_label, kpis, parts, ai_section, model_alias):
css = (
"font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;"
"max-width:1200px;margin:24px auto;padding:16px 20px;border:1px solid #e5e7eb;"
"border-radius:12px;background:#fff;color:#111827"
)
kpi_item = (
'<div class="kpi"><div class="kpi-label">{label}</div>'
'<div class="kpi-value">{value}</div></div>'
)
kpi_html = "".join(kpi_item.format(label=lbl, value=val) for (lbl, val) in kpis)
sections_html = "".join(parts)
if ai_section and not ai_section.lstrip().startswith("<div"):
ai_section = '<div class="ai-section">{}</div>'.format(ai_section)
return f"""
<div style="{css}">
<h2 style="margin:0 0 12px;font-size:22px;">Raport sprzedaży — {period_label}</h2>
<div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin:12px 0 20px;">
{kpi_html}
</div>
{sections_html if sections_html.strip() else '<div class="empty">Nie wybrano żadnych preagregatów — brak sekcji do wyświetlenia.</div>'}
<div style="margin-top:20px;border-top:1px solid #e5e7eb;padding-top:16px;">
<h3 style="margin:0 0 8px;font-size:18px;">Analiza i rekomendacje{(' (AI · ' + model_alias + ')') if model_alias else ''}</h3>
{ai_section if ai_section else '<div style="color:#6b7280">Analiza AI wyłączona lub brak danych.</div>'}
</div>
<!-- STOPKA z linkiem do Looker Studio -->
<div style="margin-top:20px;border-top:1px dashed #e5e7eb;padding-top:12px;display:flex;justify-content:flex-end;">
<a href="{LOOKER_URL}" target="_blank" rel="noopener"
style="text-decoration:none;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;
background:#f9fafb;color:#111827;font-weight:600;">
→ Otwórz pełny raport w Looker Studio
</a>
</div>
</div>
<style>
.kpi {{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:12px;}}
.kpi-label {{font-size:12px;color:#6b7280;margin-bottom:4px;}}
.kpi-value {{font-size:18px;font-weight:700;}}
.tbl-title {{font-weight:600;margin:16px 0 8px;font-size:15px;}}
.tbl-wrap {{overflow-x:auto;border:1px solid #e5e7eb;border-radius:8px;}}
.tbl {{border-collapse:collapse;width:100%;font-size:14px;}}
.tbl thead th {{text-align:left;background:#f3f4f6;padding:8px;border-bottom:1px solid #e5e7eb;white-space:nowrap;}}
.tbl tbody td {{padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;}}
.tbl td.num {{text-align:right;white-space:nowrap;}}
.empty {{color:#6b7280;font-style:italic;margin:8px 0;}}
.ai-section {{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:12px;}}
</style>
"""
# ========== UPSerTY DO REPORTING (jak u Ciebie) ==========
def _ensure_rank_and_share(items, key_sales="sales"):
if not items: return
total_sales = sum((x.get(key_sales) or 0) for x in items)
sorted_items = sorted(
items,
key=lambda x: ((x.get(key_sales) or 0), str(x.get("product_code") or x.get("customer_name") or "")),
reverse=True
)
rank_map, rank = {}, 1
for x in sorted_items:
key = x.get("product_code") or x.get("customer_name") or ""
if key not in rank_map:
rank_map[key] = rank
rank += 1
for x in items:
key = x.get("product_code") or x.get("customer_name") or ""
if not x.get("rank_in_period"):
x["rank_in_period"] = rank_map.get(key, 0)
if "mix_share_sales" not in x:
x["mix_share_sales"] = ((x.get(key_sales) or 0) / total_sales) if total_sales else 0.0
def upsert_daily_sales(cur, daily):
if not daily: return 0
sql = """
INSERT INTO reporting_daily_sales
(period_date, qty, sales, docs, asp, sales_rolling7, sales_dod_pct)
VALUES (%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
qty=VALUES(qty), sales=VALUES(sales), docs=VALUES(docs),
asp=VALUES(asp), sales_rolling7=VALUES(sales_rolling7), sales_dod_pct=VALUES(sales_dod_pct),
generated_at=CURRENT_TIMESTAMP
"""
rows = []
for r in daily:
period_date = safe_date(r.get("register_date") or r.get("period_date") or r.get("date"))
rows.append((
period_date,
safe_num(r.get("qty")),
safe_num(r.get("sales")),
safe_num(r.get("docs")),
safe_num(r.get("asp"), 6),
safe_num(r.get("sales_rolling7"), 6),
safe_num(r.get("sales_pct_change_dod") or r.get("sales_dod_pct"), 6),
))
cur.executemany(sql, rows)
return len(rows)
def upsert_product_summary(cur, prod, period_from, period_to):
if not prod: return 0
_ensure_rank_and_share(prod, key_sales="sales")
sql = """
INSERT INTO reporting_product_summary
(period_start, period_end, product_code, product_name, qty, sales, docs,
asp_weighted, mix_share_sales, rank_in_period)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
qty=VALUES(qty), sales=VALUES(sales), docs=VALUES(docs),
asp_weighted=VALUES(asp_weighted), mix_share_sales=VALUES(mix_share_sales),
rank_in_period=VALUES(rank_in_period), generated_at=CURRENT_TIMESTAMP
"""
rows = []
for r in prod:
rows.append((
period_from, period_to,
r.get("product_code"), r.get("product_name"),
safe_num(r.get("qty")),
safe_num(r.get("sales")),
safe_num(r.get("docs")),
safe_num(r.get("asp_weighted"), 6),
safe_num(r.get("mix_share_sales"), 6),
int(r.get("rank_in_period") or 0),
))
cur.executemany(sql, rows)
return len(rows)
def upsert_customer_summary(cur, cust, period_from, period_to):
if not cust: return 0
_ensure_rank_and_share(cust, key_sales="sales")
sql = """
INSERT INTO reporting_customer_summary
(period_start, period_end, customer_name, qty, sales, docs,
asp_weighted, mix_share_sales, rank_in_period)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
qty=VALUES(qty), sales=VALUES(sales), docs=VALUES(docs),
asp_weighted=VALUES(asp_weighted), mix_share_sales=VALUES(mix_share_sales),
rank_in_period=VALUES(rank_in_period), generated_at=CURRENT_TIMESTAMP
"""
rows = []
for r in cust:
rows.append((
period_from, period_to,
r.get("customer_name"),
safe_num(r.get("qty")),
safe_num(r.get("sales")),
safe_num(r.get("docs")),
safe_num(r.get("asp_weighted"), 6),
safe_num(r.get("mix_share_sales"), 6),
int(r.get("rank_in_period") or 0),
))
cur.executemany(sql, rows)
return len(rows)
def upsert_product_daily(cur, prod_daily):
if not prod_daily: return 0
sql = """
INSERT INTO reporting_product_daily
(period_date, product_code, product_name, qty, sales, asp)
VALUES (%s,%s,%s,%s,%s,%s)
ON DUPLICATE KEY UPDATE
qty=VALUES(qty), sales=VALUES(sales), asp=VALUES(asp),
generated_at=CURRENT_TIMESTAMP
"""
rows = []
for r in prod_daily:
period_date = safe_date(r.get("register_date") or r.get("period_date") or r.get("date"))
qty = safe_num(r.get("qty"))
sales = safe_num(r.get("sales"))
asp = safe_num((sales / qty) if (qty and sales is not None and qty != 0) else r.get("asp"), 6)
rows.append((
period_date,
r.get("product_code"),
r.get("product_name"),
qty, sales, asp
))
cur.executemany(sql, rows)
return len(rows)
# ========== ARGPARSE & LOGIKA WYBORU ==========
def parse_cli_args():
p = argparse.ArgumentParser()
p.add_argument('--date-from', dest='date_from', required=False, help='YYYY-MM-DD')
p.add_argument('--date-to', dest='date_to', required=False, help='YYYY-MM-DD (inclusive, we add +1 day internally)')
# akceptuj obie formy: wielokrotne --metric oraz (opcjonalnie) --metrics CSV
p.add_argument('--metric', dest='metric', action='append', default=[], help='Nazwa preagregatu; można podać wiele razy')
p.add_argument('--metrics', dest='metrics', action='append', default=[], help='CSV: a,b,c (można podać wiele razy)')
p.add_argument('--ai', dest='ai', choices=['true','false'], default='false')
return p.parse_args()
def collect_metric_names(args):
names = []
# z --metric (powtarzalne)
if args.metric:
names.extend([s.strip() for s in args.metric if s and s.strip()])
# z --metrics (może być kilka wystąpień; każde może być CSV)
for entry in (args.metrics or []):
if not entry:
continue
for part in str(entry).replace(';', ',').replace(' ', ',').split(','):
part = part.strip()
if part:
names.append(part)
# aliasy dla kpis
alias_map = {'basic': 'kpis', 'basic_totals': 'kpis'}
names = [alias_map.get(n, n) for n in names]
# deduplikacja z zachowaniem kolejności
seen = set()
uniq = []
for n in names:
if n not in seen:
seen.add(n)
uniq.append(n)
return uniq
def compute_selected_preaggs(rows, names):
"""
Liczy TYLKO wskazane preagregaty. ZAWSZE zwraca DataFrame'y (nigdy listy).
Obsługuje pseudo-agregat 'kpis' (podstawowe KPI).
"""
results = {}
if not names:
return results
df = pre.to_df(rows)
# kpis — pseudoagregat
def compute_kpis_df(dfx):
if dfx is None or dfx.empty:
return pd.DataFrame([{
"total_sales": 0.0,
"total_qty": 0.0,
"total_docs": 0,
"asp": None,
}])
total_sales = float(dfx["total_netto"].sum())
total_qty = float(dfx["quantity"].sum())
total_docs = int(dfx["document_no"].nunique())
asp = (total_sales / total_qty) if total_qty else None
return pd.DataFrame([{
"total_sales": total_sales,
"total_qty": total_qty,
"total_docs": total_docs,
"asp": asp,
}])
for name in names:
if name == 'kpis':
results[name] = compute_kpis_df(df)
continue
fn = pre.AGGREGATORS.get(name)
if not fn:
results[name] = pd.DataFrame() # nieznany agregat -> pusty
continue
try:
out = fn(df)
if out is None:
results[name] = pd.DataFrame()
elif hasattr(out, "copy"):
results[name] = out.copy()
else:
results[name] = pd.DataFrame(out)
except Exception:
# np. top10_* na pustych danych -> zwróć pusty wynik
results[name] = pd.DataFrame()
return results
def sanitize_serialized(serialized_dict):
"""
Jeśli jakikolwiek agregat zwrócił błąd (np. _error), zamieniamy na pustą listę.
"""
clean = {}
for k, records in (serialized_dict or {}).items():
if not records:
clean[k] = []
continue
if isinstance(records, list) and isinstance(records[0], dict) and ('_error' in records[0]):
clean[k] = []
else:
clean[k] = records
return clean
def has_any_rows(serialized_dict):
for records in (serialized_dict or {}).values():
if records: # lista niepusta
return True
return False
# ========== MAIN ==========
def main():
# --- CLI ---
args = parse_cli_args()
with_ai = (args.ai == 'true')
metric_names = collect_metric_names(args)
# --- Daty: preferuj CLI; 'date_to' inkluzywne (dodajemy +1 dzień dla SQL '<') ---
if args.date_from and args.date_to:
period_from, period_to = args.date_from, add_one_day(args.date_to)
shown_label = "{} .. {}".format(args.date_from, args.date_to)
else:
env_from, env_to = getenv("PERIOD_FROM"), getenv("PERIOD_TO")
if env_from and env_to:
period_from, period_to = env_from, env_to
# label dla czytelności: to-1d
try:
to_label = (datetime.strptime(period_to, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
except Exception:
to_label = period_to
shown_label = "{} .. {}".format(period_from, to_label)
else:
period_from, period_to = last_full_month_bounds()
# label: poprzedni pełny miesiąc
try:
to_label = (datetime.strptime(period_to, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d")
except Exception:
to_label = period_to
shown_label = "{} .. {}".format(period_from, to_label)
# --- DB ---
cfg = {
"host": getenv("MYSQL_HOST", "twinpol-mysql56"),
"user": getenv("MYSQL_USER", "root"),
"password": getenv("MYSQL_PASSWORD", "rootpassword"),
"database": getenv("MYSQL_DATABASE", "preDb_0dcc87940d3655fa574b253df04ca1c3"),
"port": int(getenv("MYSQL_PORT", "3306")),
}
invoice_type = getenv("INVOICE_TYPE", "normal")
# --- SQL -> rows (UWZGLĘDNIJ DATY; typ wg ENV) ---
try:
cnx = mysql.connector.connect(**cfg)
cur = cnx.cursor()
if invoice_type:
cur.execute(
"""
SELECT i.document_no,
i.parent_name,
DATE(i.register_date) AS register_date,
ii.code,
ii.name,
ii.quantity,
ii.total_netto
FROM ecminvoiceoutitems AS ii
JOIN ecminvoiceouts AS i ON i.id = ii.ecminvoiceout_id
WHERE i.register_date >= %s
AND i.register_date < %s
AND i.type = %s
""",
(period_from, period_to, invoice_type),
)
else:
cur.execute(
"""
SELECT i.document_no,
i.parent_name,
DATE(i.register_date) AS register_date,
ii.code,
ii.name,
ii.quantity,
ii.total_netto
FROM ecminvoiceoutitems AS ii
JOIN ecminvoiceouts AS i ON i.id = ii.ecminvoiceout_id
WHERE i.register_date >= %s
AND i.register_date < %s
""",
(period_from, period_to),
)
rows = cur.fetchall()
cur.close()
cnx.close()
except Exception as e:
html_fatal(str(e), title="Błąd połączenia/zapytania MySQL")
# --- LICZ TYLKO WYBRANE PREAGREGATY (w tym pseudo 'kpis') ---
results = {}
serialized = {}
if metric_names:
results = compute_selected_preaggs(rows, metric_names)
serialized = serialize_for_ai(results)
serialized = sanitize_serialized(serialized) # usuń ewentualne _error -> traktuj jako puste
else:
serialized = {}
# --- ZAPIS do reporting (tylko to, co faktycznie policzyłeś) ---
try:
if serialized:
rep_cfg = {
"host": "host.docker.internal",
"port": 3307,
"user": "remote",
"password": os.environ.get("REPORTING_PASSWORD", "areiufh*&^yhdua"),
"database": "ai",
}
if os.environ.get("REPORTING_SSL_CA"):
rep_cfg["ssl_ca"] = os.environ["REPORTING_SSL_CA"]
if os.environ.get("REPORTING_SSL_CERT"):
rep_cfg["ssl_cert"] = os.environ["REPORTING_SSL_CERT"]
if os.environ.get("REPORTING_SSL_KEY"):
rep_cfg["ssl_key"] = os.environ["REPORTING_SSL_KEY"]
cnx2 = connect_html_or_die(rep_cfg, label="ReportingDB")
cur2 = cnx2.cursor()
if "daily_sales" in serialized:
upsert_daily_sales(cur2, serialized.get("daily_sales") or [])
if "product_summary" in serialized:
upsert_product_summary(cur2, serialized.get("product_summary") or [], period_from, period_to)
if "customer_summary" in serialized:
upsert_customer_summary(cur2, serialized.get("customer_summary") or [], period_from, period_to)
if "product_daily" in serialized:
upsert_product_daily(cur2, serialized.get("product_daily") or [])
cnx2.commit()
cur2.close(); cnx2.close()
except Exception as e:
sys.stderr.write(f"[reporting] ERROR: {e}\n")
# --- KPI: jeśli wybrano 'kpis' -> bierz z wyników; w przeciwnym razie spróbuj z daily_sales; inaczej zera ---
kpis = []
if "kpis" in results and isinstance(results["kpis"], pd.DataFrame) and not results["kpis"].empty:
r = results["kpis"].iloc[0]
total_sales = r.get("total_sales") or 0
total_qty = r.get("total_qty") or 0
total_docs = r.get("total_docs") or 0
asp = r.get("asp")
else:
daily = serialized.get("daily_sales") or []
total_sales = sum((x.get("sales") or 0) for x in daily) if daily else 0
total_qty = sum((x.get("qty") or 0) for x in daily) if daily else 0
total_docs = sum((x.get("docs") or 0) for x in daily) if daily else 0
asp = (total_sales / total_qty) if total_qty else None
kpis = [
("Sprzedaż (PLN)", fmt_money(total_sales)),
("Ilość (szt.)", "{:,.0f}".format(total_qty).replace(",", " ")),
("Dokumenty", "{:,.0f}".format(total_docs).replace(",", " ")),
("ASP (PLN/szt.)", fmt_money(asp) if asp is not None else ""),
]
# --- Sekcje HTML: renderuj tylko te, które policzyłeś ---
parts = []
if "top10_products_by_sales" in serialized:
parts.append(html_table(serialized.get("top10_products_by_sales") or [], title="Top 10 produktów (po sprzedaży)", max_rows=10))
if "top10_customers_by_sales" in serialized:
parts.append(html_table(serialized.get("top10_customers_by_sales") or [], title="Top 10 klientów (po sprzedaży)", max_rows=10))
if "daily_sales" in serialized:
parts.append(html_table(serialized.get("daily_sales") or [], title="Sprzedaż dzienna (skrót)", max_rows=30))
if "product_summary" in serialized:
parts.append(html_table(serialized.get("product_summary") or [], title="Podsumowanie produktów (skrót)", max_rows=30))
if "customer_summary" in serialized:
parts.append(html_table(serialized.get("customer_summary") or [], title="Podsumowanie klientów (skrót)", max_rows=30))
if "product_daily" in serialized:
parts.append(html_table(serialized.get("product_daily") or [], title="Produkt × Dzień (próbka)", max_rows=30))
# --- AI tylko gdy: --ai true ORAZ jest co najmniej jeden rekord w którymś z wybranych agregatów ---
api_key = API_KEY_HARDCODE or getenv("OPENAI_API_KEY", "")
model = getenv("OPENAI_MODEL", "gpt-4.1")
MODEL_ALIAS = {
"gpt-4.1": "GPT-4.1",
"gpt-4.1-mini": "GPT-4.1-mini",
"gpt-4o": "GPT-4o",
"gpt-4o-mini": "GPT-4o-mini",
}
model_alias = MODEL_ALIAS.get(model, model)
ai_section = ""
if with_ai and has_any_rows(serialized):
try:
ai_data = {"kpis_hint": {"period_label": shown_label}}
for name, records in serialized.items():
ai_data[name] = compact_table(records, 100)
ai_json = json.dumps(ai_data, ensure_ascii=False, separators=(",", ":"), default=str)
ai_section = call_openai_chat(
api_key=(api_key or ""),
model=model,
system_prompt=("Jesteś analitykiem sprzedaży. Zwróć TYLKO jedną sekcję HTML (bez <html>/<head>/<body>). "
"Streszcz kluczowe trendy i daj 36 zaleceń. Po polsku."),
user_payload_json=ai_json,
temperature=0.3,
connect_timeout=10,
read_timeout=90,
max_retries=3,
)
except Exception as e:
err = str(e)
if "insufficient_quota" in err or "You exceeded your current quota" in err:
try:
ai_section = call_openai_chat(
api_key=(api_key or ""),
model="gpt-4.1-mini",
system_prompt=("Jesteś analitykiem sprzedaży. Zwróć TYLKO jedną sekcję HTML (bez <html>/<head>/<body>). "
"Streszcz kluczowe trendy i daj 36 zaleceń. Po polsku."),
user_payload_json=ai_json,
temperature=0.3,
connect_timeout=10,
read_timeout=90,
max_retries=2,
)
model_alias = "GPT-4.1-mini"
except Exception as ee:
ai_section = (
'<div style="color:#991b1b;background:#fff5f5;border:1px solid #fecaca;'
'padding:10px;border-radius:8px;">Brak dostępnego limitu API. {}</div>'.format(str(ee))
)
else:
ai_section = (
'<div style="color:#991b1b;background:#fff5f5;border:1px solid #fecaca;'
'padding:10px;border-radius:8px;">Błąd wywołania AI: {}</div>'.format(err)
)
else:
ai_section = '<div style="color:#6b7280">Analiza AI wyłączona lub brak wybranych danych.</div>'
model_alias = ""
# --- Finalny HTML ---
report_html = render_report_html(
period_label=shown_label,
kpis=kpis,
parts=parts,
ai_section=ai_section,
model_alias=(model_alias if (with_ai and has_any_rows(serialized)) else "")
)
sys.stdout.write(report_html)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,21 @@
<?php
// modules/EcmInvoiceOuts/ai/enqueue.php
$from = $_POST['from'] ?? null;
$to = $_POST['to'] ?? null;
$currency = $_POST['currency'] ?? 'PLN';
$axis = $_POST['axis'] ?? 'sku_id';
$label = $_POST['label'] ?? 'sku_name';
$top_n = (int)($_POST['top_n'] ?? 50);
$goal = $_POST['goal'] ?? 'porównanie Q2 vs Q1';
if (!$from || !$to) { http_response_code(400); exit('Missing from/to'); }
$base = __DIR__;
@mkdir("$base/queue", 0777, true);
$payload = compact('from','to','currency','axis','label','top_n','goal');
$id = bin2hex(random_bytes(8));
file_put_contents("$base/queue/$id.json", json_encode($payload, JSON_UNESCAPED_UNICODE));
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['job_id' => $id]);

View File

@@ -1,150 +0,0 @@
# --- preagg.py ---------------------------------------------------------------
from __future__ import annotations
import pandas as pd
import numpy as np
from typing import Callable, Dict, List
# Rejestr agregatorów: name -> funkcja(df) -> DataFrame
# AGGREGATORS: Dict[str, Callable[[pd.DataFrame], pd.DataFrame]] = {}
AGGREGATORS = {}
def aggregator(name: str):
"""Dekorator do łatwego rejestrowania nowych agregatorów."""
def _wrap(func: Callable[[pd.DataFrame], pd.DataFrame]):
AGGREGATORS[name] = func
return func
return _wrap
def to_df(rows: List[tuple]) -> pd.DataFrame:
"""Konwersja rows -> DataFrame (dopasuj nazwy kolumn do SELECT-a)."""
cols = [
"document_no",
"customer_name", # i.parent_name
"register_date", # DATE(i.register_date)
"product_code", # ii.code
"product_name", # ii.name
"quantity", # ii.quantity
"total_netto", # ii.total_netto (wartość sprzedaży netto)
]
df = pd.DataFrame(rows, columns=cols)
if df.empty:
return df
# Typy
df["register_date"] = pd.to_datetime(df["register_date"])
df["quantity"] = pd.to_numeric(df["quantity"], errors="coerce").fillna(0.0)
df["total_netto"] = pd.to_numeric(df["total_netto"], errors="coerce").fillna(0.0)
# ASP (Average Selling Price) średnia cena pozycji
# Uwaga: ASP lepiej liczyć ważoną średnią w agregatach; tu to „unit price” na pozycji.
df["unit_price"] = np.where(df["quantity"] != 0, df["total_netto"] / df["quantity"], np.nan)
return df
# ------------------- Wbudowane agregatory (możesz dopisywać kolejne) -------------------
@aggregator("daily_sales")
def daily_sales(df: pd.DataFrame) -> pd.DataFrame:
"""Dzienna sprzedaż: ilość, wartość, liczba dokumentów, ASP ważony."""
if df.empty:
return df
g = df.groupby(pd.Grouper(key="register_date", freq="D"))
out = g.agg(
qty=("quantity", "sum"),
sales=("total_netto", "sum"),
docs=("document_no", "nunique"),
).reset_index()
# ASP ważony (sales / qty)
out["asp"] = np.where(out["qty"] != 0, out["sales"] / out["qty"], np.nan)
# Zmiana d/d
out["sales_pct_change_dod"] = out["sales"].pct_change()
# Rolling 7
out["sales_rolling7"] = out["sales"].rolling(7, min_periods=1).mean()
return out
@aggregator("product_summary")
def product_summary(df: pd.DataFrame) -> pd.DataFrame:
"""Podsumowanie po produkcie."""
if df.empty:
return df
g = df.groupby(["product_code", "product_name"], as_index=False).agg(
qty=("quantity", "sum"),
sales=("total_netto", "sum"),
docs=("document_no", "nunique"),
)
g["asp_weighted"] = np.where(g["qty"] != 0, g["sales"] / g["qty"], np.nan)
# Udział w koszyku (mix % po wartości)
total_sales = g["sales"].sum()
g["mix_share_sales"] = np.where(total_sales > 0, g["sales"] / total_sales, 0.0)
return g.sort_values("sales", ascending=False)
@aggregator("customer_summary")
def customer_summary(df: pd.DataFrame) -> pd.DataFrame:
"""Podsumowanie po kliencie."""
if df.empty:
return df
g = df.groupby(["customer_name"], as_index=False).agg(
qty=("quantity", "sum"),
sales=("total_netto", "sum"),
docs=("document_no", "nunique"),
distinct_products=("product_code", "nunique"),
)
g["asp_weighted"] = np.where(g["qty"] != 0, g["sales"] / g["qty"], np.nan)
return g.sort_values("sales", ascending=False)
@aggregator("product_daily")
def product_daily(df: pd.DataFrame) -> pd.DataFrame:
"""Dzienna sprzedaż per produkt (przydatne do trendów/rollingów w AI)."""
if df.empty:
return df
g = (df
.groupby([pd.Grouper(key="register_date", freq="D"), "product_code", "product_name"], as_index=False)
.agg(qty=("quantity", "sum"),
sales=("total_netto", "sum")))
# Rolling 7 per produkt
g = g.sort_values(["product_code", "register_date"])
g["sales_rolling7"] = g.groupby("product_code")["sales"].transform(lambda s: s.rolling(7, min_periods=1).mean())
g["sales_pct_change_dod"] = g.groupby("product_code")["sales"].pct_change()
return g
@aggregator("top10_products_by_sales")
def top10_products_by_sales(df: pd.DataFrame) -> pd.DataFrame:
"""Top 10 produktów po wartości sprzedaży (okres z wejścia)."""
base = AGGREGATORS["product_summary"](df)
return base.nlargest(10, "sales")
@aggregator("top10_customers_by_sales")
def top10_customers_by_sales(df: pd.DataFrame) -> pd.DataFrame:
"""Top 10 klientów po wartości sprzedaży."""
base = AGGREGATORS["customer_summary"](df)
return base.nlargest(10, "sales")
# ------------------- Runner -------------------
def compute_preaggregates(rows: List[tuple]) -> dict[str, pd.DataFrame]:
#def compute_preaggregates(rows):
"""Główny punkt wejścia: rows -> df -> uruchom wszystkie agregatory."""
df = to_df(rows)
# results: dict[str, pd.DataFrame] = {}
results = {}
for name, fn in AGGREGATORS.items():
try:
results[name] = fn(df).copy()
except Exception as e:
# Niech agregat nie wysadza całości zapisz pusty DF + info
results[name] = pd.DataFrame({"_error": [str(e)], "_aggregator": [name]})
return results
def serialize_for_ai(results: dict[str, pd.DataFrame]) -> dict[str, list[dict]]:
"""
Konwersja wyników do lekkiego JSON-a (listy rekordów),
który łatwo przekazać do modelu AI lub zapisać do pliku.
"""
# out: dict[str, list[dict]] = {}
out = {}
for name, df in results.items():
if df is None or df.empty:
out[name] = []
else:
# zaokrąglij liczby dla czytelności (opcjonalnie)
df2 = df.copy()
for c in df2.select_dtypes(include=[np.number]).columns:
df2[c] = df2[c].round(6)
out[name] = df2.to_dict(orient="records")
return out

View File

@@ -0,0 +1,12 @@
<?php
// modules/EcmInvoiceOuts/ai/result.php
$base = __DIR__;
$files = glob("$base/out/*.json");
rsort($files);
$latest = $files[0] ?? null;
if (!$latest) { http_response_code(404); exit('Brak wyników'); }
$payload = json_decode(file_get_contents($latest), true);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import os
import sys
try:
import mysql.connector
except Exception as e:
sys.stderr.write("MySQL connector not available: %s\n" % e)
sys.exit(1)
def getenv(key, default=None):
return os.environ.get(key, default)
def main():
cfg = {
"host": getenv("MYSQL_HOST", "twinpol-mysql56"),
"user": getenv("MYSQL_USER", "root"),
"password": getenv("MYSQL_PASSWORD", "rootpassword"),
"database": getenv("MYSQL_DATABASE", "preDb_0dcc87940d3655fa574b253df04ca1c3"),
"port": int(getenv("MYSQL_PORT", "3306")),
}
try:
cnx = mysql.connector.connect(**cfg)
cur = cnx.cursor()
cur.execute("SELECT COUNT(*) FROM ecminvoiceouts WHERE YEAR(register_date)=2025")
row = cur.fetchone()
count = int(row[0]) if row and row[0] is not None else 0
print(count)
cur.close()
cnx.close()
except Exception as e:
sys.stderr.write("Query error: %s\n" % e)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,141 @@
# worker.py
import os, json, io, uuid
import datetime as dt
from typing import Dict, Any, List
import polars as pl
import pymysql
from tenacity import retry, wait_exponential, stop_after_attempt
from dotenv import load_dotenv
load_dotenv()
AI_MODEL = os.getenv("AI_MODEL", "gpt-5-pro")
AI_API_KEY = os.getenv("AI_API_KEY")
MYSQL_CONF = dict(
host=os.getenv("MYSQL_HOST", "localhost"),
user=os.getenv("MYSQL_USER", "root"),
password=os.getenv("MYSQL_PASSWORD", ""),
database=os.getenv("MYSQL_DB", "sales"),
cursorclass=pymysql.cursors.DictCursor,
)
def mysql_query(sql: str, params: tuple = ()) -> pl.DataFrame:
conn = pymysql.connect(**MYSQL_CONF)
try:
with conn.cursor() as cur:
cur.execute(sql, params)
rows = cur.fetchall()
finally:
conn.close()
return pl.from_dicts(rows)
def to_csv(df: pl.DataFrame) -> str:
buf = io.StringIO()
df.write_csv(buf)
return buf.getvalue()
SQL_KPIS_DAILY = """
SELECT DATE(invoice_date) AS d,
SUM(net_amount) AS revenue,
SUM(quantity) AS qty,
ROUND(100*SUM(net_amount - cost_amount)/NULLIF(SUM(net_amount),0), 2) AS gross_margin_pct,
ROUND(100*SUM(discount_amount)/NULLIF(SUM(gross_amount),0), 2) AS discount_pct
FROM fact_invoices
WHERE invoice_date BETWEEN %s AND %s
GROUP BY 1
ORDER BY 1;
"""
SQL_TOP_SEGMENTS = """
SELECT {axis} AS key,
ANY_VALUE({label}) AS label,
SUM(net_amount) AS revenue,
SUM(quantity) AS qty,
ROUND(100*SUM(net_amount - cost_amount)/NULLIF(SUM(net_amount),0), 2) AS gross_margin_pct,
ROUND(100*(SUM(net_amount) - LAG(SUM(net_amount)) OVER(ORDER BY 1))/
NULLIF(LAG(SUM(net_amount)) OVER(ORDER BY 1),0), 2) AS trend_30d
FROM fact_invoices
WHERE invoice_date BETWEEN DATE_SUB(%s, INTERVAL 60 DAY) AND %s
GROUP BY 1
ORDER BY revenue DESC
LIMIT %s;
"""
class AIClient:
def __init__(self, api_key: str): self.api_key = api_key
@retry(wait=wait_exponential(multiplier=1, min=1, max=20), stop=stop_after_attempt(6))
def structured_analysis(self, prompt: str, schema: Dict[str, Any]) -> Dict[str, Any]:
# TODO: PODMIEŃ na realne wywołanie modelu z "Structured Outputs"
raise NotImplementedError("Wire your model SDK here")
@retry(wait=wait_exponential(multiplier=1, min=1, max=20), stop=stop_after_attempt(6))
def batch_submit(self, ndjson_lines: List[str]) -> str:
# TODO: PODMIEŃ na rzeczywiste Batch API
raise NotImplementedError
def run_online(from_date: str, to_date: str, currency: str, axis: str, label: str, top_n: int, goal: str) -> Dict[str, Any]:
kpis = mysql_query(SQL_KPIS_DAILY, (from_date, to_date))
top = mysql_query(SQL_TOP_SEGMENTS.format(axis=axis, label=label), (from_date, to_date, top_n))
csv_blocks = ("## kpis_daily\n" + to_csv(kpis) + "\n\n" +
"## top_segments\n" + to_csv(top))
with open(os.path.join(os.path.dirname(__file__), "sales-analysis.schema.json"), "r", encoding="utf-8") as f:
schema = json.load(f)
prompt = f"""
Jesteś analitykiem sprzedaży. Otrzymasz: (a) kontekst, (b) dane.
Zwróć **wyłącznie** JSON zgodny ze schema.
Kontekst:
- Waluta: {currency}
- Zakres: {from_date}{to_date}
- Cel: {goal}
- Poziom segmentacji: {axis}
Dane (CSV):
{csv_blocks}
Wskazówki:
- Użyj danych jak są (nie wymyślaj liczb).
- W meta.scope wpisz opis zakresu i segmentacji.
- Jeśli brak anomalii anomalies: [].
- Kwoty do 2 miejsc, procenty do 1.
"""
ai = AIClient(AI_API_KEY)
result = ai.structured_analysis(prompt, schema)
out_dir = os.path.join(os.path.dirname(__file__), "out")
os.makedirs(out_dir, exist_ok=True)
out_path = os.path.join(out_dir, f"{uuid.uuid4()}.json")
with open(out_path, "w", encoding="utf-8") as f:
json.dump(result, f, ensure_ascii=False)
return {"status": "ok", "path": out_path}
def run_batch(from_date: str, to_date: str, axis: str, label: str):
# Zgodnie z blueprintem generujemy linie NDJSON (skrót; pełny wariant w PDF)
# TODO: dodać realne wywołania batch_submit i zapisać ID/stan
raise NotImplementedError("Implement batch per blueprint")
if __name__ == "__main__":
import argparse
p = argparse.ArgumentParser()
sub = p.add_subparsers(dest="cmd")
o = sub.add_parser("online")
o.add_argument("from_date"); o.add_argument("to_date"); o.add_argument("currency")
o.add_argument("axis", choices=["sku_id","client_id","region_code"])
o.add_argument("label"); o.add_argument("top_n", type=int, nargs="?", default=50)
o.add_argument("goal")
b = sub.add_parser("batch")
b.add_argument("from_date"); b.add_argument("to_date"); b.add_argument("axis"); b.add_argument("label")
args = p.parse_args()
if args.cmd == "online":
print(run_online(args.from_date, args.to_date, args.currency, args.axis, args.label, args.top_n, args.goal))
elif args.cmd == "batch":
print(run_batch(args.from_date, args.to_date, args.axis, args.label))
else:
p.print_help()

View File

@@ -16,7 +16,19 @@ if (isset($_REQUEST['import_baselinker'])) {
} else if (isset($_REQUEST['wnt'])) { } else if (isset($_REQUEST['wnt'])) {
include_once(getcwd().'/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/wntReader.php'); include_once(getcwd().'/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/wntReader.php');
} else if (isset($_REQUEST['amazon-wz'])) { } else if (isset($_REQUEST['amazon-wz'])) {
include_once(getcwd().'/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/amazonWZ.php'); include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/amazonWDT/amazonWZ.php');
} else if (isset($_REQUEST['import_apilo'])) {
include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/importApiloInvoices.php');
} else if (isset($_REQUEST['import_temu'])) {
include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/importTEMUInvoices.php');
} else if (isset($_REQUEST['apilo_details'])) {
include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/apiloInvoiceDetails.php');
} else if (isset($_REQUEST['baselinker_details'])) {
include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/baselinkerInvoiceDetails.php');
} else if (isset($_REQUEST['apilo_products'])) {
include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/checkApiloProducts.php');
} else if (isset($_REQUEST['temu'])) {
include_once(getcwd() . '/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/TEMU/temuInvoicesListView.php');
} else { } else {
include_once(getcwd().'/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/ecommerceInvoicesListView.php'); include_once(getcwd().'/modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/ecommerceInvoicesListView.php');
} }

View File

@@ -1,177 +0,0 @@
<?php
/**
* report_form.php — formularz + uruchomienie analysisAI.py z parametrami
* ZGODNE z PHP 5.6 i Sugar 6 (wyciszone E_STRICT/E_DEPRECATED/NOTICE).
*/
// --- wycisz „hałas” Sugar CRM ---
error_reporting(E_ALL & ~E_STRICT & ~E_DEPRECATED & ~E_NOTICE);
ini_set('display_errors', '0');
// (opcjonalnie) loguj do pliku
// ini_set('log_errors', '1');
// ini_set('error_log', '/var/log/php_form_errors.log');
// --- ŚCIEŻKI (dostosuj do swojej instalacji) ---
$python = '/usr/local/bin/python3';
$script = '/var/www/html/modules/EcmInvoiceOuts/ai/analysisAI.py';
$baseDir = dirname($script);
// --- domyślne wartości pól ---
$defaultDateTo = date('Y-m-d');
$defaultDateFrom = date('Y-m-d', strtotime('-7 days'));
// --- zbieranie POST (PHP 5.6 friendly) ---
$submitted = (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST');
$post_date_from = isset($_POST['date_from']) ? $_POST['date_from'] : $defaultDateFrom;
$post_date_to = isset($_POST['date_to']) ? $_POST['date_to'] : $defaultDateTo;
$post_preaggs = (isset($_POST['preaggs']) && is_array($_POST['preaggs'])) ? $_POST['preaggs'] : array();
$post_with_ai = !empty($_POST['with_ai']);
function h($v) { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); }
function is_valid_date_yyyy_mm_dd($d) {
return (bool)preg_match('/^\d{4}-\d{2}-\d{2}$/', $d);
}
// --- wykonanie skryptu Pythona, jeśli formularz został wysłany ---
$ran = false;
$ok = false;
$rc = 0;
$out = '';
$err = '';
if ($submitted) {
// prosta walidacja dat
if (!is_valid_date_yyyy_mm_dd($post_date_from) || !is_valid_date_yyyy_mm_dd($post_date_to)) {
$err = "Nieprawidłowy format daty. Użyj YYYY-MM-DD.";
$ran = true;
} else {
// zbuduj argumenty
$args = array(
'--date-from', $post_date_from,
'--date-to', $post_date_to,
'--ai', ($post_with_ai ? 'true' : 'false')
);
if (!empty($post_preaggs)) {
// CSV z zaznaczonych preagregatów
$args[] = '--metrics';
$args[] = implode(',', $post_preaggs);
}
// komenda: przejdź do katalogu skryptu, uruchom pythona; zbierz stdout+stderr
$cmd = 'cd ' . escapeshellarg($baseDir) . ' && ' .
escapeshellcmd($python) . ' ' . escapeshellarg($script);
foreach ($args as $a) {
$cmd .= ' ' . escapeshellarg($a);
}
$output = array();
$returnVar = 0;
exec($cmd . ' 2>&1', $output, $returnVar);
$ran = true;
$rc = $returnVar;
$out = implode("\n", $output);
$ok = ($returnVar === 0);
if (!$ok && $err === '') {
$err = "Błąd uruchamiania skryptu Python (kod: " . $rc . "):\n" . $out;
}
}
}
?>
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>Generator raportu sprzedaży</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font: 14px/1.4 system-ui, Arial, sans-serif; padding: 20px; }
fieldset { margin-bottom: 16px; padding: 12px; border-radius: 6px; border:1px solid #e5e5e5; }
.row { display: flex; gap: 16px; flex-wrap: wrap; }
.col { min-width: 220px; flex: 1; }
label { display:block; margin: 6px 0; }
input[type="date"], button { padding: 6px 10px; font-size:14px; }
button { margin-top: 10px; cursor: pointer; border:1px solid #0a66c2; background:#0a66c2; color:#fff; border-radius:8px; }
.pill { display:inline-block; padding:2px 8px; border-radius:999px; background:#eee; margin:4px 6px 0 0; }
.out { white-space: normal; background: #fff; border:1px solid #ddd; padding:12px; border-radius:6px; }
.error { white-space: pre-wrap; background: #fff3f3; border:1px solid #f3c2c2; padding:12px; border-radius:6px; color:#b00020; }
.muted { color:#666; }
</style>
</head>
<body>
<h1>Raport sprzedaży — parametry</h1>
<form method="post">
<!-- zakres dat -->
<fieldset>
<legend>Zakres dat</legend>
<div class="row">
<div class="col">
<label>Data od:
<input type="date" name="date_from" value="<?php echo h($post_date_from); ?>" required>
</label>
</div>
<div class="col">
<label>Data do:
<input type="date" name="date_to" value="<?php echo h($post_date_to); ?>" required>
</label>
</div>
</div>
</fieldset>
<!-- preagregaty -->
<fieldset>
<legend>Preagregaty do analizy</legend>
<label><input type="checkbox" name="preaggs[]" value="daily_sales" <?php echo in_array('daily_sales', $post_preaggs, true) ? 'checked' : ''; ?>> Dzienne sprzedaże</label>
<label><input type="checkbox" name="preaggs[]" value="product_summary" <?php echo in_array('product_summary', $post_preaggs, true) ? 'checked' : ''; ?>> Podsumowanie produktów</label>
<label><input type="checkbox" name="preaggs[]" value="customer_summary" <?php echo in_array('customer_summary', $post_preaggs, true) ? 'checked' : ''; ?>> Podsumowanie klientów</label>
<label><input type="checkbox" name="preaggs[]" value="product_daily" <?php echo in_array('product_daily', $post_preaggs, true) ? 'checked' : ''; ?>> Sprzedaż produktu dziennie</label>
<label><input type="checkbox" name="preaggs[]" value="top10_products_by_sales" <?php echo in_array('top10_products_by_sales', $post_preaggs, true) ? 'checked' : ''; ?>> Top10 produktów</label>
<label><input type="checkbox" name="preaggs[]" value="top10_customers_by_sales"<?php echo in_array('top10_customers_by_sales', $post_preaggs, true) ? 'checked' : ''; ?>> Top10 klientów</label>
</fieldset>
<!-- AI -->
<fieldset>
<legend>Analiza AI</legend>
<label>
<input type="checkbox" name="with_ai" <?php echo $post_with_ai ? 'checked' : ''; ?>> Dołącz analizę AI
</label>
</fieldset>
<button type="submit">Generuj</button>
</form>
<?php if ($submitted): ?>
<hr>
<h2>Użyte parametry</h2>
<p>
<span class="pill">Od: <?php echo h($post_date_from); ?></span>
<span class="pill">Do: <?php echo h($post_date_to); ?></span>
<span class="pill">AI: <?php echo $post_with_ai ? 'tak' : 'nie'; ?></span>
</p>
<p>Preagregaty:
<?php
if (!empty($post_preaggs)) {
foreach ($post_preaggs as $p) {
echo '<span class="pill">'.h($p).'</span>';
}
} else {
echo '<span class="muted">brak</span>';
}
?>
</p>
<h2>Wynik analizy</h2>
<?php if (!$ok): ?>
<div class="error"><?php echo h($err); ?></div>
<?php else: ?>
<!-- Zakładamy, że Python zwraca gotowy HTML -->
<div class="out"><?php echo $out; ?></div>
<?php endif; ?>
<?php endif; ?>
</body>
</html>

View File

@@ -1,28 +1,16 @@
<?php <?php
$python = '/usr/local/bin/python3'; // Runs the Python script, waits for completion, and returns its output.
$script = '/var/www/html/modules/EcmInvoiceOuts/ai/analysisAI.py';
$cmd = escapeshellcmd("$python $script");
// odczyt $cmd = 'python3 /var/www/html/modules/EcmInvoiceOuts/ai/test.py';
$output = []; $output = [];
$returnVar = 0; $returnVar = 0;
exec($cmd . ' 2>&1', $output, $returnVar); exec($cmd . ' 2>&1', $output, $returnVar);
$body = implode("\n", $output);
// błąd Pythona
if ($returnVar !== 0) { if ($returnVar !== 0) {
// pokaż błąd jako tekst
while (ob_get_level()) { ob_end_clean(); }
header_remove();
header('Content-Type: text/plain; charset=utf-8');
http_response_code(500); http_response_code(500);
echo "Error running Python script:\n".$body; echo "Error running Python script:\n" . implode("\n", $output);
exit; exit;
} }
// --- WYMUSZENIE RENDEROWANIA HTML --- // Expect a single line with the count
while (ob_get_level()) { ob_end_clean(); } // wyczyść wszystkie bufory echo trim(implode("\n", $output));
header_remove(); // usuń nagłówki ustawione wcześniej przez framework
header('Content-Type: text/html; charset=utf-8');
echo $body;
exit; // ZATRZYMAJ framework (np. SugarCRM), żeby nic już nie dopisywał

View File

@@ -40,13 +40,13 @@ if (isset($_REQUEST['record']) && $_REQUEST['isDuplicate'] == "false") {
if (isset($focus->id) && $focus->id != '') { if (isset($focus->id) && $focus->id != '') {
$focus->format_all_fields(); $focus->format_all_fields();
} }
} else if (isset($_REQUEST['ecommerceZS'])) { } else if (isset($_REQUEST['ecommerceZSApilo'])) {
$new_number = true; $new_number = true;
$focus->stock_id = 'cf16804e-f698-5e09-2da3-6553588446ae'; $focus->stock_id = 'cf16804e-f698-5e09-2da3-6553588446ae';
$focus->register_date = date("d.m.Y"); $focus->register_date = date("d.m.Y");
$focus->parent_id = 'b5612f7f-85e5-f930-293e-62cead14b424'; $focus->parent_id = 'b5612f7f-85e5-f930-293e-62cead14b424';
$db = $GLOBALS['db']; $db = $GLOBALS['db'];
$invoiceIds = $_SESSION[$_REQUEST['ecommerceZS']]; $invoiceIds = $_SESSION[$_REQUEST['ecommerceZSApilo']];
$i = $db->fetchByAssoc($db->query("SELECT register_date FROM ecommerce_invoices WHERE id = '$invoiceIds[0]'")); $i = $db->fetchByAssoc($db->query("SELECT register_date FROM ecommerce_invoices WHERE id = '$invoiceIds[0]'"));
$focus->delivery_date = date("d.m.Y"); $focus->delivery_date = date("d.m.Y");
$focus->payment_date = date("d.m.Y"); $focus->payment_date = date("d.m.Y");
@@ -92,6 +92,65 @@ GROUP BY ip.ecmproduct_id, ip.price_netto;
$documentNos[] = $row['document_no']; $documentNos[] = $row['document_no'];
} }
$focus->pdf_text = "Dotyczy faktur: " . implode(', ', $documentNos); $focus->pdf_text = "Dotyczy faktur: " . implode(', ', $documentNos);
} else if (isset($_REQUEST['ecommerceZSTemu'])) {
$orders = json_decode($_SESSION[$_REQUEST['ecommerceZSTemu']]);
$new_number = true;
$focus->stock_id = 'cf16804e-f698-5e09-2da3-6553588446ae';
$focus->register_date = date("d.m.Y");
$focus->parent_id = '8b99791c-4e32-4443-fa57-69037059fe3f';
$db = $GLOBALS['db'];
$invoiceIds = $_SESSION[$_REQUEST['ecommerceZSApilo']];
$focus->delivery_date = date("d.m.Y");
$focus->payment_date = date("d.m.Y");
$focus->send_date = date("d.m.Y");
$focus->payment_date_days = 0;
$focus->status = 's30';
$focus->order_source = 'temu-'.$_REQUEST['temuCountry'];
$documentNos = array();
$products = array();
foreach ($orders as $order) {
$documentNos[] = $order->orderNumber;
foreach($order->products as $product) {
$prod = $db->fetchByAssoc($db->query("SELECT id, name, code, unit_id FROM ecmproducts WHERE code = '".$product->extCode."' AND deleted = 0"));
if (!isset($prod)) {
echo 'Brak produktu, nie wystawiaj dokumentu! '.$product->extCode.' (Zamówienie: '.$order->orderNumber.')<br>';
} else {
$products[] = array(
'product_id' => $prod['id'],
'product_code' => $prod['code'],
'name' => $prod['name'],
'quantity' => $product->soldFactor,
'price_start' => 0.01,
'price_netto' => 0.01,
'unit_id' => $prod['unit_id'],
'unit_name' => $app_list_strings['ecmproducts_unit_dom'][$prod['unit_id']],
'ecmvat_name' => '0%',
'ecmvat_value' => 0,
'ecmvat_id' => '9b783d21-5548-6653-e1d6-49610eb3f9dd',
);
}
}
$prod = $db->fetchByAssoc($db->query("SELECT id FROM ecmproducts WHERE code = '".$order->productCode."'"));
}
$groupedProducts = array();
foreach ($products as $p) {
$pid = $p['product_id'];
if (!isset($groupedProducts[$pid])) {
$groupedProducts[$pid] = $p;
} else {
$groupedProducts[$pid]['quantity'] += $p['quantity'];
}
}
$groupedProducts = array_values($groupedProducts);
$edit->ss->assign('ECOMMERCE_PRODUCTS', json_encode($groupedProducts));
$query = "SELECT document_no FROM ecommerce_invoices WHERE id IN ('" . implode('\',\'', $invoiceIds) . "')";
$res = $db->query($query);
$focus->pdf_text = "Dotyczy zamówień: " . implode(', ', $documentNos);
} else if ($_REQUEST['isDuplicate'] == "true") { } else if ($_REQUEST['isDuplicate'] == "true") {
$new_number = true; $new_number = true;
$duplicate = true; $duplicate = true;

View File

@@ -0,0 +1,166 @@
<?php
function noquote($v){
$j = json_encode($v, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
return (is_string($v) && $j[0]==='"' && substr($j,-1)==='"') ? substr($j,1,-1) : $j;
}
function sign_md5($p, $secret){
ksort($p, SORT_STRING);
$s = $secret;
foreach($p as $k=>$v){ if($v!=='' && $v!==null) $s .= $k . noquote($v); }
$s .= $secret;
return strtoupper(md5($s));
}
function getTemuOrders($from, $to, $country, $pageSize = 50){
$APP_KEY = '02cbeff8afd35247294810033b036cfe';
$APP_SECRET = '8ecfa21b55cddf66c66043cbb80756efc4ba6596';
$ACCESS_TOKEN_DE = 'eploouslfhwjwwzvz5okvmu29stwwdw9l3ullz8pv6y4raips8gouzwcqmv';
$ACCESS_TOKEN_PL = 'eplyaf6ogcpewjtzytw8q9ahvx7haqntzk8wsz7vokjusvpry93gy9cvn2c';
$ACCESS_TOKEN_FR = 'eplhrdwtdgjpfelzmyelnsifakqjzfza6rpa8neb7nqc8djxj11hlenl3u2';
$BASE_URL = 'https://openapi-b-eu.temu.com/openapi/router';
$REGION_ID = 162;
// --- parse daty ---
if (!is_int($from)) $from = strtotime($from);
if (!is_int($to)) $to = strtotime($to);
if (!$from || !$to) return [];
if ($from > $to) { $tmp = $from; $from = $to; $to = $tmp; }
$page = 1;
$out = [];
$seenAnyInRange = false;
$guardPages = 500;
while ($guardPages-- > 0) {
$biz = array(
"type" => "bg.order.list.v2.get",
//"regionId" => $REGION_ID,
"pageNumber" => $page,
"pageSize" => $pageSize
);
switch ($country) {
case 'pl': $ACCESS_TOKEN = $ACCESS_TOKEN_PL; break;
case 'fr': $ACCESS_TOKEN = $ACCESS_TOKEN_FR; break;
default: $ACCESS_TOKEN = $ACCESS_TOKEN_DE; break;
}
$common = array(
'app_key' => $APP_KEY,
'access_token' => $ACCESS_TOKEN,
'data_type' => 'JSON',
'timestamp' => (int)floor(microtime(true)),
);
$req = $biz + $common;
$req['sign'] = sign_md5($req, $APP_SECRET);
$payload = json_encode($req, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $BASE_URL,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => array('Content-Type: application/json'),
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 30,
));
$resp = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$cerr = curl_error($ch);
curl_close($ch);
if ($resp === false || $http !== 200) {
break;
}
$data = json_decode($resp, true);
if ($data['success'] == false) {
var_dump('Błąd ładowania zamówień TEMU '.$country.': '.$data['errorMsg']);
break;
}
if (!is_array($data)
|| !isset($data['result']['pageItems'])
|| !is_array($data['result']['pageItems'])
|| count($data['result']['pageItems']) === 0) {
break;
}
$items = $data['result']['pageItems'];
usort($items, function($a, $b){
$ta = isset($a['parentOrderMap']['parentOrderTime']) ? (int)$a['parentOrderMap']['parentOrderTime'] : 0;
$tb = isset($b['parentOrderMap']['parentOrderTime']) ? (int)$b['parentOrderMap']['parentOrderTime'] : 0;
if ($ta == $tb) return 0;
return ($ta > $tb) ? -1 : 1;
});
$maxTs = isset($items[0]['parentOrderMap']['parentOrderTime']) ? (int)$items[0]['parentOrderMap']['parentOrderTime'] : 0;
$last = $items[count($items)-1];
$minTs = isset($last['parentOrderMap']['parentOrderTime']) ? (int)$last['parentOrderMap']['parentOrderTime'] : 0;
foreach ($items as $row) {
if (!isset($row['parentOrderMap']['parentOrderSn'])) continue;
$ts = isset($row['parentOrderMap']['parentOrderTime']) ? (int)$row['parentOrderMap']['parentOrderTime'] : 0;
if ($ts < $from || $ts > $to) continue;
$pst = isset($row['parentOrderMap']['parentOrderStatus']) ? (int)$row['parentOrderMap']['parentOrderStatus'] : -1;
if (!($pst === 4 || $pst === 5)) continue;
$seenAnyInRange = true;
$orderSn = $row['parentOrderMap']['parentOrderSn'];
// check if order exitst
$db = $GLOBALS['db'];
$exists = $db->fetchByAssoc($db->query("SELECT id FROM ecmsales WHERE pdf_text LIKE '%$orderSn%'"));
if (isset($exists['id'])) continue;
$products = array();
if (isset($row['orderList']) && is_array($row['orderList'])) {
foreach ($row['orderList'] as $ol) {
if (!isset($ol['productList']) || !is_array($ol['productList'])) continue;
foreach ($ol['productList'] as $p) {
// hacks :)
if ($p['extCode'] == 'FR00259_52_15g_amz_de-1') {
$p['extCode'] = 'FR00259_52_15g_amz_de';
}
if ($p['extCode'] == 'FR00138_1000_amz_de-1') {
$p['extCode'] = 'FR00138_1000_amz_de';
}
$products[] = array(
'extCode' => isset($p['extCode']) ? $p['extCode'] : null,
'soldFactor' => isset($p['soldFactor']) ? $p['soldFactor'] : null,
);
}
}
}
$out[] = array(
'orderNumber' => $orderSn,
'issueTime' => $ts,
'issueDate' => $ts ? date('Y-m-d H:i:s', $ts) : null,
'products' => $products,
'raw' => $row,
);
}
if ($minTs >= $from) {
$page++;
continue;
}
if ($maxTs < $from) {
break;
}
if (!$seenAnyInRange) {
$page++;
continue;
}
break;
}
usort($out, function($a, $b){
if ($a['issueTime'] == $b['issueTime']) return 0;
return ($a['issueTime'] > $b['issueTime']) ? -1 : 1;
});
return $out;
}

View File

@@ -3,6 +3,8 @@ ini_set('display_errors', 1);
ini_set('display_startup_errors', 1); ini_set('display_startup_errors', 1);
error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED); error_reporting(E_ALL & ~E_NOTICE & ~E_STRICT & ~E_DEPRECATED);
require_once ('modules/EcmSales/bimit_importTemuOrders.php');
$db = $GLOBALS['db']; $db = $GLOBALS['db'];
global $app_list_strings; global $app_list_strings;
@@ -18,9 +20,9 @@ if (isset($_REQUEST['date_from'])) {
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
$invoiceIds[] = $invoice['id']; $invoiceIds[] = $invoice['id'];
} }
$sessionId = create_guid(); $sessionIdApilo = create_guid();
$_SESSION[$sessionId] = $invoiceIds; $_SESSION[$sessionIdApilo] = $invoiceIds;
$smarty->assign('sessionId', $sessionId); $smarty->assign('sessionIdApilo', $sessionIdApilo);
$invoiceNo = array(); $invoiceNo = array();
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
@@ -28,6 +30,39 @@ if (isset($_REQUEST['date_from'])) {
} }
$smarty->assign('invoicesNo', join(", ", $invoiceNo)); $smarty->assign('invoicesNo', join(", ", $invoiceNo));
$dateFrom = date('Y-m-d', strtotime($_REQUEST['date_from']));
$dateTo = date('Y-m-d', strtotime($_REQUEST['date_to']));
$ordersPl = getTemuOrders($dateFrom.' 00:00:00', $dateTo.' 23:59:59', 'pl', 50);
$orderNoPl = array();
foreach ($ordersPl as $order) {
$orderNoPl[] = $order['orderNumber'];
}
$smarty->assign('ordersNoPl', join(", ", $orderNoPl));
$sessionIdTemuPl = create_guid();
$_SESSION[$sessionIdTemuPl] = json_encode($ordersPl);
$smarty->assign('sessionIdTemuPl', $sessionIdTemuPl);
$ordersDe = getTemuOrders($dateFrom.' 00:00:00', $dateTo.' 23:59:59', 'de', 50);
$orderNoDe = array();
foreach ($ordersDe as $order) {
$orderNoDe[] = $order['orderNumber'];
}
$smarty->assign('ordersNoDe', join(", ", $orderNoDe));
$sessionIdTemuDe = create_guid();
$_SESSION[$sessionIdTemuDe] = json_encode($ordersDe);
$smarty->assign('sessionIdTemuDe', $sessionIdTemuDe);
$ordersFr = getTemuOrders($dateFrom.' 00:00:00', $dateTo.' 23:59:59', 'fr', 50);
$orderNoFr = array();
foreach ($ordersFr as $order) {
$orderNoFr[] = $order['orderNumber'];
}
$smarty->assign('ordersNoFr', join(", ", $orderNoFr));
$sessionIdTemuFr = create_guid();
$_SESSION[$sessionIdTemuFr] = json_encode($ordersFr);
$smarty->assign('sessionIdTemuFr', $sessionIdTemuFr);
$smarty->assign('date_from', $_REQUEST['date_from']); $smarty->assign('date_from', $_REQUEST['date_from']);
$smarty->assign('date_to', $_REQUEST['date_to']); $smarty->assign('date_to', $_REQUEST['date_to']);
echo $smarty->display(getcwd() . '/modules/EcmSales/eCommerceZS/eCommerceZS.tpl'); echo $smarty->display(getcwd() . '/modules/EcmSales/eCommerceZS/eCommerceZS.tpl');
@@ -45,7 +80,7 @@ function getInvoices($dateFrom, $dateTo) {
$query = "SELECT ip.ecmproduct_id, ip.code, i.document_no, SUM(ip.quantity) AS quantity, i.register_date, i.id $query = "SELECT ip.ecmproduct_id, ip.code, i.document_no, SUM(ip.quantity) AS quantity, i.register_date, i.id
FROM ecommerce_invoices_products AS ip FROM ecommerce_invoices_products AS ip
INNER JOIN ecommerce_invoices AS i ON ip.invoice_id = i.id AND i.register_date BETWEEN '$dateFrom' AND '$dateTo' AND i.type='normal' INNER JOIN ecommerce_invoices AS i ON ip.invoice_id = i.id AND i.register_date BETWEEN '$dateFrom' AND '$dateTo' AND i.type='normal'
AND i.origin IN ('allegro', 'shop') AND (i.origin IN ('allegro', 'shop') OR origin LIKE 'apilo%')
WHERE ip.ecmproduct_id != '' AND ip.ecmproduct_id !='165f364e-9301-25ac-5906-58e38f1de4ca' WHERE ip.ecmproduct_id != '' AND ip.ecmproduct_id !='165f364e-9301-25ac-5906-58e38f1de4ca'
AND i.ecmstockdocout_id IS NULL AND i.ecmsale_id IS NULL AND i.ecmstockdocout_id IS NULL AND i.ecmsale_id IS NULL
GROUP BY i.id, ip.ecmproduct_id GROUP BY i.id, ip.ecmproduct_id

View File

@@ -63,7 +63,22 @@
</form> </form>
<hr> <hr>
ZS zbiorcza dla FV:<br> {$invoicesNo} <b>ZS zbiorcza dla FV (Apilo):</b><br> {$invoicesNo}
<br><br> <br><br>
<input title="Wystaw ZS" class="button primary" type="button" name="Edit" id="edit_button" value="Wystaw ZS" <input title="Wystaw ZS" class="button primary" type="button" name="Edit" id="edit_button" value="Wystaw ZS Apilo"
onclick="window.open('index.php?module=EcmSales&action=EditView&ecommerceZS={$sessionId}')"> onclick="window.open('index.php?module=EcmSales&action=EditView&ecommerceZSApilo={$sessionIdApilo}')">
<br><br>
<b>ZS zbiorcza dla zamówień TEMU PL:</b><br> {$ordersNoPl}
<br><br>
<input title="Wystaw ZS" class="button primary" type="button" name="Edit" id="edit_button" value="Wystaw ZS Temu PL"
onclick="window.open('index.php?module=EcmSales&action=EditView&temuCountry=PL&ecommerceZSTemu={$sessionIdTemuPl}')">
<br><br>
<b>ZS zbiorcza dla zamówień TEMU DE:</b><br> {$ordersNoDe}
<br><br>
<input title="Wystaw ZS" class="button primary" type="button" name="Edit" id="edit_button" value="Wystaw ZS Temu DE"
onclick="window.open('index.php?module=EcmSales&action=EditView&temuCountry=DE&ecommerceZSTemu={$sessionIdTemuDe}')">
<br><br>
<b>ZS zbiorcza dla zamówień TEMU FR:</b><br> {$ordersNoFr}
<br><br>
<input title="Wystaw ZS" class="button primary" type="button" name="Edit" id="edit_button" value="Wystaw ZS Temu FR"
onclick="window.open('index.php?module=EcmSales&action=EditView&temuCountry=FR&ecommerceZSTemu={$sessionIdTemuFr}')">

View File

@@ -106,7 +106,6 @@ class Scheduler extends SugarBean {
*/ */
function fire() { function fire() {
if(empty($this->job)) { // only execute when valid if(empty($this->job)) { // only execute when valid
$GLOBALS['log']->fatal('Scheduler tried to fire an empty job!!');
return false; return false;
} }
@@ -126,16 +125,18 @@ class Scheduler extends SugarBean {
$job->retrieve($jobId); $job->retrieve($jobId);
if($exJob[0] == 'function') { if($exJob[0] == 'function') {
$GLOBALS['log']->debug('----->Scheduler found a job of type FUNCTION');
require_once('modules/Schedulers/_AddJobsHere.php'); require_once('modules/Schedulers/_AddJobsHere.php');
$job->setJobFlag(1); $job->setJobFlag(1);
$func = $exJob[1]; $func = $exJob[1];
$GLOBALS['log']->debug('----->SchedulersJob firing '.$func);
$GLOBALS['log']->bimit('Starting function', $job->scheduler->id, $job->scheduler->name, $func);
$res = call_user_func($func); $res = call_user_func($func);
if($res) { if($res) {
$GLOBALS['log']->bimit('Setting JOB as finished', $job->scheduler->id, $job->scheduler->name, $res);
$job->setJobFlag(2); $job->setJobFlag(2);
$job->finishJob(); $job->finishJob();
return true; return true;
@@ -218,7 +219,6 @@ class Scheduler extends SugarBean {
*/ */
function fireQualified() { function fireQualified() {
if(empty($this->id)) { // execute only if we have an instance if(empty($this->id)) { // execute only if we have an instance
$GLOBALS['log']->fatal('Scheduler called fireQualified() in a non-instance');
return false; return false;
} }
@@ -227,10 +227,8 @@ class Scheduler extends SugarBean {
//_pp('now: '.$now); _pp($validTimes); //_pp('now: '.$now); _pp($validTimes);
if(is_array($validTimes) && in_array($now, $validTimes)) { if(is_array($validTimes) && in_array($now, $validTimes)) {
$GLOBALS['log']->debug('----->Scheduler found valid job ('.$this->name.') for time GMT('.$now.')');
return true; return true;
} else { } else {
$GLOBALS['log']->debug('----->Scheduler did NOT find valid job ('.$this->name.') for time GMT('.$now.')');
return false; return false;
} }
} }
@@ -339,21 +337,14 @@ class Scheduler extends SugarBean {
$this->cleanJobLog(); $this->cleanJobLog();
$allSchedulers = $this->get_full_list('', 'schedulers.status=\'Active\''); $allSchedulers = $this->get_full_list('', 'schedulers.status=\'Active\'');
$GLOBALS['log']->info('-----> Scheduler found [ '.count($allSchedulers).' ] ACTIVE jobs');
if(!empty($allSchedulers)) { if(!empty($allSchedulers)) {
foreach($allSchedulers as $focus) { foreach($allSchedulers as $focus) {
if($focus->fireQualified()) { if($focus->fireQualified()) {
if($focus->fire()) { $GLOBALS['log']->bimit('Starting job', $focus->id, $focus->name);
$GLOBALS['log']->debug('----->Scheduler Job completed successfully'); $res = $focus->fire();
} else { $GLOBALS['log']->bimit('Finished job', $focus->id, $focus->name, $res);
$GLOBALS['log']->fatal('----->Scheduler Job FAILED');
}
} }
} }
} else {
$GLOBALS['log']->debug('----->No Schedulers found');
} }
} }

View File

@@ -65,18 +65,39 @@ if (! defined('sugarEntry') || ! sugarEntry)
$job_strings = array( $job_strings = array(
0 => 'getNbpCurrencies', 0 => 'getNbpCurrencies',
1 => 'importBaselinkerInvoices', 1 => 'importBaselinkerInvoices',
2 => 'importBaselinkerCorrectingInvoices' 2 => 'importBaselinkerCorrectingInvoices',
3 => 'importApiloInvoices'
); );
function importApiloInvoices() {
try {
$GLOBALS['log']->bimit('_addJobsHere, importApiloInvoices started');
$GLOBALS['db']->query("USE preDb_0dcc87940d3655fa574b253df04ca1c3;");
$GLOBALS['log']->bimit('_addJobsHere, importApiloInvoices db query done');
require_once('modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/importApiloInvoices.php');
$GLOBALS['log']->bimit('_addJobsHere, importApiloInvoices file loadded');
$GLOBALS['log']->bimit('_addJobsHere, importApiloInvoices importInvoices() fired');
$res = apilo_importInvoices();
$GLOBALS['log']->bimit('_addJobsHere, importApiloInvoices finished', $res);
return true;
} catch (Exception $e) {
$GLOBALS['log']->bimit('_addJobsHere, importApiloInvoices error', $e);
return false;
}
}
function importBaselinkerInvoices() function importBaselinkerInvoices()
{ {
try { try {
$GLOBALS['log']->bimit('_addJobsHere, importBaselinkerInvoices started');
$GLOBALS['db']->query("USE preDb_0dcc87940d3655fa574b253df04ca1c3;"); $GLOBALS['db']->query("USE preDb_0dcc87940d3655fa574b253df04ca1c3;");
require_once('modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/importBaselinkerInvoices.php'); require_once('modules/EcmInvoiceOuts/BimIT-eCommerceInvoices/importBaselinkerInvoices.php');
importFV('7688'); // FV Polska importFV('7688'); // FV Polska
//importFV('15356'); // FV Polska (stare) //importFV('15356'); // FV Polska (stare)
importFV('53096'); // FV Temu
$GLOBALS['log']->bimit('_addJobsHere, importBaselinkerInvoices finished');
return true; return true;
} catch (Exception $e) { } catch (Exception $e) {
$GLOBALS['log']->bimit('_addJobsHere, importBaselinkerInvoices error', $e);
return false; return false;
} }
} }

View File

@@ -1,11 +1,4 @@
<?php <?php
die(); $logger = $GLOBALS['log'];
require_once 'modules/Schedulers/Monitoring.php'; $logger->bimit('test');
$EcmSysInfo = new EcmSysInfo (); echo 'test';
if ($EcmSysInfo->getDatabaseName () == 'preDb_7561b7965a2f9cebf2cbca60a9a07064') {
new Monitoring ();
}
return true;
?>