mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
added xlsx js for excel
This commit is contained in:
@@ -24,9 +24,11 @@ const AUDIT_ID_FIELDS = new Set(["createdBy", "updatedBy", "deletedBy"]);
|
||||
* "column reference is ambiguous" as soon as a query joins another table
|
||||
* that happens to share a column name (e.g. Users + UserGroups both have
|
||||
* is_active/createdAt/updatedAt/deletedAt).
|
||||
* @param {Set<string>} [dateFields] - fields whose picklist values are
|
||||
* calendar days (see below) — matched by range, not substring.
|
||||
* @returns {Object} Sequelize where clause
|
||||
*/
|
||||
function buildWhere(filters = [], allowedFields = new Set(), parentAlias = null) {
|
||||
function buildWhere(filters = [], allowedFields = new Set(), parentAlias = null, dateFields = new Set()) {
|
||||
const where = [];
|
||||
|
||||
for (const { id, value } of filters) {
|
||||
@@ -42,6 +44,31 @@ function buildWhere(filters = [], allowedFields = new Set(), parentAlias = null)
|
||||
|
||||
const qualifiedCol = parentAlias ? `${parentAlias}.${id}` : id;
|
||||
|
||||
// Date/timestamp columns (createdAt, updatedAt, ...) — the filter sheet's
|
||||
// picklist values are whole calendar days (e.g. "2026-07-16"), but the
|
||||
// column itself is a full timestamp. Casting the timestamp to TEXT and
|
||||
// doing a substring iLike match against just the date portion is a loose
|
||||
// match: it also picks up every OTHER row whose time-of-day component
|
||||
// happens to render into digits that appear elsewhere in the cast string,
|
||||
// so a single selected day can silently pull in unrelated rows. Match by
|
||||
// an explicit [dayStart, nextDayStart) range on the real column instead —
|
||||
// exact, and immune to how the DB happens to stringify the timestamp.
|
||||
if (dateFields.has(id) && !id.startsWith("personal_info.")) {
|
||||
const conditions = values.map((v) => {
|
||||
const datePart = String(v).slice(0, 10);
|
||||
const dayStart = new Date(`${datePart}T00:00:00.000Z`);
|
||||
const dayEnd = new Date(dayStart.getTime() + 24 * 60 * 60 * 1000);
|
||||
|
||||
return Sequelize.where(Sequelize.col(qualifiedCol), {
|
||||
[Op.gte]: dayStart,
|
||||
[Op.lt]: dayEnd,
|
||||
});
|
||||
});
|
||||
|
||||
where.push({ [Op.or]: conditions });
|
||||
continue;
|
||||
}
|
||||
|
||||
const conditions = values.map((v) =>
|
||||
id.startsWith("personal_info.")
|
||||
? Sequelize.where(
|
||||
@@ -114,17 +141,19 @@ function buildOrder(sort = [], allowedFields = new Set(), computedFields = new S
|
||||
*
|
||||
* @param {Array} filters
|
||||
* @param {Array} sort
|
||||
* @param {Array} [dateFields] - fields to match by day-range instead of substring
|
||||
* @returns {{ where: Object, order: Array }}
|
||||
*/
|
||||
function buildQuery(filters = [], sort = [], allowedFields = [], computedFields = [], parentAlias = null) {
|
||||
function buildQuery(filters = [], sort = [], allowedFields = [], computedFields = [], parentAlias = null, dateFields = []) {
|
||||
const fieldSet = new Set(allowedFields);
|
||||
const computedSet = new Set(computedFields);
|
||||
const orderFieldSet = new Set([...allowedFields, ...computedFields]);
|
||||
const dateFieldSet = new Set(dateFields);
|
||||
|
||||
return {
|
||||
// Computed (subquery/literal) columns aren't real table columns — filtering
|
||||
// via Sequelize.col() would error, so only allow them in ORDER BY, not WHERE.
|
||||
where: buildWhere(filters, fieldSet, parentAlias),
|
||||
where: buildWhere(filters, fieldSet, parentAlias, dateFieldSet),
|
||||
order: buildOrder(sort, orderFieldSet, computedSet, parentAlias),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -110,11 +110,12 @@ async function paginate(model, req, {
|
||||
const attributes = modelToAttributes(model, { exclude: excludeAttributes, jsonbSchemas, context });
|
||||
const ALLOWED_FIELDS = attributes.map((a) => a.field);
|
||||
const computedFieldKeys = computedAttributes.map((c) => c.key);
|
||||
const dateFieldKeys = attributes.filter((a) => a.type === 'date').map((a) => a.field);
|
||||
|
||||
// Sequelize aliases the main model's table with the model's name by default
|
||||
// (e.g. `FROM "users" AS "User"`) — needed to qualify Sequelize.col()
|
||||
// references so they don't collide with same-named columns on joined tables.
|
||||
const { where, order } = buildQuery(filters, sort, ALLOWED_FIELDS, computedFieldKeys, model.name);
|
||||
const { where, order } = buildQuery(filters, sort, ALLOWED_FIELDS, computedFieldKeys, model.name, dateFieldKeys);
|
||||
|
||||
// Build attribute includes: jsonb + audit subqueries + any extra from findOptions
|
||||
const baseIncludes = jsonbAttr ? [jsonbAttr] : [];
|
||||
|
||||
Reference in New Issue
Block a user