113 lines
4.0 KiB
JavaScript

/**
* Shared export utilities for OData, CSV, and Excel.
* Centralizes filter fields, order-by parsing, and row flattening.
*/
/** Allowed filter fields per object type for OData/CSV/Excel exports */
export const EXPORT_FILTER_BY_TYPE = {
note: ['parent._id', 'noteType', 'user'],
notification: ['user'],
userNotifier: ['user', 'object', 'objectType'],
printer: ['host'],
job: ['printer', 'gcodeFile'],
subJob: ['job'],
filamentStock: ['filamentSku'],
filament: ['material', 'material._id', 'name', 'diameter', 'cost'],
filamentSku: ['filament', 'vendor', 'costTaxRate'],
material: ['name', 'tags'],
partStock: ['partSku'],
partSku: ['part', 'vendor', 'priceTaxRate', 'costTaxRate'],
productStock: ['productSku'],
productSku: ['product', 'vendor', 'priceTaxRate', 'costTaxRate'],
purchaseOrder: ['vendor'],
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'sku._id', 'shipment._id'],
shipment: ['order._id', 'orderType', 'courierService._id'],
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
stockLocation: ['name'],
stockTransfer: ['state.type', 'postedAt'],
stockAudit: ['filamentStock._id', 'partStock._id'],
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],
documentTemplate: ['parent._id', 'documentSize._id'],
salesOrder: ['client'],
invoice: ['to._id', 'from._id', 'order._id', 'orderType'],
auditLog: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
appPassword: ['name', 'user', 'active'],
};
/**
* Get allowed filter fields for a given object type.
* @param {string} objectType - Model type (e.g. 'filament', 'material')
* @returns {string[]} Allowed filter field names
*/
export function getModelFilterFields(objectType) {
const base = ['_id'];
const extra = EXPORT_FILTER_BY_TYPE[objectType] || [];
return [...base, ...extra];
}
/**
* Parse OData $orderby or orderby string into sort and order.
* Supports "field asc", "field desc", or just "field" (defaults asc).
* @param {string} [orderby] - Orderby string (e.g. "createdAt desc")
* @returns {{ sort: string, order: 'ascend'|'descend' }}
*/
export function parseOrderBy(orderby) {
if (!orderby || typeof orderby !== 'string') {
return { sort: 'createdAt', order: 'ascend' };
}
const trimmed = orderby.trim();
const parts = trimmed.split(/\s+/);
const sort = parts[0] || 'createdAt';
const dir = (parts[1] || 'asc').toLowerCase();
const order = dir === 'desc' ? 'descend' : 'ascend';
return { sort, order };
}
/**
* Flatten nested objects for export display.
* Objects become "key.subkey: value"; arrays become comma-separated strings.
* @param {*} obj - Value to flatten
* @param {string} [prefix=''] - Key prefix for nested values
* @returns {Object} Flat key-value object
*/
export function flattenForExport(obj, prefix = '') {
if (obj === null || obj === undefined) return {};
if (typeof obj !== 'object') return { [prefix]: obj };
if (obj instanceof Date) return { [prefix]: obj };
if (Array.isArray(obj)) {
const str = obj
.map((v) => (v && typeof v === 'object' && !(v instanceof Date) ? JSON.stringify(v) : v))
.join(', ');
return { [prefix]: str };
}
const result = {};
for (const [k, v] of Object.entries(obj)) {
const key = prefix ? `${prefix}.${k}` : k;
if (v !== null && typeof v === 'object' && !(v instanceof Date) && !Array.isArray(v)) {
Object.assign(result, flattenForExport(v, key));
} else {
result[key] = v;
}
}
return result;
}
/**
* Convert a row (e.g. OData value item) to flat key-value for CSV/Excel.
* Nested objects are flattened; @odata.* keys are skipped.
* @param {Object} row - Row object
* @returns {Object} Flat key-value object
*/
export function rowToFlat(row) {
const flat = {};
for (const [key, val] of Object.entries(row)) {
if (key.startsWith('@')) continue;
if (val !== null && typeof val === 'object' && !(val instanceof Date) && !Array.isArray(val)) {
Object.assign(flat, flattenForExport(val, key));
} else {
flat[key] = val;
}
}
return flat;
}