feat(items): add server-side "export all" download for items table

Add ability to export the items manage-table results (respecting
current search/filters/sort) to downloadable files in multiple
formats, instead of only the on-screen page.

- app/Controllers/Items.php: new getExportAll(format) endpoint that
  re-runs the current search/filter query, builds raw export rows via
  getItemExportRow(), times db/build/write phases for debug logging,
  and streams back either a PDF response or a generic file download.
- app/Helpers/export_helper.php (new): shared writer dispatch
  (generateExportFile) supporting csv, txt, json, xml, sql, xlsx and
  pdf formats, with per-format value coercion (currency/quantity/
  numeric columns), XLSX temp-file caching with stale-file sweeping,
  and SQL INSERT generation keyed off the target table name.
- app/Helpers/tabular_helper.php: add itemExportHeaders() (manage
  table columns minus UI-only icon columns) and getItemExportRow()
  (unformatted raw values for export consumers); rename
  expand_attribute_values() to expandAttributeValues() and add a
  $raw flag to skip locale decimal formatting for exports.
- app/Controllers/Reports.php: update call sites for the
  expandAttributeValues() rename.
- app/Config/Autoload.php: autoload new 'export' helper.
- app/Config/Mimes.php: register .sql mime type for export downloads.
- app/Controllers/Items.php: getSearch() now defaults missing
  'search' param to '' instead of null.

Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
This commit is contained in:
objecttothis committed 2026-10-13 15:17:53 +04:00
1 parent c450418fbd
commit c004a3f412
13 files changed
+1670 -29

No files matched your search

