|
|
@@ -13,6 +13,7 @@ const CLASS_LABELS = {
|
|
|
ExamClass: '试题分类', ExamSysQuestions: '考试题库', ExamSysPapers: '试卷', ExamType: '考试类型', ExamPoint: '知识点', PaperQuestions: '试卷题目',
|
|
|
Datadiccategory: '数据字典分类', Datadic: '数据字典项',
|
|
|
GradeCate: '多级字典分类', Grade: '多级字典选项',
|
|
|
+ Currency: '货币管理',
|
|
|
Product: '商品', StoreProduct: '门店商品', StoreApplication: '门店申请', ShopFareTlp: '运费模板', ShopMoneyRegular: '金额规则', PayPlat: '支付平台',
|
|
|
GuestBar: '互动社区', Guestbook: '留言', Guestcate: '留言分类', Feedback: '反馈', Baike: '百科', Search: '搜索记录', Agency: '代理机构', DeliveryCenter: '交付中心'
|
|
|
};
|
|
|
@@ -39,7 +40,8 @@ const CLASS_SYSTEM_FIELDS = {
|
|
|
Datadiccategory: new Set(['diccateid','sourceKey']),
|
|
|
Datadic: new Set(['dicid','diccate','sourceKey']),
|
|
|
GradeCate: new Set(['cateId','sourceKey']),
|
|
|
- Grade: new Set(['gradeId','cate','parentId','grade','sourceKey'])
|
|
|
+ Grade: new Set(['gradeId','cate','parentId','grade','sourceKey']),
|
|
|
+ Currency: new Set(['id','sourceKey'])
|
|
|
};
|
|
|
|
|
|
function inputOf(request) {
|
|
|
@@ -53,11 +55,12 @@ function safeValue(value, depth = 0) {
|
|
|
if (value == null || depth > 4) return value;
|
|
|
if (Array.isArray(value)) return value.map((item) => safeValue(item, depth + 1));
|
|
|
if (value instanceof Date) return value.toISOString();
|
|
|
+ if (value && typeof value === 'object' && ['Object','Pointer'].includes(value.__type) && value.className && value.objectId) return { __type: 'Pointer', className: String(value.className), objectId: String(value.objectId) };
|
|
|
if (value && typeof value.toJSON === 'function') return safeValue(value.toJSON(), depth + 1);
|
|
|
if (typeof value === 'object') {
|
|
|
const output = {};
|
|
|
for (const [key, item] of Object.entries(value)) {
|
|
|
- if (HIDDEN_FIELDS.has(key) || /(?:password|secret|sessiontoken|masterkey|privatekey)/i.test(key)) continue;
|
|
|
+ if (HIDDEN_FIELDS.has(key) || /(?:password|secret|token|masterkey|privatekey|mch_key|^ak$|^sk$)/i.test(key)) continue;
|
|
|
output[key] = safeValue(item, depth + 1);
|
|
|
}
|
|
|
return output;
|
|
|
@@ -881,6 +884,29 @@ async function handler(request, response) {
|
|
|
await Parse.Object.destroyAll(targets, { useMasterKey: true }); for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'grade-delete', className, target.id);
|
|
|
return response.json({ success: true, data: { action: 'delete', updated: targets.length, results: [] } });
|
|
|
}
|
|
|
+ if (operation === 'saveCurrency') {
|
|
|
+ const objectId = String(input.objectId || ''); const payload = input.fields && typeof input.fields === 'object' ? input.fields : {};
|
|
|
+ const title = String(payload.title || '').trim(); const currencyName = String(payload.currencyName || '').trim().toUpperCase(); const currencySymbol = String(payload.currencySymbol || '').trim(); const exchange = Number(payload.currentExchange); const remark = String(payload.remark || '').trim();
|
|
|
+ if (!title || title.length > 50) fail(400, '货币名称长度应为 1 至 50 位');
|
|
|
+ if (!/^[A-Z][A-Z0-9]{1,9}$/.test(currencyName)) fail(400, '货币代码应为 2 至 10 位大写字母或数字');
|
|
|
+ if (!currencySymbol || currencySymbol.length > 16) fail(400, '货币符号长度应为 1 至 16 位');
|
|
|
+ if (!Number.isFinite(exchange) || exchange <= 0 || exchange > 1000000000) fail(400, '当前汇率必须是大于 0 的有效数字');
|
|
|
+ if (remark.length > 255) fail(400, '货币备注不能超过 255 位');
|
|
|
+ let companyId = pointerId(context.company); if (context.isSuperAdmin && input.companyId) companyId = String(input.companyId); if (!companyId) fail(400, '货币必须指定帐套');
|
|
|
+ const company = await new Parse.Query('Company').get(companyId, { useMasterKey: true }); const fields = await schemaFor('Currency'); let target;
|
|
|
+ if (!objectId) fail(501, 'migration_blocked: Currency.id 与 Parse 对象保留 id 冲突,且 PostgreSQL 物理表缺少可写旧 ID 列,不能安全新增货币');
|
|
|
+ { const query = new Parse.Query('Currency'); applyTenant(query, fields, context, input.companyId); target = await query.get(objectId, { useMasterKey: true }); companyId = pointerId(target.get('company')) || companyId; }
|
|
|
+ const duplicateQuery = new Parse.Query('Currency'); applyTenant(duplicateQuery, fields, context, input.companyId); duplicateQuery.limit(1000); const duplicates = await duplicateQuery.find({ useMasterKey: true });
|
|
|
+ if (duplicates.some((entry) => entry.id !== objectId && String(entry.get('title') || '').trim().toLowerCase() === title.toLowerCase())) fail(409, '同一帐套的货币名称不能重复');
|
|
|
+ if (duplicates.some((entry) => entry.id !== objectId && String(entry.get('currencyName') || '').trim().toUpperCase() === currencyName)) fail(409, '同一帐套的货币代码不能重复');
|
|
|
+ target.set('title', title); target.set('currencyName', currencyName); target.set('currencySymbol', currencySymbol); target.set('currentExchange', exchange); target.set('remark', remark); target.set('company', company);
|
|
|
+ try { await target.save(null, { useMasterKey: true }); } catch (error) { fail(422, '货币写入失败: ' + String(error && error.message || error)); }
|
|
|
+ await target.fetch({ useMasterKey: true }); await audit({ ...context, company }, objectId ? 'update-currency' : 'create-currency', 'Currency', target.id); return response.json({ success: true, data: serializeObject(target) });
|
|
|
+ }
|
|
|
+ if (operation === 'currencyBatch') {
|
|
|
+ if (String(input.className || '') !== 'Currency' || String(input.action || '') !== 'delete') fail(400, '不支持的货币批量操作'); const requestedIds = Array.isArray(input.objectIds) ? input.objectIds : [input.objectId]; const objectIds = [...new Set(requestedIds.map((value) => String(value || '').trim()).filter(Boolean))]; if (!objectIds.length || objectIds.length > 100) fail(400, '每次请选择 1 至 100 条货币记录');
|
|
|
+ const fields = await schemaFor('Currency'); const query = new Parse.Query('Currency'); applyTenant(query, fields, context, input.companyId); query.containedIn('objectId', objectIds); query.limit(objectIds.length); const targets = await query.find({ useMasterKey: true }); if (targets.length !== objectIds.length) fail(404, '部分货币记录不存在或不属于当前帐套'); await Parse.Object.destroyAll(targets, { useMasterKey: true }); for (const target of targets) await audit({ ...context, company: target.get('company') || context.company }, 'currency-delete', 'Currency', target.id); return response.json({ success: true, data: { action: 'delete', updated: targets.length, results: [] } });
|
|
|
+ }
|
|
|
if (operation === 'saveExamClass') {
|
|
|
const objectId = String(input.objectId || '');
|
|
|
const payload = input.fields && typeof input.fields === 'object' ? input.fields : {};
|
|
|
@@ -1144,7 +1170,7 @@ async function handler(request, response) {
|
|
|
const classWritable = !READ_ONLY_CLASSES.has(className);
|
|
|
if (operation === 'schema') {
|
|
|
const fieldList = Object.entries(fields).filter(([name]) => !HIDDEN_FIELDS.has(name) && !/(?:password|secret|sessiontoken|masterkey|privatekey)/i.test(name)).map(([name, field]) => ({ name, type: field.type, targetClass: field.targetClass, required: field.required === true, writable: classWritable && !isSystemField(className, name) && GENERIC_WRITE_TYPES.has(field.type) }));
|
|
|
- return response.json({ success: true, data: { className, label: CLASS_LABELS[className] || className, fields: fieldList, writable: classWritable, creatable: classWritable && !['CommonModel','Model','ModelField','Guestbook'].includes(className), supportsSoftDelete: Boolean(fields.isDeleted) } });
|
|
|
+ return response.json({ success: true, data: { className, label: CLASS_LABELS[className] || className, fields: fieldList, writable: classWritable, creatable: classWritable && !['CommonModel','Model','ModelField','Guestbook','Currency'].includes(className), supportsSoftDelete: Boolean(fields.isDeleted) } });
|
|
|
}
|
|
|
if (operation === 'list') {
|
|
|
const page = Math.max(1, Number(input.page) || 1); const pageSize = Math.min(100, Math.max(1, Number(input.pageSize) || 20));
|
|
|
@@ -1176,6 +1202,7 @@ async function handler(request, response) {
|
|
|
if (className === 'ExTeacher') fail(400, '考试教师必须走专用保存流程');
|
|
|
if (className === 'Datadiccategory' || className === 'Datadic') fail(400, '数据字典必须走专用保存流程');
|
|
|
if (className === 'GradeCate' || className === 'Grade') fail(400, '多级数据字典必须走专用保存流程');
|
|
|
+ if (className === 'Currency') fail(400, '货币必须走专用保存流程');
|
|
|
if (className === 'CommonModel' && !objectId) fail(400, '内容新增必须同时写入模型附表,不能走通用保存');
|
|
|
let object;
|
|
|
if (objectId) { const query = new Parse.Query(className); applyTenant(query, fields, context, input.companyId); object = await query.get(objectId, { useMasterKey: true }); } else object = new Parse.Object(className);
|