| Server IP : 173.209.174.21 / Your IP : 216.73.216.89 Web Server : Apache/2.4.58 (Ubuntu) System : Linux wcfs-server 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64 User : nodor ( 1000) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/wcfs/tabs/ |
Upload File : |
<?php
require_once __DIR__ . '/store_settings.php';
require_once __DIR__ . '/qbo_name_map_helpers.php';
if (file_exists(__DIR__ . '/export_private_config.php')) {
require_once __DIR__ . '/export_private_config.php';
}
function privateCategorizerDir()
{
if (defined('PRIVATE_CATEGORIZER_DIR')) {
$configured = rtrim((string) PRIVATE_CATEGORIZER_DIR, '/\\');
if ($configured !== '' && is_dir($configured)) {
return $configured;
}
}
$candidates = array(
dirname(__DIR__) . '/categorize',
__DIR__ . '/scripts/categorize',
);
foreach ($candidates as $candidate) {
if (!is_dir($candidate)) {
continue;
}
if (file_exists($candidate . '/item_map.json') || file_exists($candidate . '/category_rules.json') || file_exists($candidate . '/autosave.php')) {
return $candidate;
}
}
foreach ($candidates as $candidate) {
if (is_dir($candidate)) {
return $candidate;
}
}
return dirname(__DIR__) . '/categorize';
}
function privateCategorizerRules()
{
$rulesPath = privateCategorizerDir() . '/category_rules.json';
$rules = json_decode(@file_get_contents($rulesPath), true);
if (!$rules || !is_array($rules)) {
$rules = array(
'No Tax Grocery' => array(),
'Grocery (Taxable)' => array(),
'Hardware' => array(),
'Feed' => array(),
'Oil' => array(),
'Unleaded' => array(),
'Diesel' => array(),
'Off Road Diesel' => array(),
'Misc' => array(),
);
}
return $rules;
}
function privateCategorizerMap()
{
$mapPath = privateCategorizerDir() . '/item_map.json';
$map = json_decode(@file_get_contents($mapPath), true);
if (!$map || !is_array($map)) {
$map = array();
}
return $map;
}
function privateMergedCategorizerMap($overrideMap = array())
{
$map = privateCategorizerMap();
if (!is_array($overrideMap)) {
return $map;
}
foreach ($overrideMap as $item => $category) {
$itemKey = strtolower(trim((string) $item));
$categoryValue = trim((string) $category);
if ($itemKey === '' || $categoryValue === '') {
continue;
}
$map[$itemKey] = $categoryValue;
}
return $map;
}
function privateMergedItemLabel($itemName, $notes)
{
$itemName = trim((string) $itemName);
$notes = trim((string) $notes);
if ($notes === '') {
return $itemName;
}
if ($itemName === '') {
return $notes;
}
return $itemName . ', ' . $notes;
}
function privateCategorizeItem($itemLabel, $rules, $map)
{
$lower = strtolower(trim((string) $itemLabel));
if ($lower === '') {
return 'Misc';
}
if (isset($map[$lower]) && trim((string) $map[$lower]) !== '') {
return (string) $map[$lower];
}
foreach ($rules as $category => $keywords) {
if (!is_array($keywords)) {
continue;
}
foreach ($keywords as $keyword) {
$keyword = trim((string) $keyword);
if ($keyword !== '' && strpos($lower, strtolower($keyword)) !== false) {
return (string) $category;
}
}
}
return 'Misc';
}
function normalizePrivateExportDate($date)
{
$date = trim((string) $date);
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
return '';
}
return $date;
}
function privateExportDateRange($month, $startDate = '', $endDate = '')
{
$startDate = normalizePrivateExportDate($startDate);
$endDate = normalizePrivateExportDate($endDate);
if ($startDate !== '' && $endDate !== '') {
if (strtotime($startDate) > strtotime($endDate)) {
throw new RuntimeException('The export start date must be before the end date.');
}
return array($startDate, $endDate);
}
if (!preg_match('/^\d{4}-\d{2}$/', (string) $month)) {
throw new RuntimeException('Please choose a valid export month.');
}
$startDate = $month . '-01';
$endDate = date('Y-m-t', strtotime($startDate));
return array($startDate, $endDate);
}
function privateExportRangeLabel($month, $startDate = '', $endDate = '')
{
list($rangeStart, $rangeEnd) = privateExportDateRange($month, $startDate, $endDate);
if ($rangeStart === $rangeEnd) {
return date('F j, Y', strtotime($rangeStart));
}
return date('F j, Y', strtotime($rangeStart)) . ' to ' . date('F j, Y', strtotime($rangeEnd));
}
function privatePreparedExportRows($conn, $month, $includeArchived, $mapQboNames, $overrideMap = array(), $startDate = '', $endDate = '')
{
list($rangeStart, $rangeEnd) = privateExportDateRange($month, $startDate, $endDate);
$rows = exportRowsForDateRange($conn, $rangeStart, $rangeEnd, $includeArchived);
$taxRate = getStoreTaxRate($conn);
$qboNameMap = $mapQboNames ? qboNameMapLoadSaved($conn) : array();
$categoryRules = privateCategorizerRules();
$itemMap = privateMergedCategorizerMap($overrideMap);
$prepared = array();
foreach ($rows as $row) {
$quantity = isset($row['quantity']) ? (float) $row['quantity'] : 0;
$price = isset($row['price']) ? (float) $row['price'] : 0;
$subtotal = $quantity * $price;
$isTaxable = !empty($row['taxable']);
$tax = $isTaxable ? $subtotal * $taxRate : 0.0;
$total = $subtotal + $tax;
$firstName = isset($row['first_name']) ? (string) $row['first_name'] : '';
$lastName = isset($row['last_name']) ? (string) $row['last_name'] : '';
$customer = trim($firstName . ' ' . $lastName);
$mappedCustomer = $customer;
$itemName = isset($row['item_name']) ? (string) $row['item_name'] : '';
$notes = isset($row['notes']) ? (string) $row['notes'] : '';
$mergedItem = privateMergedItemLabel($itemName, $notes);
$baseItem = trim($itemName);
$category = privateCategorizeItem($baseItem, $categoryRules, $itemMap);
if ($mapQboNames) {
$normalizedCustomer = strtolower(trim($customer));
if (isset($qboNameMap[$normalizedCustomer])) {
$mappedCustomer = $qboNameMap[$normalizedCustomer];
}
}
$prepared[] = array(
'Date' => isset($row['transaction_date']) ? (string) $row['transaction_date'] : '',
'Customer' => $mappedCustomer,
'BaseItem' => $baseItem,
'Item' => $mergedItem,
'Category' => $category,
'Quantity' => number_format($quantity, 2, '.', ''),
'Price' => number_format($price, 2, '.', ''),
'Subtotal' => number_format($subtotal, 2, '.', ''),
'Taxable' => $isTaxable ? 'Yes' : 'No',
'Tax' => number_format($tax, 2, '.', ''),
'Total' => number_format($total, 2, '.', ''),
'Notes' => $notes,
'Source' => isset($row['source_type']) ? (string) $row['source_type'] : 'current',
);
}
return $prepared;
}
function exportRowsForMonth($conn, $month, $includeArchived)
{
if (!preg_match('/^\d{4}-\d{2}$/', $month)) {
throw new RuntimeException('Please choose a valid export month.');
}
$startDate = $month . '-01';
$endDate = date('Y-m-t', strtotime($startDate));
return exportRowsForDateRange($conn, $startDate, $endDate, $includeArchived);
}
function exportRowsForDateRange($conn, $startDate, $endDate, $includeArchived)
{
$startDate = normalizePrivateExportDate($startDate);
$endDate = normalizePrivateExportDate($endDate);
if ($startDate === '' || $endDate === '') {
throw new RuntimeException('Please choose a valid export date range.');
}
if (strtotime($startDate) > strtotime($endDate)) {
throw new RuntimeException('The export start date must be before the end date.');
}
$rows = array();
$currentStmt = $conn->prepare("
SELECT
t.date AS transaction_date,
c.first_name,
c.last_name,
t.item_name,
t.quantity,
t.price,
t.taxable,
t.notes,
'current' AS source_type
FROM transactions t
INNER JOIN customers c ON c.id = t.customer_id
WHERE t.date BETWEEN ? AND ?
ORDER BY t.date ASC, c.last_name ASC, c.first_name ASC, t.id ASC
");
$currentStmt->bind_param('ss', $startDate, $endDate);
$currentStmt->execute();
$currentResult = $currentStmt->get_result();
while ($row = $currentResult->fetch_assoc()) {
$rows[] = $row;
}
$currentStmt->close();
if ($includeArchived) {
$archivedStmt = $conn->prepare("
SELECT
a.original_date AS transaction_date,
c.first_name,
c.last_name,
a.item_name,
a.quantity,
a.price,
a.taxable,
a.notes,
'archived' AS source_type
FROM archived_transactions a
INNER JOIN customers c ON c.id = a.customer_id
WHERE a.original_date BETWEEN ? AND ?
ORDER BY a.original_date ASC, c.last_name ASC, c.first_name ASC, a.id ASC
");
$archivedStmt->bind_param('ss', $startDate, $endDate);
$archivedStmt->execute();
$archivedResult = $archivedStmt->get_result();
while ($row = $archivedResult->fetch_assoc()) {
$rows[] = $row;
}
$archivedStmt->close();
}
usort($rows, static function ($a, $b) {
return array($a['transaction_date'], $a['last_name'], $a['first_name'], $a['item_name'])
<=>
array($b['transaction_date'], $b['last_name'], $b['first_name'], $b['item_name']);
});
return $rows;
}
function exportCsvStringForMonth($conn, $month, $includeArchived, $mapQboNames, $overrideMap = array())
{
$preparedRows = privatePreparedExportRows($conn, $month, $includeArchived, $mapQboNames, $overrideMap);
$output = fopen('php://temp', 'r+');
if ($output === false) {
throw new RuntimeException('Could not generate CSV.');
}
fputcsv($output, array(
'Date',
'Customer',
'Item',
'Category',
'Quantity',
'Price',
'Subtotal',
'Taxable',
'Tax',
'Total',
'Notes',
'Source',
));
foreach ($preparedRows as $row) {
fputcsv($output, array(
isset($row['Date']) ? $row['Date'] : '',
isset($row['Customer']) ? $row['Customer'] : '',
isset($row['Item']) ? $row['Item'] : '',
isset($row['Category']) ? $row['Category'] : '',
isset($row['Quantity']) ? $row['Quantity'] : '',
isset($row['Price']) ? $row['Price'] : '',
isset($row['Subtotal']) ? $row['Subtotal'] : '',
isset($row['Taxable']) ? $row['Taxable'] : '',
isset($row['Tax']) ? $row['Tax'] : '',
isset($row['Total']) ? $row['Total'] : '',
isset($row['Notes']) ? $row['Notes'] : '',
isset($row['Source']) ? $row['Source'] : '',
));
}
rewind($output);
$csv = stream_get_contents($output);
fclose($output);
if ($csv === false) {
throw new RuntimeException('Could not generate CSV.');
}
return $csv;
}
function exportFilenameForStore($storeName, $month, $mapQboNames, $startDate = '', $endDate = '')
{
$base = trim($storeName);
if ($base === '') {
$base = 'HouseTabPro';
}
$safe = preg_replace('/[^A-Za-z0-9]+/', '_', $base);
$safe = trim((string) $safe, '_');
if ($safe === '') {
$safe = 'HouseTabPro';
}
$startDate = normalizePrivateExportDate($startDate);
$endDate = normalizePrivateExportDate($endDate);
if ($startDate !== '' && $endDate !== '') {
$dateLabel = str_replace('-', '_', $startDate) . '_to_' . str_replace('-', '_', $endDate);
} else {
$dateLabel = preg_match('/^\d{4}-\d{2}$/', $month) ? date('Y_m', strtotime($month . '-01')) : date('Y_m');
}
return $safe . '_export_' . $dateLabel . ($mapQboNames ? '_qbo_mapped' : '') . '.csv';
}