{
"data": "{{ $trigger.payload }}"
}
const formatter = new Intl.DateTimeFormat('en-GB', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
timeZone: 'Asia/Shanghai'
});
const main = async function (options, context) {
const { payload: { data }, transaction } = options;
const { database } = context;
return await transaction(database, async (trx) => {
const serialNumber = await getSerialNumber(trx);
// Inside a Filter Hook
return {
...data,
serial_number: serialNumber,
};
});
};
return await main(options, context);
async function getSerialNumber(trx) {
// Timezone Handling: Use Intl to force China Standard Time (CST)
// to prevent date flips at 8:00 AM if the server uses UTC.
const now = new Date();
const parts = formatter.formatToParts(now);
const year = parts.find(p => p.type === 'year').value;
const month = parts.find(p => p.type === 'month').value;
const day = parts.find(p => p.type === 'day').value;
const datePart = `${year}${month}${day}`;
const prefix = `PUR${datePart}`;
const PAD_LENGTH = 4; // Centralized padding control
const DAILY_QUOTA = Number('9'.repeat(PAD_LENGTH));
const row = await trx('purchase_order')
.where('serial_number', 'like', `${prefix}%`)
.max('serial_number as maxSerial')
.first();
let nextNumber = 1; // Start at 1
if (row?.maxSerial) {
// Extract suffix and increment numerically
const lastDigits = row.maxSerial.replace(prefix, '');
nextNumber = parseInt(lastDigits, 10) + 1;
}
if (nextNumber > DAILY_QUOTA) {
throw new Error(`Daily quota exceeded for ${prefix}. Capacity is ${DAILY_QUOTA} orders.`);
}
const nextDigits = String(nextNumber).padStart(PAD_LENGTH, '0');
return `${prefix}${nextDigits}`;
}
The Circular Loop Risk: Since the script calls updateOne inside the transaction, it might trigger another "Update" event. If this script is attached to an items.update hook, you could create an infinite loop.
Optimization: In a Filter (Before) hook, it is often better to modify the payload object directly rather than calling services.ItemsService.updateOne. However, if this is an Action or a custom script where the ID already exists, your current approach is fine as long as you ensure you aren't re-triggering the same logic.
const main = async function(options, context) {
const { payload: { collection, id }, transaction, schema } = options;
const { services, database, accountability } = context;
await transaction(database, async (trx) => {
const service = new services.ItemsService(collection, { knex: trx, schema, accountability });
const item = await service.readOne(id, {
fields: ['duration_days'],
});
await service.updateOne(id, {
duration_days: item.duration_days + 1,
});
});
return {};
};
return await main(options, context);