+1
View File
@@ -201,6 +201,7 @@ class Autoload extends AutoloadConfig
'form',
'cookie',
'tabular',
'export',
'locale',
'security'
];
+4
View File
@@ -279,6 +279,10 @@ class Mimes
],
'txt' => 'text/plain',
'text' => 'text/plain',
'sql' => [
'application/sql',
'text/plain',
],
'log' => [
'text/plain',
'text/x-log',
+71 -1
View File
@@ -108,7 +108,7 @@ class Items extends Secure_Controller
**/
public function getSearch(): ResponseInterface
{
$search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? '';
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
@@ -162,6 +162,76 @@ class Items extends Secure_Controller
return $this->response->setJSON(['total' => $totalRows, 'rows' => $dataRows]);
}
/**
* Streams items matching the current search/filters as a downloadable file.
* With no limit/offset given, exports every matching row; otherwise exports just that page.
* @noinspection PhpUnused
**/
public function getExportAll(string $format): ResponseInterface
{
$search = $this->request->getGet('search', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? '';
$limit = $this->request->getGet('limit', FILTER_SANITIZE_NUMBER_INT);
$offset = $this->request->getGet('offset', FILTER_SANITIZE_NUMBER_INT);
$order = $this->request->getGet('order', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
$this->item_lib->set_item_location($this->request->getGet('stock_location'));
$definitionNamesWithTypes = $this->attribute->getDefinitionsByFlags(Attribute::SHOW_IN_ITEMS, true);
$definitionIds = array_keys($definitionNamesWithTypes);
$sort = $this->sanitizeSortColumn(itemSortColumns($definitionIds), $this->request->getGet('sort', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'items.item_id');
$filters = [
'start_date' => $this->request->getGet('start_date'),
'end_date' => $this->request->getGet('end_date'),
'stock_location_id' => $this->item_lib->get_item_location(),
'empty_upc' => false,
'low_inventory' => false,
'is_serialized' => false,
'no_description' => false,
'search_custom' => false,
'is_deleted' => false,
'temporary' => false,
'definition_ids' => $definitionIds
];
$requestFilters = array_fill_keys($this->request->getGet('filters', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? [], true);
$filters = array_merge($filters, $requestFilters);
if (!empty($filters['search_custom'])) {
$searchableDefinitions = $this->attribute->getDefinitionsByFlags(Attribute::SHOW_IN_ITEMS | Attribute::SHOW_IN_SEARCH);
$filters['definition_ids'] = array_keys($searchableDefinitions);
}
$dbStart = microtime(true);
$items = $this->item->search($search, $filters, $limit, $offset, $sort, $order);
$itemResults = $items->getResult();
$dbMs = (int) round((microtime(true) - $dbStart) * 1000);
$taxPercentsByItemId = $this->buildTaxPercentsByItem($itemResults);
$buildStart = microtime(true);
$exportRows = [];
foreach ($itemResults as $item) {
$exportRows[] = getItemExportRow($item, $definitionNamesWithTypes, $taxPercentsByItemId);
}
$buildMs = (int) round((microtime(true) - $buildStart) * 1000);
$writeStart = microtime(true);
$file = generateExportFile($format, itemExportHeaders(), $exportRows, 'items_' . date('Ymd_His'), $this->db->prefixTable('items'));
$writeMs = (int) round((microtime(true) - $writeStart) * 1000);
log_message('debug', "Export $format: db={$dbMs}ms build={$buildMs}ms write={$writeMs}ms rows=" . count($exportRows));
if ($format === 'pdf') {
return $this->response->setContentType('application/pdf')
->setHeader('Content-Disposition', 'attachment; filename="' . $file['filename'] . '"')
->setBody($file['body']);
}
return $this->response->download($file['filename'], $file['body'], true);
}
/**
* @return array item_id => formatted tax percents string
*/
+2 -2
View File
@@ -1851,7 +1851,7 @@ class Reports extends Secure_Controller
$quantity_purchased .= ' [' . $this->stock_location->get_location_name($drow['item_location']) . ']';
}
$attribute_values = expand_attribute_values($definition_names, $drow);
$attribute_values = expandAttributeValues($definition_names, $drow);
$details_data[$row['sale_id']][] = array_merge([
$drow['name'],
@@ -1991,7 +1991,7 @@ class Reports extends Secure_Controller
$quantity_purchased .= ' [' . $this->stock_location->get_location_name($drow['item_location']) . ']';
}
$attribute_values = expand_attribute_values($definition_names, $drow);
$attribute_values = expandAttributeValues($definition_names, $drow);
$details_data[$row['receiving_id']][] = array_merge([
$drow['item_number'],
+339
View File
@@ -0,0 +1,339 @@
<?php
/**
* Shared server-side export file generation for manage-table "Export all" downloads.
*
* @param array $headers Array of single-key [column_key => label] pairs, e.g. item_headers()
* @param iterable $rows Array/iterable of objects or arrays keyed by the same column_key values
* @param string $tableName Underlying DB table name, used for the SQL export's INSERT statements
*/
function generateExportFile(string $format, array $headers, iterable $rows, string $baseFilename, string $tableName): array
{
$writers = [
'csv' => fn () => writeExportCsv($headers, $rows, ','),
'txt' => fn () => writeExportCsv($headers, $rows, "\t"),
'json' => fn () => writeExportJson($headers, $rows),
'xml' => fn () => writeExportXml($headers, $rows),
'sql' => fn () => writeExportSql($headers, $rows, $tableName),
'xlsx' => fn () => writeExportXlsx($headers, $rows),
'pdf' => fn () => writeExportPdf($headers, $rows),
];
if (!isset($writers[$format])) {
throw new InvalidArgumentException("Unsupported export format: $format");
}
$extensions = [
'csv' => 'csv', 'txt' => 'txt', 'json' => 'json', 'xml' => 'xml',
'sql' => 'sql', 'xlsx' => 'xlsx', 'pdf' => 'pdf',
];
return [
'filename' => $baseFilename . '.' . $extensions[$format],
'body' => $writers[$format](),
];
}
const EXPORT_BLANKABLE_COLUMNS = ['tax_percents'];
const EXPORT_INTEGER_COLUMNS = ['items.item_id'];
const EXPORT_QUANTITY_COLUMNS = ['quantity'];
const EXPORT_CURRENCY_COLUMNS = ['cost_price', 'unit_price'];
const EXPORT_NUMERIC_IF_NUMERIC_COLUMNS = ['item_number'];
function exportRowValues(array $headers, object|array $row): array
{
$values = [];
foreach ($headers as $header) {
$key = array_key_first($header);
$property = str_contains($key, '.') ? substr($key, strrpos($key, '.') + 1) : $key;
$value = is_array($row) ? ($row[$property] ?? '') : ($row->$property ?? '');
if (in_array($key, EXPORT_BLANKABLE_COLUMNS, true) && $value === '-') {
$value = '';
}
$values[$key] = $value;
}
return $values;
}
function writeExportCsv(array $headers, iterable $rows, string $delimiter): string
{
$stream = fopen('php://temp', 'r+');
fputcsv($stream, array_map(static fn ($header) => reset($header), $headers), $delimiter, '"', '\\');
foreach ($rows as $row) {
fputcsv($stream, array_values(exportRowValues($headers, $row)), $delimiter, '"', '\\');
}
rewind($stream);
$body = stream_get_contents($stream);
fclose($stream);
return "\xEF\xBB\xBF" . $body;
}
function writeExportJson(array $headers, iterable $rows): string
{
$data = [];
foreach ($rows as $row) {
$data[] = exportRowValues($headers, $row);
}
return json_encode($data, JSON_PRETTY_PRINT);
}
function writeExportXml(array $headers, iterable $rows): string
{
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent(true);
$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('rows');
foreach ($rows as $row) {
$writer->startElement('row');
foreach (exportRowValues($headers, $row) as $key => $value) {
$writer->writeElement(sanitizeExportXmlTag($key), (string) $value);
}
$writer->endElement();
}
$writer->endElement();
$writer->endDocument();
return $writer->outputMemory();
}
function sanitizeExportXmlTag(string $key): string
{
return preg_replace('/[^a-zA-Z0-9_]/', '_', $key) ?: 'column';
}
function writeExportSql(array $headers, iterable $rows, string $tableName): string
{
$db = db_connect();
$columns = array_map(static fn ($header) => array_key_first($header), $headers);
$quotedColumns = implode(', ', array_map(static fn ($col) => '`' . $col . '`', $columns));
$lines = [];
foreach ($rows as $row) {
$values = array_values(exportRowValues($headers, $row));
$quotedValues = implode(', ', array_map(static fn ($value) => $db->escape($value), $values));
$lines[] = "INSERT INTO `$tableName` ($quotedColumns) VALUES ($quotedValues);";
}
return implode("\n", $lines);
}
/**
* Deletes export cache files left behind by a prior export that crashed or timed
* out before reaching its own cleanup (e.g. memory/timeout kills the request before
* the `finally` in writeExportXlsx() runs). Only sweeps files older than 10
* minutes - far longer than any real export should take - so a concurrent,
* still-in-progress export (different tab/user) is never touched.
*/
function sweepStaleExportCache(): void
{
$directory = WRITEPATH . 'cache/exports/';
if (!is_dir($directory)) {
return;
}
$staleBefore = time() - 600;
foreach (glob($directory . 'xlsx_*') as $file) {
if (is_file($file) && filemtime($file) < $staleBefore) {
unlink($file);
}
}
}
/**
* Column index (0-based, matching exportRowValues()'s order) => Excel NumberFormat
* code to apply to every cell in that column, or null if the column should stay text.
*/
function exportXlsxColumnFormats(array $headers): array
{
$quantityFormat = '0' . (quantity_decimals() > 0 ? '.' . str_repeat('0', quantity_decimals()) : '');
$currencyFormat = '0' . (totals_decimals() > 0 ? '.' . str_repeat('0', totals_decimals()) : '');
$formats = [];
foreach (array_values($headers) as $index => $header) {
$key = array_key_first($header);
if (in_array($key, EXPORT_INTEGER_COLUMNS, true) || in_array($key, EXPORT_NUMERIC_IF_NUMERIC_COLUMNS, true)) {
$formats[$index] = '0';
} elseif (in_array($key, EXPORT_QUANTITY_COLUMNS, true)) {
$formats[$index] = $quantityFormat;
} elseif (in_array($key, EXPORT_CURRENCY_COLUMNS, true)) {
$formats[$index] = $currencyFormat;
} elseif (($header['type'] ?? null) === DECIMAL) {
$formats[$index] = '0.00';
} else {
$formats[$index] = null;
}
}
return $formats;
}
function writeExportXlsx(array $headers, iterable $rows): string
{
sweepStaleExportCache();
$tempPath = WRITEPATH . 'cache/exports/xlsx_' . bin2hex(random_bytes(8)) . '.xlsx';
$columns = array_map(static fn ($header) => array_key_first($header), $headers);
$columnFormats = exportXlsxColumnFormats($headers);
$numericIfNumericIndexes = array_keys(array_intersect($columns, EXPORT_NUMERIC_IF_NUMERIC_COLUMNS));
$columnStyles = [];
foreach ($columnFormats as $index => $format) {
if ($format !== null) {
$columnStyles[$index] = new OpenSpout\Common\Entity\Style\Style(format: $format);
}
}
$writer = new OpenSpout\Writer\XLSX\Writer();
try {
$writer->openToFile($tempPath);
$writer->addRow(OpenSpout\Common\Entity\Row::fromValues(array_map(static fn ($header) => (string) reset($header), $headers)));
foreach ($rows as $row) {
$values = array_values(exportRowValues($headers, $row));
$cellValues = [];
foreach ($values as $index => $value) {
$format = $columnFormats[$index] ?? null;
if ($format !== null && in_array($index, $numericIfNumericIndexes, true) && !is_numeric($value)) {
$format = null;
}
$cellValues[$index] = ($format !== null && $value !== '' && is_numeric($value)) ? (float) $value : (string) $value;
}
$writer->addRow(OpenSpout\Common\Entity\Row::fromValuesWithStyles($cellValues, $columnStyles));
}
$writer->close();
return file_get_contents($tempPath);
} finally {
if (is_file($tempPath)) {
unlink($tempPath);
}
}
}
const EXPORT_PDF_PAGE_MARGIN = 10.0;
const EXPORT_PDF_FONT_SIZE = 9;
const EXPORT_PDF_ROW_HEIGHT = 6.0;
const EXPORT_PDF_AVG_CHAR_WIDTH_MM = 0.42 * EXPORT_PDF_FONT_SIZE * 25.4 / 72;
function exportPdfColumnWidths(array $headers, float $usableWidth): array
{
$labelLengths = array_map(static fn ($header) => max(mb_strlen((string) reset($header)), 6), $headers);
$totalLength = array_sum($labelLengths);
return array_map(static fn ($length) => $usableWidth * $length / $totalLength, $labelLengths);
}
function exportPdfTruncateCellText(string $text, float $columnWidth): string
{
$maxChars = max(1, (int) floor($columnWidth / EXPORT_PDF_AVG_CHAR_WIDTH_MM));
if (mb_strlen($text) <= $maxChars) {
return $text;
}
return mb_substr($text, 0, max(1, $maxChars - 1)) . '…';
}
function writeExportPdf(array $headers, iterable $rows): string
{
$buildStart = microtime(true);
set_time_limit(300);
define('K_PATH_FONTS', realpath(APPPATH . '../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'));
$pdf = new Com\Tecnick\Pdf\Tcpdf(unit: 'mm', subsetfont: true);
$font = $pdf->font->insert($pdf->pon, 'dejavusans', '', EXPORT_PDF_FONT_SIZE);
$headerValues = array_values(array_map(static fn ($header) => (string) reset($header), $headers));
$startNewPage = function () use ($pdf, $font): array {
$page = $pdf->addPage(['format' => 'A4', 'orientation' => 'L']);
$pdf->page->addContent($font['out']);
return $page;
};
$page = $startNewPage();
$pageHeight = $page['height'];
$usableWidth = $page['width'] - (2 * EXPORT_PDF_PAGE_MARGIN);
$columnWidths = array_values(exportPdfColumnWidths($headers, $usableWidth));
$drawRow = function (array $values, float $posY) use ($pdf, $columnWidths): void {
$posX = EXPORT_PDF_PAGE_MARGIN;
foreach ($values as $index => $value) {
$text = exportPdfTruncateCellText((string) $value, $columnWidths[$index]);
$out = $pdf->getTextCell(
txt: $text,
posx: $posX,
posy: $posY,
width: $columnWidths[$index],
height: EXPORT_PDF_ROW_HEIGHT,
halign: 'L',
valign: 'T',
);
$pdf->page->addContent($out);
$posX += $columnWidths[$index];
}
};
$drawHeaderRow = function (float $posY) use ($pdf, $font, $headerValues, $drawRow): void {
$boldFont = $pdf->font->cloneFont($pdf->pon, $font['idx'], 'B');
$pdf->page->addContent($boldFont['out']);
$drawRow($headerValues, $posY);
$regularFont = $pdf->font->cloneFont($pdf->pon, $boldFont['idx'], '');
$pdf->page->addContent($regularFont['out']);
};
$posY = EXPORT_PDF_PAGE_MARGIN;
$drawHeaderRow($posY);
$posY += EXPORT_PDF_ROW_HEIGHT;
$rowCount = 0;
foreach ($rows as $row) {
if ($posY + EXPORT_PDF_ROW_HEIGHT > $pageHeight - EXPORT_PDF_PAGE_MARGIN) {
$startNewPage();
$posY = EXPORT_PDF_PAGE_MARGIN;
$drawHeaderRow($posY);
$posY += EXPORT_PDF_ROW_HEIGHT;
}
$drawRow(array_values(exportRowValues($headers, $row)), $posY);
$posY += EXPORT_PDF_ROW_HEIGHT;
$rowCount++;
}
$buildMs = (int) round((microtime(true) - $buildStart) * 1000);
log_message('debug', "Export pdf write: build={$buildMs}ms rows=$rowCount");
return $pdf->getOutPDFString();
}
+84 -18
View File
@@ -452,6 +452,34 @@ function getItemsManageTableHeaders(): string
return transform_headers($headers);
}
/**
* Column set for exporting items: same data columns the manage table shows,
* minus the icon/action columns (image thumbnail, inventory/stock/edit icons)
* that only make sense in the live UI.
*
* @return array Array of single-key [column_key => label] pairs, see item_headers()
*/
function itemExportHeaders(): array
{
$attribute = model(Attribute::class);
$config = config(OSPOS::class)->settings;
$definitionsWithTypes = $attribute->getDefinitionsByFlags($attribute::SHOW_IN_ITEMS, true);
$headers = item_headers();
if ($config['use_destination_based_tax']) {
$headers[] = ['tax_percents' => lang('Items.tax_category')];
} else {
$headers[] = ['tax_percents' => lang('Items.tax_percents')];
}
foreach ($definitionsWithTypes as $definitionId => $definitionInfo) {
$headers[] = [$definitionId => $definitionInfo['name'], 'type' => $definitionInfo['type']];
}
return $headers;
}
/**
* Get the html data row for the item
*
@@ -528,7 +556,43 @@ function getItemDataRow(object $item, array $definitionNames, array $taxPercents
)
];
return $columns + expand_attribute_values($definitionNames, (array) $item) + $icons;
return $columns + expandAttributeValues($definitionNames, (array) $item) + $icons;
}
/**
* Export row for an item: raw (unformatted) values, matching itemExportHeaders()'s
* column set - no locale display formatting, no icons/HTML, since consumers (CSV/XLSX/etc
* writers) each apply their own number formatting as appropriate for the target format.
*
* @param object $item
* @param array $definitionNames Attribute definitions with types, keyed by definition_id (see Attribute::getDefinitionsByFlags(..., true))
* @param array $taxPercentsByItemId Pre-computed tax percent strings, keyed by item_id (see Items::buildTaxPercentsByItem())
* @return array
*/
function getItemExportRow(object $item, array $definitionNames, array $taxPercentsByItemId): array
{
$config = config(OSPOS::class)->settings;
$taxPercents = $taxPercentsByItemId[$item->item_id] ?? '';
$name = $item->name;
if ($config['multi_pack_enabled']) {
$name .= NAME_SEPARATOR . $item->pack_name;
}
$columns = [
'items.item_id' => $item->item_id,
'item_number' => $item->item_number,
'name' => $name,
'category' => $item->category,
'company_name' => $item->company_name,
'cost_price' => $item->cost_price,
'unit_price' => $item->unit_price,
'quantity' => $item->quantity ?? '',
'tax_percents' => $taxPercents
];
return $columns + expandAttributeValues($definitionNames, (array) $item, raw: true);
}
function giftcard_headers(): array
@@ -639,39 +703,41 @@ function parse_attribute_values(array $columns, array $row): array
}
/**
* @param array $definition_names Array of definition_id => ['name' => name, 'type' => type] or definition_id => name
* @param array $definitionNames Array of definition_id => ['name' => name, 'type' => type] or definition_id => name
* @param array $row
* @param bool $raw When true, skip locale formatting of DECIMAL attributes and return the raw value instead
* @return array
*/
function expand_attribute_values(array $definition_names, array $row): array
function expandAttributeValues(array $definitionNames, array $row, bool $raw = false): array
{
$values = parse_attribute_values(['attribute_values', 'attribute_dtvalues', 'attribute_dvalues'], $row);
$indexed_values = [];
foreach ($values as $attribute_value) {
$exploded_value = explode('_', $attribute_value);
if (sizeof($exploded_value) > 1) {
$indexed_values[$exploded_value[0]] = $exploded_value[1];
$indexedValues = [];
foreach ($values as $attributeValue) {
$explodedValue = explode('_', $attributeValue);
if (sizeof($explodedValue) > 1) {
$indexedValues[$explodedValue[0]] = $explodedValue[1];
}
}
$attribute_values = [];
foreach ($definition_names as $definition_id => $definitionInfo) {
if (isset($indexed_values[$definition_id])) {
$raw_value = $indexed_values[$definition_id];
$attributeValues = [];
foreach ($definitionNames as $definitionId => $definitionInfo) {
if (isset($indexedValues[$definitionId])) {
$rawValue = $indexedValues[$definitionId];
$isDecimal = is_array($definitionInfo) && isset($definitionInfo['type']) && $definitionInfo['type'] === DECIMAL;
// Format DECIMAL attributes according to locale
if (is_array($definitionInfo) && isset($definitionInfo['type']) && $definitionInfo['type'] === DECIMAL) {
$attribute_values["$definition_id"] = to_decimals($raw_value);
// Format DECIMAL attributes according to locale, unless the raw value was requested
if ($isDecimal && !$raw) {
$attributeValues["$definitionId"] = to_decimals($rawValue);
} else {
$attribute_values["$definition_id"] = $raw_value;
$attributeValues["$definitionId"] = $rawValue;
}
} else {
$attribute_values["$definition_id"] = "";
$attributeValues["$definitionId"] = "";
}
}
return $attribute_values;
return $attributeValues;
}
function attribute_definition_headers(): array
+14 -2
View File
@@ -31,9 +31,12 @@
"dompdf/dompdf": "^3.1.6",
"ezyang/htmlpurifier": "^4.17",
"laminas/laminas-escaper": "2.18.0",
"openspout/openspout": "^5.3",
"paragonie/random_compat": "^2.0.21",
"picqer/php-barcode-generator": "^2.4.0",
"tamtamchik/namecase": "^3.0.0"
"tamtamchik/namecase": "^3.0.0",
"tecnickcom/tc-lib-pdf": "^8.78",
"tecnickcom/tc-lib-pdf-font": "^4.5"
},
"require-dev": {
"codeigniter/coding-standard": "^1.8",
@@ -71,7 +74,16 @@
"sort-packages": true
},
"scripts": {
"test": "phpunit"
"test": "phpunit",
"tc-lib-pdf-fonts": [
"@php vendor/tecnickcom/tc-lib-pdf-font/util/build_fonts.php"
],
"post-install-cmd": [
"@tc-lib-pdf-fonts"
],
"post-update-cmd": [
"@tc-lib-pdf-fonts"
]
},
"scripts-descriptions": {
"test": "Run unit tests"
Generated
+1104 -1
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -90,5 +90,8 @@
},
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"allowScripts": {
"core-js@3.37.0": true
}
}
+45 -2
View File
@@ -226,9 +226,11 @@
search: options.resource || false,
showColumns: true,
clickToSelect: true,
// showExport/exportTypes only render the button+dropdown; init_export_all_intercept()
// always wins the click and routes every format to the server, bypassing this plugin's own export.
showExport: true,
exportDataType: 'basic',
exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel', 'pdf'],
exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'xlsx', 'pdf'],
exportOptions: {
fileName: options.resource.replace(/.*\/(.*?)$/g, '$1') + "_" + export_suffix
},
@@ -266,9 +268,30 @@
init_restore();
toggle_column_visibility();
init_export_all_toggle();
init_export_all_intercept();
dialog_support.init("button.modal-dlg");
};
let export_all_enabled = false;
const init_export_all_intercept = function () {
document.addEventListener('click', function(event) {
const $exportItem = $(event.target).closest('.export [data-type], .export[data-type]');
if (!$exportItem.length) {
return;
}
const format = $exportItem.attr('data-type') || $exportItem.closest('.export').data('type');
event.preventDefault();
event.stopImmediatePropagation();
$exportItem.closest('.dropdown-menu').prev('[data-toggle="dropdown"]').dropdown('toggle');
const queryString = export_all_enabled ? export_all_query_string() : export_page_query_string();
window.location = options.resource + '/exportAll/' + format + '?' + queryString;
}, true);
};
const init_export_all_toggle = function () {
const $toggleInput = $('<input>', {
type: 'checkbox',
@@ -301,10 +324,30 @@
$toggleInput.on('change', function() {
$toggleWrapper.attr('aria-checked', this.checked);
$('#table').bootstrapTable('refreshOptions', { exportDataType: this.checked ? 'all' : 'basic' });
export_all_enabled = this.checked;
});
};
const export_all_query_string = function() {
const queryParamsFn = $('#table').bootstrapTable('getOptions').queryParams;
const params = queryParamsFn ? queryParamsFn({}) : {};
delete params.limit;
delete params.offset;
return $.param(params);
};
const export_page_query_string = function() {
const tableOptions = $('#table').bootstrapTable('getOptions');
const queryParamsFn = tableOptions.queryParams;
const params = queryParamsFn ? queryParamsFn({
limit: tableOptions.pageSize,
offset: (tableOptions.pageNumber - 1) * tableOptions.pageSize
}) : {};
return $.param(params);
};
const init_delete = function (confirmMessage) {
$("#delete").click(function(event) {
do_action("delete")();
+1 -1
View File
@@ -18,7 +18,7 @@ class ItemSearchTest extends CIUnitTestCase
protected $migrateOnce = true;
protected $seed = '';
protected $seedOnce = true;
protected $refresh = true;
protected $refresh = false;
protected $namespace = null;
protected $item;
+1 -1
View File
@@ -17,7 +17,7 @@ class ItemTaxesMultipleTest extends CIUnitTestCase
protected $migrateOnce = true;
protected $seed = '';
protected $seedOnce = true;
protected $refresh = true;
protected $refresh = false;
protected $namespace = null;
protected $item_taxes;
+1 -1
View File
@@ -22,7 +22,7 @@ class ReceivingTest extends CIUnitTestCase
protected $migrate = true;
protected $migrateOnce = true;
protected $refresh = true;
protected $refresh = false;
protected $namespace = null;
private const LOCATION_ID = 1;