diff --git a/api.php b/api.php
index 3efca87..d32392a 100644
--- a/api.php
+++ b/api.php
@@ -1,49 +1,86 @@
$duration,
+ 'path' => '/',
+ 'secure' => isset($_SERVER['HTTPS']),
+ 'httponly' => true,
+ 'samesite' => 'Lax'
+]);
+
+if (session_status() === PHP_SESSION_NONE) {
+ session_start();
+}
+
+// 2. RECOVERY LOGIC (Check cookie before WP loads)
+if (!isset($_SESSION['user']) && isset($_COOKIE['telvero_remember'])) {
+ $decoded = json_decode(base64_decode($_COOKIE['telvero_remember']), true);
+ if ($decoded && $decoded['expires'] > time()) {
+ $_SESSION['user'] = $decoded['user'];
+ $_SESSION['full_name'] = $decoded['full_name'];
+ }
+}
+
+// 3. CAPTURE DATA FOR WP PROTECTION
+$cap_user = $_SESSION['user'] ?? null;
+$cap_name = $_SESSION['full_name'] ?? null;
+
+// 4. LOAD WORDPRESS
+$wp_load = __DIR__ . '/wp-load.php';
+if (!file_exists($wp_load)) { $wp_load = dirname(__DIR__) . '/wp-load.php'; }
+if (file_exists($wp_load)) { require_once $wp_load; }
+
+// 5. RESTORE DATA
+if ($cap_user && !isset($_SESSION['user'])) {
+ $_SESSION['user'] = $cap_user;
+ $_SESSION['full_name'] = $cap_name;
+}
+
+// 6. REST OF THE BOOTSTRAP
require __DIR__ . '/vendor/autoload.php';
-
if (file_exists(__DIR__ . '/.env')) {
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
}
use Automattic\WooCommerce\Client;
-use Mollie\Api\MollieApiClient;
-
header('Content-Type: application/json');
$db = new mysqli($_ENV['DB_HOST'], $_ENV['DB_USER'], $_ENV['DB_PASS'], $_ENV['DB_NAME']);
if ($db->connect_error) { die(json_encode(['error' => 'Database connectie mislukt'])); }
-function writeLog($action, $details) {
- global $db;
- $user = $_SESSION['user'] ?? 'system';
- $stmt = $db->prepare("INSERT INTO sales_logs (username, action, details, created_at) VALUES (?, ?, ?, NOW())");
- $stmt->bind_param("sss", $user, $action, $details);
- $stmt->execute();
-}
-
-
$action = $_GET['action'] ?? '';
-// --- AUTH ---
+// 6. LOGIN ACTION
if ($action === 'login') {
$input = json_decode(file_get_contents('php://input'), true);
+ $username = $input['username'] ?? '';
$stmt = $db->prepare("SELECT password, full_name FROM sales_users WHERE username = ?");
- $stmt->bind_param("s", $input['username']);
+ $stmt->bind_param("s", $username);
$stmt->execute();
$res = $stmt->get_result()->fetch_assoc();
+
if ($res && password_verify($input['password'], $res['password'])) {
- $_SESSION['user'] = $input['username'];
+ $_SESSION['user'] = $username;
$_SESSION['full_name'] = $res['full_name'];
- writeLog('LOGIN', 'Gebruiker ingelogd');
- session_write_close();
+
+ // Recovery cookie payload
+ $cookie_payload = base64_encode(json_encode([
+ 'user' => $username,
+ 'full_name' => $res['full_name'],
+ 'expires' => $midnight_timestamp
+ ]));
+
+ // Use the explicit $midnight_timestamp variable
+ setcookie('telvero_remember', $cookie_payload, $midnight_timestamp, '/', '', isset($_SERVER['HTTPS']), true);
+
echo json_encode(['success' => true, 'user' => $res['full_name']]);
} else {
http_response_code(401); echo json_encode(['error' => 'Login mislukt']);
@@ -51,117 +88,417 @@ if ($action === 'login') {
exit;
}
-if (!isset($_SESSION['user']) && $action !== 'login') {
- http_response_code(403); exit;
+// 7. SECURITY GATE (The ONLY one needed)
+if (!isset($_SESSION['user'])) {
+ http_response_code(403);
+ echo json_encode(['error' => 'Not authenticated']);
+ exit;
}
-$woocommerce = new Client($_ENV['WC_URL'], $_ENV['WC_KEY'], $_ENV['WC_SECRET'], ['version' => 'wc/v3', 'verify_ssl' => false, 'timeout' => 400]);
+// 8. LOGOUT
+if ($action === 'logout') {
+ session_destroy();
+ setcookie('telvero_remember', '', time() - 3600, '/');
+ echo json_encode(['success' => true]);
+ exit;
+}
-// --- HELPERS ---
-if ($action === 'get_payment_methods') {
- try {
- $gateways = $woocommerce->get('payment_gateways');
- $output = [];
- foreach ($gateways as $gw) {
- if (str_contains($gw->id, 'applepay') || str_contains($gw->id, 'googlepay')) continue;
- if ($gw->enabled && (str_contains($gw->id, 'mollie') || str_contains($gw->id, 'riverty') || str_contains($gw->id, 'klarna'))) {
- $output[] = ['id' => $gw->id, 'title' => $gw->method_title];
+$woocommerce = new Client($_ENV['WC_URL'], $_ENV['WC_KEY'], $_ENV['WC_SECRET'], ['version' => 'wc/v3', 'verify_ssl' => false]);
+
+function ss_get_upsellwp_recommended_product_ids($trigger_product_id) {
+ if (!function_exists('get_option')) {
+ return [];
+ }
+
+ global $wpdb;
+
+ $trigger_product_id = (int) $trigger_product_id;
+ if ($trigger_product_id <= 0) return [];
+
+ // 1) Vind "campaign posts" waar in meta_value ergens dit product_id voorkomt.
+ // We weten niet exact welke meta_key UpsellWP gebruikt, dus zoeken we breed op meta_key LIKE '%upsellwp%'.
+ // Daarnaast zoeken we op bekende patronen in serialized/json/plain.
+ $like_json = '%' . $wpdb->esc_like('"' . $trigger_product_id . '"') . '%';
+ $like_plain = '%' . $wpdb->esc_like((string) $trigger_product_id) . '%';
+ $like_serial = '%' . $wpdb->esc_like('i:' . $trigger_product_id . ';') . '%';
+
+ $campaign_post_ids = $wpdb->get_col(
+ $wpdb->prepare(
+ "
+ SELECT DISTINCT post_id
+ FROM {$wpdb->postmeta}
+ WHERE meta_key LIKE %s
+ AND (
+ meta_value LIKE %s
+ OR meta_value LIKE %s
+ OR meta_value LIKE %s
+ )
+ ",
+ '%upsellwp%',
+ $like_json,
+ $like_serial,
+ $like_plain
+ )
+ );
+
+ if (empty($campaign_post_ids)) {
+ return [];
+ }
+
+ // 2) Verzamel uit die campaigns alle product IDs die genoemd worden (offers / bundles / buy-more-save-more tiers).
+ $recommended = [];
+
+ foreach ($campaign_post_ids as $cid) {
+ $cid = (int) $cid;
+
+ $rows = $wpdb->get_results(
+ $wpdb->prepare(
+ "
+ SELECT meta_key, meta_value
+ FROM {$wpdb->postmeta}
+ WHERE post_id = %d
+ ",
+ $cid
+ )
+ );
+
+ foreach ($rows as $r) {
+ $key = (string) $r->meta_key;
+
+ // Kleine bias naar keys die logisch product/offers bevatten
+ // maar alsnog niet te streng, want add-ons gebruiken soms andere keys.
+ if (!preg_match('/product|products|offer|offers|bundle|bmsm|tier|rule|condition|trigger|cart|recommend/i', $key)
+ && stripos($key, 'upsellwp') === false
+ ) {
+ continue;
+ }
+
+ $val = (string) $r->meta_value;
+
+ // Extract alle integers uit json/serialized/plain
+ if (preg_match_all('/\b\d+\b/', $val, $m)) {
+ foreach ($m[0] as $id) {
+ $id = (int) $id;
+ if ($id > 0) $recommended[] = $id;
+ }
}
}
- echo json_encode($output);
- } catch (Exception $e) { echo json_encode([]); }
- exit;
+ }
+
+ // 3) Opschonen: unieke IDs, trigger zelf eruit, en alleen echte WC producten (best effort).
+ $recommended = array_values(array_unique(array_filter($recommended)));
+ $recommended = array_values(array_diff($recommended, [$trigger_product_id]));
+
+ // Filter op bestaande producten (scheelt false positives zoals campaign IDs)
+ $recommended = array_values(array_filter($recommended, function($id){
+ return get_post_type($id) === 'product' || get_post_type($id) === 'product_variation';
+ }));
+
+ return $recommended;
}
-if ($action === 'get_products') {
- try {
- $products = $woocommerce->get('products', ['status' => 'publish', 'per_page' => 100]);
- $enriched = [];
- foreach ($products as $product) {
- $p = (array)$product;
- $p['variation_details'] = ($product->type === 'variable') ? (array)$woocommerce->get("products/{$product->id}/variations", ['per_page' => 50]) : [];
- $enriched[] = $p;
+/**
+ * Build a map: product_id => related product ids from CUW (UpsellWP) campaigns.
+ * Uses {$wpdb->prefix}cuw_campaigns table (as in your screenshot).
+ */
+function ss_cuw_build_product_map() {
+ if (!function_exists('get_post_type')) return [];
+
+ global $wpdb;
+
+ $table = $wpdb->prefix . 'cuw_campaigns';
+ // Safety: table might not exist
+ $exists = $wpdb->get_var( $wpdb->prepare("SHOW TABLES LIKE %s", $table) );
+ if ($exists !== $table) return [];
+
+ $rows = $wpdb->get_results("
+ SELECT id, type, filters, data
+ FROM {$table}
+ WHERE enabled = 1
+ ORDER BY priority DESC, id DESC
+ ");
+
+ if (empty($rows)) return [];
+
+ $map = [];
+
+ foreach ($rows as $row) {
+ $filters = json_decode((string) $row->filters, true);
+ if (!is_array($filters)) continue;
+
+ // Extract product lists from filters:
+ // Example (your screenshot):
+ // {"relation":"or","176...":{"type":"products","method":"in_list","values":["1831"]}}
+ $filterProductLists = [];
+
+ foreach ($filters as $k => $v) {
+ if ($k === 'relation') continue;
+ if (!is_array($v)) continue;
+
+ $type = $v['type'] ?? '';
+ $method = $v['method'] ?? '';
+ $values = $v['values'] ?? [];
+
+ if ($type !== 'products') continue;
+ if (!in_array($method, ['in_list', 'in', 'include', 'equals'], true)) continue;
+ if (!is_array($values)) continue;
+
+ $ids = array_values(array_unique(array_map('intval', $values)));
+ $ids = array_values(array_filter($ids));
+
+ if (!empty($ids)) {
+ $filterProductLists[] = $ids;
+ }
}
- echo json_encode($enriched);
- } catch (Exception $e) { echo json_encode([]); }
- exit;
+
+ if (empty($filterProductLists)) continue;
+
+ // For each list: if a product is in that list, relate it to the other products in that list
+ foreach ($filterProductLists as $list) {
+ foreach ($list as $pid) {
+ $related = array_values(array_diff($list, [$pid]));
+ if (empty($related)) continue;
+
+ // Keep only real Woo products
+ $related = array_values(array_filter($related, function($id){
+ $pt = get_post_type($id);
+ return $pt === 'product' || $pt === 'product_variation';
+ }));
+
+ if (empty($related)) continue;
+
+ if (!isset($map[$pid])) $map[$pid] = [];
+ $map[$pid] = array_values(array_unique(array_merge($map[$pid], $related)));
+ }
+ }
+ }
+
+ return $map;
}
+function ss_cuw_build_deals_map() {
+ global $wpdb;
+
+ $table = $wpdb->prefix . 'cuw_campaigns';
+
+ $exists = $wpdb->get_var( $wpdb->prepare("SHOW TABLES LIKE %s", $table) );
+ if ($exists !== $table) return [];
+
+ $rows = $wpdb->get_results("
+ SELECT id, type, title, priority, filters, data
+ FROM {$table}
+ WHERE enabled = 1
+ ORDER BY priority DESC, id DESC
+ ");
+
+ if (empty($rows)) return [];
+
+ $map = [];
+
+ foreach ($rows as $row) {
+ $type = (string) $row->type;
+ if ($type !== 'buy_more_save_more') {
+ continue;
+ }
+
+ $filters = json_decode((string) $row->filters, true);
+ $data = json_decode((string) $row->data, true);
+
+ if (!is_array($filters) || !is_array($data)) continue;
+
+ // 1) Product IDs uit filters halen (zoals jouw screenshot: type=products, method=in_list, values=[...])
+ $product_ids = [];
+
+ foreach ($filters as $k => $v) {
+ if ($k === 'relation') continue;
+ if (!is_array($v)) continue;
+
+ $f_type = $v['type'] ?? '';
+ $f_method = $v['method'] ?? '';
+ $values = $v['values'] ?? [];
+
+ if ($f_type !== 'products') continue;
+ if (!in_array($f_method, ['in_list', 'in', 'include', 'equals'], true)) continue;
+ if (!is_array($values)) continue;
+
+ foreach ($values as $pid) {
+ $pid = (int) $pid;
+ if ($pid > 0) $product_ids[] = $pid;
+ }
+ }
+
+ $product_ids = array_values(array_unique(array_filter($product_ids)));
+ if (empty($product_ids)) continue;
+
+ // 2) Deal details uit data halen
+ $cta_text = $data['template']['cta_text'] ?? '';
+ $display_location = $data['display_location'] ?? ($data['template']['display_location'] ?? '');
+
+ $discounts = $data['discounts'] ?? [];
+ if (!is_array($discounts) || empty($discounts)) continue;
+
+ foreach ($discounts as $disc) {
+ if (!is_array($disc)) continue;
+
+ $qty = isset($disc['qty']) ? (int) $disc['qty'] : 0;
+ $dType = isset($disc['type']) ? (string) $disc['type'] : '';
+ $value = isset($disc['value']) ? (float) $disc['value'] : 0.0;
+
+ if ($qty <= 0 || $dType === '' || $value <= 0) continue;
+
+ $deal = [
+ 'campaign_id' => (int) $row->id,
+ 'campaign_type' => $type,
+ 'title' => (string) $row->title,
+ 'priority' => (int) $row->priority,
+ 'qty' => $qty,
+ 'discount_type' => $dType, // fixed_price
+ 'value' => $value, // 10 (korting op 2e in jouw geval)
+ 'cta_text' => (string) $cta_text,
+ 'display_location' => (string) $display_location,
+ ];
+
+ foreach ($product_ids as $pid) {
+ if (!isset($map[$pid])) $map[$pid] = [];
+ $map[$pid][] = $deal;
+ }
+ }
+ }
+
+ return $map;
+}
+
+
+// --- POSTCODE CHECK ---
if ($action === 'postcode_check') {
$postcode = str_replace(' ', '', $_GET['postcode']);
$url = "https://postcode.tech/api/v1/postcode?postcode={$postcode}&number=" . $_GET['number'];
- $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+ $ch = curl_init($url);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer " . $_ENV['POSTCODE_TECH_KEY']]);
- echo curl_exec($ch); exit;
+ echo curl_exec($ch);
+ exit;
}
-// --- CREATE ORDER (V8.1 - MET LOGGING) ---
+// --- GET PRODUCTS ---
+// --- GET PRODUCTS ---
+if ($action === 'get_products') {
+ try {
+ $products = $woocommerce->get('products', ['status' => 'publish', 'per_page' => 100]);
+ $cuw_map = ss_cuw_build_product_map();
+
+ $enriched = [];
+
+ foreach ($products as $product) {
+
+ // variations (bestaand)
+ $variation_details = ($product->type === 'variable')
+ ? (array) $woocommerce->get("products/{$product->id}/variations", ['per_page' => 50])
+ : [];
+
+ // wpupsell IDs uit meta_data halen (heuristisch, werkt bij veel plugins)
+ $wpupsell_ids = [];
+ if (!empty($product->meta_data) && is_array($product->meta_data)) {
+ foreach ($product->meta_data as $md) {
+ $key = isset($md->key) ? (string) $md->key : '';
+ if ($key === '' || !preg_match('/wp\s*upsell|wpupsell|upsell\s*campaign/i', $key)) {
+ continue;
+ }
+
+ $val = $md->value ?? null;
+
+ // value kan array/object/string zijn
+ if (is_array($val)) {
+ $flat = json_encode($val);
+ if (preg_match_all('/\b\d+\b/', $flat, $m)) {
+ foreach ($m[0] as $id) $wpupsell_ids[] = (int) $id;
+ }
+ } elseif (is_object($val)) {
+ $flat = json_encode($val);
+ if (preg_match_all('/\b\d+\b/', $flat, $m)) {
+ foreach ($m[0] as $id) $wpupsell_ids[] = (int) $id;
+ }
+ } else {
+ $flat = (string) $val;
+ if (preg_match_all('/\b\d+\b/', $flat, $m)) {
+ foreach ($m[0] as $id) $wpupsell_ids[] = (int) $id;
+ }
+ }
+ }
+ }
+
+ // upsell + cross-sell + wpupsell bundelen
+ $upsell_ids = !empty($product->upsell_ids) ? array_map('intval', (array) $product->upsell_ids) : [];
+ $cross_sell_ids = !empty($product->cross_sell_ids) ? array_map('intval', (array) $product->cross_sell_ids) : [];
+
+ $cuw_ids = $cuw_map[(int)$product->id] ?? [];
+
+ $recommended_ids = array_values(array_unique(array_filter(array_merge(
+ $upsell_ids,
+ $cross_sell_ids,
+ $cuw_ids
+ ))));
+
+ // product naar array + velden toevoegen
+ $p = (array) $product;
+ $p['variation_details'] = $variation_details;
+
+ // expliciet meesturen voor frontend
+ $p['cuw_ids'] = $cuw_ids;
+ $p['recommended_ids'] = $recommended_ids;
+
+ $enriched[] = $p;
+ }
+
+ echo json_encode($enriched);
+ } catch (Exception $e) {
+ echo json_encode([]);
+ }
+ exit;
+}
+
+
+// --- CREATE ORDER ---
if ($action === 'create_order') {
$input = json_decode(file_get_contents('php://input'), true);
try {
$email = $input['billing']['email'];
$mediacode = $input['mediacode_internal'] ?? 'Geen';
- $shipping_incl_tax = (float)($input['shipping_total'] ?? 0);
- $wc_gateway_id = $input['payment_method'];
- $mollie_method = str_replace(['mollie_wc_gateway_', 'rve_'], '', $wc_gateway_id);
+
+ $input['payment_method'] = 'cod';
+ $input['payment_method_title'] = 'Sales Panel Order';
+ $input['status'] = 'on-hold';
- // 1. ACCOUNT SYNC
- $existing_customers = $woocommerce->get('customers', ['email' => $email]);
- $input['customer_id'] = !empty($existing_customers) ? $existing_customers[0]->id : 0;
+ $existing = $woocommerce->get('customers', ['email' => $email]);
+ $input['customer_id'] = !empty($existing) ? $existing[0]->id : 0;
- // 2. SHIPPING TAX FIX
- if ($shipping_incl_tax > 0) {
- $shipping_ex_tax = $shipping_incl_tax / 1.21;
- $input['shipping_lines'] = [['method_id' => 'flat_rate', 'method_title' => 'Verzendkosten', 'total' => number_format($shipping_ex_tax, 4, '.', '')]];
+ $shipping_incl = (float)($input['shipping_total'] ?? 0);
+ if ($shipping_incl > 0) {
+ $input['shipping_lines'] = [[
+ 'method_id' => 'flat_rate',
+ 'method_title' => 'Verzendkosten',
+ 'total' => number_format($shipping_incl / 1.21, 4, '.', '')
+ ]];
}
- $input['payment_method'] = $wc_gateway_id;
$input['customer_note'] = "Agent: {$_SESSION['user']} | Mediacode: $mediacode";
- // 3. ATTRIBUTION METADATA
+ // --- ATTRIBUTION DATA (GEFIXTE SYNTAX) ---
$input['meta_data'][] = ['key' => '_wc_order_attribution_source_type', 'value' => 'utm'];
$input['meta_data'][] = ['key' => '_wc_order_attribution_utm_source', 'value' => 'SalesPanel'];
$input['meta_data'][] = ['key' => '_wc_order_attribution_utm_campaign', 'value' => $mediacode];
$input['meta_data'][] = ['key' => 'Mediacode', 'value' => $mediacode];
+ $input['meta_data'][] = ['key' => 'Bron', 'value' => 'SalesPanel'];
- // ORDER AANMAKEN
$order = $woocommerce->post('orders', $input);
- // 4. TERMIJN CHECK
- if (in_array($mollie_method, ['in3', 'klarna', 'klarnapaylater', 'klarnasliceit', 'riverty']) && (float)$order->total < 100.00) {
- $woocommerce->delete("orders/{$order->id}", ['force' => true]);
- throw new Exception("Termijnbetaling pas vanaf €100,-");
- }
+ $action_type = 'order_created';
+ $log_stmt = $db->prepare("INSERT INTO sales_logs (username, action_type, order_id, amount, mediacode, customer_email, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())");
+ $log_stmt->bind_param("ssidss", $_SESSION['user'], $action_type, $order->id, $order->total, $mediacode, $email);
+ $log_stmt->execute();
- // 5. MOLLIE PAYMENT
- $mollie = new MollieApiClient();
- $mollie->setApiKey($_ENV['MOLLIE_KEY']);
-
- $paymentData = [
- "amount" => ["currency" => "EUR", "value" => number_format((float)$order->total, 2, '.', '')],
- "description" => "Order #{$order->id} [$mediacode]",
- "redirectUrl" => $_ENV['WC_URL'] . "/checkout/order-received/{$order->id}/?key={$order->order_key}&order_id={$order->id}&utm_source=SalesPanel&utm_campaign=" . urlencode($mediacode),
- "webhookUrl" => $_ENV['WC_URL'] . "/wc-api/{$wc_gateway_id}/?key={$order->order_key}&order_id={$order->id}",
- "method" => $mollie_method,
- "metadata" => ["order_id" => (string)$order->id, "mediacode" => $mediacode]
- ];
-
- if (in_array($mollie_method, ['in3', 'klarna', 'klarnapaylater', 'klarnasliceit', 'riverty'])) {
- if ($mollie_method === 'riverty') $paymentData["captureMode"] = "manual";
- $paymentData["billingAddress"] = ["givenName" => $input['billing']['first_name'], "familyName" => $input['billing']['last_name'], "email" => $input['billing']['email'], "streetAndNumber" => $input['billing']['address_1'], "city" => $input['billing']['city'], "postalCode" => $input['billing']['postcode'], "country" => "NL"];
- $paymentData["lines"] = [["name" => "Bestelling #" . $order->id, "quantity" => 1, "unitPrice" => ["currency" => "EUR", "value" => number_format((float)$order->total, 2, '.', '')], "totalAmount" => ["currency" => "EUR", "value" => number_format((float)$order->total, 2, '.', '')], "vatRate" => "21.00", "vatAmount" => ["currency" => "EUR", "value" => number_format((float)$order->total_tax, 2, '.', '')]]];
- }
-
- $payment = $mollie->payments->create($paymentData);
-
- // 7. FINISH ORDER
- $woocommerce->put("orders/{$order->id}", ['meta_data' => [['key' => '_mollie_payment_id', 'value' => $payment->id], ['key' => '_transaction_id', 'value' => $payment->id]]]);
- $woocommerce->post("orders/{$order->id}/notes", ['note' => "Voltooi de betaling of keur de incasso z.s.m. goed via de volgende link:
" . $payment->getCheckoutUrl(), 'customer_note' => true]);
- writeLog('ORDER_CREATED', "Order #{$order->id} voor {$input['billing']['email']}");
-
- echo json_encode(['payment_url' => $payment->getCheckoutUrl()]);
+ echo json_encode(['success' => true, 'order_id' => $order->id, 'total' => $order->total]);
} catch (Exception $e) {
- writeLog('ERROR', $e->getMessage());
http_response_code(422); echo json_encode(['error' => $e->getMessage()]);
}
exit;
diff --git a/index.html b/index.html
index f10ee0e..89085ad 100644
--- a/index.html
+++ b/index.html
@@ -2,19 +2,23 @@
Upsell Suggesties
- -Aanbevolen
+ + +Omzet Vandaag
+€
+Orders Vandaag
+ +| Tijd | +Agent | +Inhoud | +
|---|---|---|
| + | + | + + ORDER # + | € + + + + + + | +