Compare commits

...

2 Commits

Author SHA1 Message Date
8974d1e9da Add quantity field to document job schema and update related routes and services
All checks were successful
farmcontrol/farmcontrol-api/pipeline/head This commit looks good
This commit introduces a new `quantity` field to the document job schema, ensuring it is required with a default value of 1. The routes and services for managing document jobs are updated to include this new field, allowing for its inclusion in both creation and editing processes. Additionally, the allowed filters and sorters are modified to accommodate the new `quantity` field, enhancing the overall functionality and flexibility of document job management.
2026-08-22 20:02:28 +01:00
0470f62e05 Refactor schema type handling and enhance utility functions in utils and database modules
This commit introduces several utility functions to improve schema type handling, including `getEmbeddedSchemaType` and `getObjectIdSchemaTypeFromPath`. The `getFilterFieldKind` function is updated to utilize these new utilities for better clarity and functionality. Additionally, the `getArrayParentPaths` function is renamed to `getArrayUnwindPaths` for improved readability, and the logic for fetching unique IDs is enhanced with a new `flattenRefIds` function. These changes streamline the code and enhance the overall management of schema references and array paths.
2026-08-22 20:02:21 +01:00
5 changed files with 107 additions and 26 deletions

View File

@ -657,21 +657,26 @@ export const listPropertyValues = async ({ model, property, filter = {}, search
return await model.aggregate(aggregateCommand);
};
function getEmbeddedSchemaType(path) {
if (!path) return null;
return path.embeddedSchemaType ?? path.$embeddedSchemaType ?? path.caster ?? null;
}
function getObjectRefPathInfo(model, property) {
if (!model?.schema || !property) return null;
const path = model.schema.path(property);
if (!path) return null;
const schemaType = path.instance === 'Array' ? path.caster : path;
const schemaType = path.instance === 'Array' ? getEmbeddedSchemaType(path) : path;
if (!schemaType) return null;
const instance = schemaType.instance;
if (instance !== 'ObjectID' && instance !== 'ObjectId') return null;
return {
ref: schemaType.options?.ref,
refPath: schemaType.options?.refPath,
ref: schemaType.options?.ref ?? path.options?.ref,
refPath: schemaType.options?.refPath ?? path.options?.refPath,
};
}
@ -690,28 +695,52 @@ function inferTypeFieldFromRefFunction(refFn) {
return matches.find((name) => name.endsWith('Type') || name === 'type') || matches[0] || null;
}
function getArrayParentPaths(model, property) {
if (!model?.schema || !property?.includes('.')) return [];
function getArrayUnwindPaths(model, property) {
if (!model?.schema || !property) return [];
const parents = [];
const paths = [];
const parts = property.split('.');
for (let i = 1; i < parts.length; i++) {
const parentPath = parts.slice(0, i).join('.');
const parentSchemaPath = model.schema.path(parentPath);
if (parentSchemaPath?.instance === 'Array') {
parents.push(parentPath);
paths.push(parentPath);
}
}
return parents;
const path = model.schema.path(property);
if (path?.instance === 'Array') {
paths.push(property);
}
return paths;
}
function flattenRefIds(ids) {
const flattened = [];
const visit = (value) => {
if (value == null) return;
if (Array.isArray(value)) {
value.forEach(visit);
return;
}
if (
typeof value === 'object' &&
!(value instanceof mongoose.Types.ObjectId) &&
!(value instanceof Date) &&
value._id != null
) {
visit(value._id);
return;
}
flattened.push(value);
};
visit(ids);
return flattened;
}
async function fetchBasicObjectsByIds(refName, ids) {
const uniqueIds = [
...new Map(
(ids || [])
.filter((id) => id != null)
.map((id) => [id.toString(), id])
).values(),
...new Map(flattenRefIds(ids).map((id) => [id.toString(), id])).values(),
];
if (uniqueIds.length === 0) return [];
@ -762,10 +791,10 @@ export const getPropertyValues = async ({ model, property, filter = {} }) => {
pipeline.push({ $match: convertedFilter });
}
for (const parentPath of getArrayParentPaths(model, property)) {
for (const unwindPath of getArrayUnwindPaths(model, property)) {
pipeline.push({
$unwind: {
path: `$${parentPath}`,
path: `$${unwindPath}`,
preserveNullAndEmptyArrays: false,
},
});
@ -803,6 +832,28 @@ export const getPropertyValues = async ({ model, property, filter = {} }) => {
}
if (hasFixedRef) {
const unwindPaths = getArrayUnwindPaths(model, property);
if (unwindPaths.length > 0) {
const pipeline = [];
if (Object.keys(convertedFilter).length > 0) {
pipeline.push({ $match: convertedFilter });
}
for (const unwindPath of unwindPaths) {
pipeline.push({
$unwind: {
path: `$${unwindPath}`,
preserveNullAndEmptyArrays: false,
},
});
}
pipeline.push(
{ $match: { [property]: { $ne: null } } },
{ $group: { _id: `$${property}` } }
);
const ids = (await model.aggregate(pipeline)).map((row) => row._id);
return fetchBasicObjectsByIds(pathInfo.ref, ids);
}
const ids = await model.distinct(property, convertedFilter);
return fetchBasicObjectsByIds(pathInfo.ref, ids);
}

View File

@ -31,6 +31,12 @@ const documentJobSchema = new Schema(
ref: 'documentPrinter',
required: true,
},
quantity: {
type: Number,
required: true,
default: 1,
min: 1,
},
content: {
type: String,
required: false,

View File

@ -7,6 +7,7 @@ const router = express.Router();
const listAllowedFilters = [
'name',
'quantity',
'width',
'height',
'state',
@ -14,7 +15,7 @@ const listAllowedFilters = [
'updatedAt',
'_reference',
];
const listAllowedSorters = ['name', 'state', 'createdAt', 'updatedAt'];
const listAllowedSorters = ['name', 'quantity', 'state', 'createdAt', 'updatedAt'];
const propertiesAllowedFilters = [];
import {
listDocumentJobsRouteHandler,

View File

@ -113,6 +113,9 @@ export const editDocumentJobRouteHandler = async (req, res) => {
logger.trace(`Document Job with ID: ${id}`);
const updateData = {};
if (req.body.quantity != null) {
updateData.quantity = req.body.quantity;
}
// Create audit log before updating
const result = await editObject({
model: documentJobModel,
@ -142,6 +145,7 @@ export const newDocumentJobRouteHandler = async (req, res) => {
objectType: req.body.objectType,
object: req.body.object,
content: req.body.content,
quantity: req.body.quantity ?? 1,
state: { type: 'draft' },
createdAt: new Date(),
updatedAt: new Date(),

View File

@ -93,8 +93,19 @@ function getBaseProperty(property) {
return property;
}
function isObjectIdSchemaType(path) {
return path?.instance === 'ObjectId' || path?.instance === 'ObjectID';
function getEmbeddedSchemaType(path) {
if (!path) return null;
return path.embeddedSchemaType ?? path.$embeddedSchemaType ?? path.caster ?? null;
}
function getObjectIdSchemaTypeFromPath(path) {
if (!path) return null;
const schemaType = path.instance === 'Array' ? getEmbeddedSchemaType(path) : path;
if (!schemaType) return null;
if (schemaType.instance === 'ObjectId' || schemaType.instance === 'ObjectID') {
return schemaType;
}
return null;
}
function getSchemaPathFromModels(property, model = null) {
@ -112,12 +123,14 @@ function getFilterFieldKind(property, model = null) {
const inspectPath = (path) => {
if (!path) return null;
if (isObjectIdPath(property) || isObjectIdSchemaType(path)) {
if (property.endsWith('._id') && property !== '_id' && path.options?.ref) {
const objectIdType = getObjectIdSchemaTypeFromPath(path);
if (isObjectIdPath(property) || objectIdType) {
const ref = objectIdType?.options?.ref ?? path.options?.ref;
if (property.endsWith('._id') && property !== '_id' && ref) {
return { kind: 'objectId', property: baseProperty };
}
if (path.options?.ref) {
return { kind: 'objectRef', property: baseProperty, ref: path.options.ref };
if (ref) {
return { kind: 'objectRef', property: baseProperty, ref };
}
return { kind: 'objectId', property: baseProperty };
}
@ -162,7 +175,8 @@ function buildRegexOp(pattern, useOptions = true) {
function getSchemaRefName(property, model = null) {
const baseProperty = getBaseProperty(property);
const path = model?.schema?.path(baseProperty) ?? getSchemaPathFromModels(baseProperty);
return path?.options?.ref ?? path?.caster?.options?.ref ?? null;
const embeddedType = getEmbeddedSchemaType(path);
return path?.options?.ref ?? embeddedType?.options?.ref ?? null;
}
function getRefModelEntryFromSchemaRef(refName) {
@ -1264,6 +1278,7 @@ function filterDistributeKeys(value, keys = DISTRIBUTE_KEYS) {
result[key] = val;
}
}
console.log('filterDistributeKeys', result);
return result;
}
@ -1276,11 +1291,11 @@ async function distributeStats(value, type) {
}
async function distributeNew(value, type) {
await natsServer.publish(`${type}s.new`, filterDistributeKeys(value));
await natsServer.publish(`${type}s.new`, value);
}
async function distributeDelete(value, type) {
await natsServer.publish(`${type}s.delete`, filterDistributeKeys(value));
await natsServer.publish(`${type}s.delete`, value);
}
function getReferenceId(value) {
@ -1589,7 +1604,11 @@ function normalizeFilterQuery(query = {}) {
// qs turns `order._id=` into `{ order: { _id } }`. Lift it so it can be parsed
// as the order object filter instead of a nested query object.
if (queryClean.order && typeof queryClean.order === 'object' && !Array.isArray(queryClean.order)) {
if (
queryClean.order &&
typeof queryClean.order === 'object' &&
!Array.isArray(queryClean.order)
) {
if (queryClean.order._id !== undefined) {
queryClean['order._id'] = queryClean.order._id;
}