skill-calorie 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +54 -26
- package/dist/analysis/anomaly/common.d.ts.map +1 -1
- package/dist/analysis/anomaly/common.js +2 -1
- package/dist/analysis/anomaly/common.js.map +1 -1
- package/dist/analysis/cross.d.ts.map +1 -1
- package/dist/analysis/cross.js +3 -2
- package/dist/analysis/cross.js.map +1 -1
- package/dist/analysis/deficit.d.ts.map +1 -1
- package/dist/analysis/deficit.js +4 -0
- package/dist/analysis/deficit.js.map +1 -1
- package/dist/analysis/diet.js +2 -2
- package/dist/analysis/diet.js.map +1 -1
- package/dist/analysis/exercise.js +5 -5
- package/dist/analysis/exercise.js.map +1 -1
- package/dist/analysis/review.js +2 -2
- package/dist/analysis/review.js.map +1 -1
- package/dist/analysis/series.d.ts +7 -1
- package/dist/analysis/series.d.ts.map +1 -1
- package/dist/analysis/series.js +22 -6
- package/dist/analysis/series.js.map +1 -1
- package/dist/analysis/utils.d.ts +7 -0
- package/dist/analysis/utils.d.ts.map +1 -1
- package/dist/analysis/utils.js +7 -0
- package/dist/analysis/utils.js.map +1 -1
- package/dist/analysis/weightCompare3.js +2 -2
- package/dist/analysis/weightCompare3.js.map +1 -1
- package/dist/cli/cmd_read.d.ts +21 -3
- package/dist/cli/cmd_read.d.ts.map +1 -1
- package/dist/cli/cmd_read.js +460 -54
- package/dist/cli/cmd_read.js.map +1 -1
- package/dist/cli/keys.d.ts +89 -1
- package/dist/cli/keys.d.ts.map +1 -1
- package/dist/cli/keys.js +30 -0
- package/dist/cli/keys.js.map +1 -1
- package/dist/cli/write.d.ts +12 -9
- package/dist/cli/write.d.ts.map +1 -1
- package/dist/cli/write.js +133 -48
- package/dist/cli/write.js.map +1 -1
- package/dist/output.d.ts +55 -1
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +203 -5
- package/dist/output.js.map +1 -1
- package/dist/render/dietDocs.d.ts +45 -0
- package/dist/render/dietDocs.d.ts.map +1 -0
- package/dist/render/dietDocs.js +561 -0
- package/dist/render/dietDocs.js.map +1 -0
- package/dist/render/envelope.d.ts +32 -6
- package/dist/render/envelope.d.ts.map +1 -1
- package/dist/render/envelope.js +71 -0
- package/dist/render/envelope.js.map +1 -1
- package/dist/render/exercisePort.d.ts +161 -0
- package/dist/render/exercisePort.d.ts.map +1 -0
- package/dist/render/exercisePort.js +395 -0
- package/dist/render/exercisePort.js.map +1 -0
- package/dist/render/goal.d.ts.map +1 -1
- package/dist/render/goal.js +13 -2
- package/dist/render/goal.js.map +1 -1
- package/dist/render/goalPlate.d.ts +1 -1
- package/dist/render/goalPlate.d.ts.map +1 -1
- package/dist/render/goalPlate.js +5 -1
- package/dist/render/goalPlate.js.map +1 -1
- package/dist/render/helpCenter.d.ts +104 -0
- package/dist/render/helpCenter.d.ts.map +1 -0
- package/dist/render/helpCenter.js +386 -0
- package/dist/render/helpCenter.js.map +1 -0
- package/dist/render/html.d.ts.map +1 -1
- package/dist/render/html.js +2 -1
- package/dist/render/html.js.map +1 -1
- package/dist/render/index.d.ts +3 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +2 -0
- package/dist/render/index.js.map +1 -1
- package/dist/render/nutritionPort.d.ts +89 -0
- package/dist/render/nutritionPort.d.ts.map +1 -0
- package/dist/render/nutritionPort.js +183 -0
- package/dist/render/nutritionPort.js.map +1 -0
- package/dist/render/nutritionPortDocs.d.ts +6 -0
- package/dist/render/nutritionPortDocs.d.ts.map +1 -0
- package/dist/render/nutritionPortDocs.js +317 -0
- package/dist/render/nutritionPortDocs.js.map +1 -0
- package/dist/render/photo.d.ts.map +1 -1
- package/dist/render/photo.js +5 -1
- package/dist/render/photo.js.map +1 -1
- package/dist/render/planPlate.d.ts +2 -1
- package/dist/render/planPlate.d.ts.map +1 -1
- package/dist/render/planPlate.js +6 -1
- package/dist/render/planPlate.js.map +1 -1
- package/dist/render/receipt.d.ts +47 -1
- package/dist/render/receipt.d.ts.map +1 -1
- package/dist/render/receipt.js +67 -3
- package/dist/render/receipt.js.map +1 -1
- package/dist/render/sportDocs.d.ts +14 -0
- package/dist/render/sportDocs.d.ts.map +1 -0
- package/dist/render/sportDocs.js +540 -0
- package/dist/render/sportDocs.js.map +1 -0
- package/dist/render/sportPortDocs.d.ts +8 -0
- package/dist/render/sportPortDocs.d.ts.map +1 -0
- package/dist/render/sportPortDocs.js +468 -0
- package/dist/render/sportPortDocs.js.map +1 -0
- package/dist/render/trendDocs.d.ts +11 -0
- package/dist/render/trendDocs.d.ts.map +1 -0
- package/dist/render/trendDocs.js +445 -0
- package/dist/render/trendDocs.js.map +1 -0
- package/dist/render/trendMiscPort.d.ts +127 -0
- package/dist/render/trendMiscPort.d.ts.map +1 -0
- package/dist/render/trendMiscPort.js +277 -0
- package/dist/render/trendMiscPort.js.map +1 -0
- package/dist/render/trendMiscPortDocs.d.ts +10 -0
- package/dist/render/trendMiscPortDocs.d.ts.map +1 -0
- package/dist/render/trendMiscPortDocs.js +397 -0
- package/dist/render/trendMiscPortDocs.js.map +1 -0
- package/dist/render/wizardPort.d.ts +125 -0
- package/dist/render/wizardPort.d.ts.map +1 -0
- package/dist/render/wizardPort.js +393 -0
- package/dist/render/wizardPort.js.map +1 -0
- package/dist/render/wizardPortDocs.d.ts +6 -0
- package/dist/render/wizardPortDocs.d.ts.map +1 -0
- package/dist/render/wizardPortDocs.js +274 -0
- package/dist/render/wizardPortDocs.js.map +1 -0
- package/dist/triggers/routing.d.ts +21 -21
- package/dist/triggers/routing.d.ts.map +1 -1
- package/dist/triggers/routing.js +60 -30
- package/dist/triggers/routing.js.map +1 -1
- package/package.json +2 -2
- package/templates/diet.html +2 -2
- package/templates/exercise.html +2 -2
- package/templates/goal.html +2 -2
- package/templates/help.html +2 -2
- package/templates/home.html +2 -2
- package/templates/photo-gallery.html +2 -2
package/dist/cli/write.js
CHANGED
|
@@ -9,7 +9,7 @@ import { getNutritionGoal, setNutritionGoal, updateWaterGoal } from '../fetch/nu
|
|
|
9
9
|
import { pauseAllGoals, resumeAllGoals, setWeightGoal } from '../fetch/goal.js';
|
|
10
10
|
import { CALIPER_FIELDS, ValidationError, addComposition, addMeasurement, deleteComposition, deleteMeasurement, } from '../fetch/body.js';
|
|
11
11
|
import { SOURCE_CHOICES, SOURCE_LABELS } from '../kcal.js';
|
|
12
|
-
import { buildCrudReceipt } from '../render/receipt.js';
|
|
12
|
+
import { buildCrudReceipt, withM5 } from '../render/receipt.js';
|
|
13
13
|
import { CalorieRenderError } from '../render/errors.js';
|
|
14
14
|
import { shiftISODate, todayISO } from '../analysis/utils.js';
|
|
15
15
|
import { isCalorieWriteKey } from './keys.js';
|
|
@@ -74,7 +74,6 @@ function esc(s) {
|
|
|
74
74
|
}
|
|
75
75
|
/** #101 · 删除口径单一来源(prose 词条 + `items[].status` 状态串同源派生)。
|
|
76
76
|
* `recoverable` 恒 false:全仓 77 键 0 个 restore/undo/recover 入口,故不得出现「可恢复」承诺。 */
|
|
77
|
-
const SOFT_STILL_COUNTED = '(软删除:行保留,仍计入历史统计;暂无恢复入口)';
|
|
78
77
|
const SOFT_EXCLUDED_INNER = '软删除:行保留,已从查询与统计中排除;暂无恢复入口';
|
|
79
78
|
const SOFT_EXCLUDED = '(' + SOFT_EXCLUDED_INNER + ')';
|
|
80
79
|
const HARD_INNER = '硬删除,不可恢复';
|
|
@@ -83,6 +82,58 @@ const HARD_WORDING = '(' + HARD_INNER + ')';
|
|
|
83
82
|
function deleteStatus(kind, base = '已删除') {
|
|
84
83
|
return base + (kind === 'soft' ? '(软,不可恢复)' : '(硬,不可恢复)');
|
|
85
84
|
}
|
|
85
|
+
/* ------------------------------------------------ #97 · M5 回执契约(追加字段,正本 t97-m5-contract.md) */
|
|
86
|
+
/** 影响行数来源:SQLite `total_changes()` 在本键写库前后的增量。
|
|
87
|
+
* 真实库行数(INSERT/UPDATE/DELETE/软删标记一律计;`INSERT OR REPLACE` 命中已有行=1,
|
|
88
|
+
* `INSERT OR IGNORE` 命中已有行=0——实测 node:sqlite v24),非自报。 */
|
|
89
|
+
function totalChanges(db) {
|
|
90
|
+
const row = db.prepare('SELECT total_changes() AS n').get();
|
|
91
|
+
return row && typeof row.n === 'number' ? Number(row.n) : 0;
|
|
92
|
+
}
|
|
93
|
+
/** 写入字段摘要(CLI 参数名口径):create 键=该键写入字段全集(缺省值也算写入);
|
|
94
|
+
* update 键=本次实际变更字段;delete 键=空数组(无写入字段,旧版同样只印 id/日期/影响)。 */
|
|
95
|
+
const F = {
|
|
96
|
+
diet: ['foodName', 'calories', 'protein', 'carbs', 'fat', 'grams', 'note', 'date', 'time'],
|
|
97
|
+
water: ['ml', 'note', 'date', 'time'],
|
|
98
|
+
weight: ['kg', 'note', 'date', 'time'],
|
|
99
|
+
weightBatch: ['kg', 'date'],
|
|
100
|
+
exercise: ['type', 'calories', 'minutes', 'date', 'time', 'note', 'reps', 'category', 'difficulty', 'distance', 'heartRate', 'maxHeartRate', 'steps', 'setIndex', 'loadKg', 'backfill'],
|
|
101
|
+
photo: ['srcPaths', 'tag', 'note', 'date', 'time'],
|
|
102
|
+
product: ['productName', 'brand', 'calories', 'protein', 'fat', 'saturatedFat', 'carbohydrates', 'sugar', 'dietaryFiber', 'sodium', 'note'],
|
|
103
|
+
};
|
|
104
|
+
/** `calorie.goal.set` 本次**实际被 SET 的列** → CLI 参数名(正本 §3.4「update 键=本次实际变更字段」)。
|
|
105
|
+
* 与 `fetch/nutritionGoal.ts:68-78` 的两条 SQL 同源:传 `water` 走 6 列 `INSERT OR REPLACE`
|
|
106
|
+
* (含 `water_goal`),不传则 SQL 里**没有** `water_goal` 列——该列的值只因 REPLACE 落列默认
|
|
107
|
+
* (`schema.ts:32` `water_goal INTEGER DEFAULT 2000`),**不属本次 SET 的字段**,故不得报 `water`。
|
|
108
|
+
* 记账列 `updated_at` 无 CLI 参数,按 §3.4 不计入摘要。
|
|
109
|
+
* 注意:`INSERT OR REPLACE` 连带把 `weight_goal`/`goal_deadline`/`goal_paused`/`start_weight`/
|
|
110
|
+
* `start_date` 重置为默认/NULL——那是**实际行为缺陷**(红队 D-4),已转 #127;本票只让回执如实
|
|
111
|
+
* 反映「本次 SET 了哪些列」,**不改写语义**(不做 UPSERT 化)。 */
|
|
112
|
+
const goalSetWrittenFields = (hasWater) => ['calorie', 'protein', 'carbs', 'fat', ...(hasWater ? ['water'] : [])];
|
|
113
|
+
/** 库列名 → CLI 参数名(update 键按实际变更列回报写入字段摘要)。 */
|
|
114
|
+
const COL_CLI = {
|
|
115
|
+
food_name: 'foodName', grams: 'grams', calories: 'calories', protein: 'protein', carbs: 'carbs',
|
|
116
|
+
fat: 'fat', note: 'note', date: 'date', time: 'time',
|
|
117
|
+
exercise_type: 'type', calories_burned: 'calories', duration_minutes: 'minutes', category: 'category',
|
|
118
|
+
difficulty: 'difficulty', distance_km: 'distance', avg_heart_rate: 'heartRate',
|
|
119
|
+
max_heart_rate: 'maxHeartRate', steps: 'steps', reps: 'reps', load_kg: 'loadKg',
|
|
120
|
+
set_index: 'setIndex', is_backfill: 'backfill',
|
|
121
|
+
product_name: 'productName', brand: 'brand', saturated_fat: 'saturatedFat', carbohydrates: 'carbohydrates',
|
|
122
|
+
sugar: 'sugar', dietary_fiber: 'dietaryFiber', sodium: 'sodium',
|
|
123
|
+
};
|
|
124
|
+
const cliNames = (cols) => cols.map((c) => COL_CLI[c] ?? c);
|
|
125
|
+
/** 只取「实际提供」的参数名(写入字段摘要;`undefined` 不算写入)。 */
|
|
126
|
+
function provided(params, names) {
|
|
127
|
+
return names.filter((n) => params[n] !== undefined);
|
|
128
|
+
}
|
|
129
|
+
/** `input` 里实际有值的键(体脂/围度等动态字段表)。 */
|
|
130
|
+
function definedKeys(input) {
|
|
131
|
+
return Object.keys(input).filter((k) => input[k] !== undefined);
|
|
132
|
+
}
|
|
133
|
+
/** 围度库列名 → CLI 参数名(`MEASURE_CAMEL` 反向;写入字段摘要统一走 CLI 名口径)。 */
|
|
134
|
+
function measureCliNames(keys) {
|
|
135
|
+
return keys.map((k) => Object.keys(MEASURE_CAMEL).find((c) => MEASURE_CAMEL[c] === k) ?? k);
|
|
136
|
+
}
|
|
86
137
|
/** C6 #43 · 写收据 HTML 结构化分项:摘要 + 操作元 + items 逐条(id/日期/状态/原因/明细)。 */
|
|
87
138
|
function receiptHtml(scene, summary, op, recordId, items) {
|
|
88
139
|
const list = (items ?? []).length > 0
|
|
@@ -177,12 +228,17 @@ function oneExercise(item, i) {
|
|
|
177
228
|
isBackfill: (g('backfill') ?? g('isBackfill')) === true,
|
|
178
229
|
};
|
|
179
230
|
}
|
|
180
|
-
/** 写分发(唯一出口 cmd_read 内调用;未知键上游已拦,此处再拦一道)。
|
|
231
|
+
/** 写分发(唯一出口 cmd_read 内调用;未知键上游已拦,此处再拦一道)。
|
|
232
|
+
* #97 · M5:`affectedRows` 在此统一注入——写库前后各取一次 SQLite `total_changes()`,
|
|
233
|
+
* 增量即「影响 N 行」(35 键单一来源,逐键不各自自报)。 */
|
|
181
234
|
export function dispatchWrite(key, params, db) {
|
|
182
235
|
if (!isCalorieWriteKey(key))
|
|
183
236
|
fail(3, '未知 calorie 写键:' + key);
|
|
237
|
+
const before = totalChanges(db);
|
|
184
238
|
try {
|
|
185
|
-
|
|
239
|
+
const res = dispatchInner(key, params, db);
|
|
240
|
+
const receipt = withM5(res.data.receipt, { affectedRows: totalChanges(db) - before });
|
|
241
|
+
return { data: { ...res.data, receipt }, html: res.html };
|
|
186
242
|
}
|
|
187
243
|
catch (e) {
|
|
188
244
|
if (e instanceof ValidationError)
|
|
@@ -225,11 +281,13 @@ function dispatchInner(key, params, db) {
|
|
|
225
281
|
if (r.duplicate) {
|
|
226
282
|
return out(R('记一餐', 'create', String(r.message ?? '重复记录已跳过'), '记一餐', 'food_log (写库回执)', {
|
|
227
283
|
recordId: r.dupId ?? null, noChange: true,
|
|
284
|
+
ids: r.dupId ? [r.dupId] : [], idSource: r.dupId ? 'record' : 'none', writtenFields: [],
|
|
228
285
|
}));
|
|
229
286
|
}
|
|
230
287
|
const remain = r.remainingCal === null || r.remainingCal === undefined ? '' : ' · 今日剩 ' + r.remainingCal + ' 卡';
|
|
231
288
|
return out(R('记一餐', 'create', '已记一餐:' + r.food_name + ' ' + r.date + ' ' + r.time + '(' + r.meal + ')' + remain, '记一餐', 'food_log (写库回执)', {
|
|
232
|
-
recordId: r.id,
|
|
289
|
+
recordId: r.id, ids: r.id === null ? [] : [r.id], writtenFields: [...F.diet],
|
|
290
|
+
items: [{ id: r.id ?? undefined, date: r.date, status: '成功', reason: '', detail: r.food_name }],
|
|
233
291
|
}));
|
|
234
292
|
}
|
|
235
293
|
case 'calorie.diet.update': {
|
|
@@ -254,7 +312,7 @@ function dispatchInner(key, params, db) {
|
|
|
254
312
|
assertISO(fields['date'], 'date');
|
|
255
313
|
const r = updateMeal(db, id, fields);
|
|
256
314
|
return out(R('改饮食记录', 'update', '已更新饮食 #' + id + '(' + (r.changed.length ? r.changed.join('、') : '无实际变化') + ')', '改饮食记录', 'food_log (写库回执)', {
|
|
257
|
-
recordId: id, noChange: r.changed.length === 0,
|
|
315
|
+
recordId: id, ids: [id], writtenFields: cliNames(Object.keys(fields)), noChange: r.changed.length === 0,
|
|
258
316
|
items: [{ id, status: '已更新', reason: '', detail: r.changed.join(',') || '无变化' }],
|
|
259
317
|
}));
|
|
260
318
|
}
|
|
@@ -262,7 +320,7 @@ function dispatchInner(key, params, db) {
|
|
|
262
320
|
const id = needId(params);
|
|
263
321
|
const r = deleteMeal(db, id);
|
|
264
322
|
return out(R('删饮食记录', 'delete', '已删除饮食 #' + id + '(' + r.food_name + ' ' + r.calories + ' 卡 · ' + HARD_INNER + ')', '删饮食记录', 'food_log (写库回执)', {
|
|
265
|
-
recordId: id, items: [{ id, status: deleteStatus('hard'), reason: '', detail: r.food_name }],
|
|
323
|
+
recordId: id, ids: [id], writtenFields: [], items: [{ id, status: deleteStatus('hard'), reason: '', detail: r.food_name }],
|
|
266
324
|
}));
|
|
267
325
|
}
|
|
268
326
|
case 'calorie.diet.batch': {
|
|
@@ -282,7 +340,8 @@ function dispatchInner(key, params, db) {
|
|
|
282
340
|
};
|
|
283
341
|
}));
|
|
284
342
|
return out(R('批量补记饮食', 'create', '批量记饮食:新增 ' + r.added + ',跳过 ' + r.skipped + ',失败 ' + r.failed, '批量补记饮食', 'food_log (写库回执)', {
|
|
285
|
-
noChange: r.added === 0,
|
|
343
|
+
noChange: r.added === 0, ids: [], idSource: 'condition',
|
|
344
|
+
writtenFields: r.added > 0 ? [...F.diet] : [],
|
|
286
345
|
items: r.failures.slice(0, 20).map(([idx, reason]) => ({ status: '失败', reason, detail: '第' + idx + '条' })),
|
|
287
346
|
}));
|
|
288
347
|
}
|
|
@@ -296,7 +355,8 @@ function dispatchInner(key, params, db) {
|
|
|
296
355
|
if (r.copied + r.skipped === 0)
|
|
297
356
|
throw new CalorieRenderError('missing-data', '来源无饮食记录(' + from + ')');
|
|
298
357
|
return out(R('复制昨日饮食', 'create', '已复制饮食 ' + from + '→' + to + ':复制 ' + r.copied + ',跳过 ' + r.skipped, '复制昨日饮食', 'food_log (写库回执)', {
|
|
299
|
-
noChange: r.copied === 0,
|
|
358
|
+
noChange: r.copied === 0, ids: [], idSource: 'condition',
|
|
359
|
+
writtenFields: r.copied > 0 ? [...F.diet] : [],
|
|
300
360
|
}));
|
|
301
361
|
}
|
|
302
362
|
case 'calorie.diet.update-by-date': {
|
|
@@ -316,7 +376,9 @@ function dispatchInner(key, params, db) {
|
|
|
316
376
|
const r = updateMealsByDate(db, date, fields);
|
|
317
377
|
if (r.matched === 0)
|
|
318
378
|
throw new CalorieRenderError('missing-data', '无饮食记录(' + date + ')');
|
|
319
|
-
return out(R('改某日饮食', 'update', '已更新 ' + date + ' 饮食 ' + r.updated + ' 条(' + r.changedFields.join('、') + ')', '改某日饮食', 'food_log (写库回执)', {
|
|
379
|
+
return out(R('改某日饮食', 'update', '已更新 ' + date + ' 饮食 ' + r.updated + ' 条(' + r.changedFields.join('、') + ')', '改某日饮食', 'food_log (写库回执)', {
|
|
380
|
+
ids: [], idSource: 'condition', writtenFields: cliNames(r.changedFields),
|
|
381
|
+
}));
|
|
320
382
|
}
|
|
321
383
|
case 'calorie.diet.remove-by-date': {
|
|
322
384
|
const date = needStr(params, 'date');
|
|
@@ -324,7 +386,7 @@ function dispatchInner(key, params, db) {
|
|
|
324
386
|
const r = deleteMealsByDate(db, date);
|
|
325
387
|
if (r.deleted === 0)
|
|
326
388
|
throw new CalorieRenderError('missing-data', '无饮食记录(' + date + ')');
|
|
327
|
-
return out(R('删某日饮食', 'delete', '已删除 ' + date + ' 饮食 ' + r.deleted + ' 条' + HARD_WORDING, '删某日饮食', 'food_log (写库回执)', {}));
|
|
389
|
+
return out(R('删某日饮食', 'delete', '已删除 ' + date + ' 饮食 ' + r.deleted + ' 条' + HARD_WORDING, '删某日饮食', 'food_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
328
390
|
}
|
|
329
391
|
case 'calorie.diet.remove-by-range': {
|
|
330
392
|
const start = needStr(params, 'start');
|
|
@@ -336,7 +398,7 @@ function dispatchInner(key, params, db) {
|
|
|
336
398
|
const r = deleteMealsByRange(db, start, end);
|
|
337
399
|
if (r.deleted === 0)
|
|
338
400
|
throw new CalorieRenderError('missing-data', '无饮食记录(' + start + '~' + end + ')');
|
|
339
|
-
return out(R('批量删饮食', 'delete', '已删除 ' + start + '~' + end + ' 饮食 ' + r.deleted + ' 条' + HARD_WORDING, '批量删饮食', 'food_log (写库回执)', {}));
|
|
401
|
+
return out(R('批量删饮食', 'delete', '已删除 ' + start + '~' + end + ' 饮食 ' + r.deleted + ' 条' + HARD_WORDING, '批量删饮食', 'food_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
340
402
|
}
|
|
341
403
|
case 'calorie.diet.remove-by-type': {
|
|
342
404
|
const date = needStr(params, 'date');
|
|
@@ -347,7 +409,7 @@ function dispatchInner(key, params, db) {
|
|
|
347
409
|
const r = deleteMealsByType(db, date, mealType);
|
|
348
410
|
if (r.deleted === 0)
|
|
349
411
|
throw new CalorieRenderError('missing-data', date + ' 无' + mealType + '记录');
|
|
350
|
-
return out(R('删一餐', 'delete', '已删除 ' + date + ' ' + mealType + ' ' + r.deleted + ' 条' + HARD_WORDING, '删一餐', 'food_log (写库回执)', {}));
|
|
412
|
+
return out(R('删一餐', 'delete', '已删除 ' + date + ' ' + mealType + ' ' + r.deleted + ' 条' + HARD_WORDING, '删一餐', 'food_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
351
413
|
}
|
|
352
414
|
case 'calorie.water.log': {
|
|
353
415
|
const ml = needNum(params, 'ml');
|
|
@@ -361,12 +423,17 @@ function dispatchInner(key, params, db) {
|
|
|
361
423
|
note: optStr(params, 'note'), date, time: optStr(params, 'time'),
|
|
362
424
|
});
|
|
363
425
|
if (r.duplicate) {
|
|
364
|
-
|
|
426
|
+
// 与 `calorie.diet.add` 同口径(§3.3 `record`):重复跳过的原 id 由 fetch 回传(`fetch/diet.ts:96`),
|
|
427
|
+
// 拿得到就报 `record`+`ids=[dupId]`,拿不到才退 `none`(返修 R-3/蓝队 D-3 统一)。
|
|
428
|
+
return out(R('记喝水', 'create', String(r.message ?? '重复记录已跳过'), '记喝水', 'food_log (写库回执)', {
|
|
429
|
+
recordId: r.dupId ?? null, noChange: true,
|
|
430
|
+
ids: r.dupId ? [r.dupId] : [], idSource: r.dupId ? 'record' : 'none', writtenFields: [],
|
|
431
|
+
}));
|
|
365
432
|
}
|
|
366
433
|
const day = date ?? todayISO();
|
|
367
434
|
const sum = getDailySummary(db, day);
|
|
368
435
|
return out(R('记喝水', 'create', '已记喝水 ' + ml + ' ml(' + day + ' 累计 ' + sum.waterMl + ' ml)', '记喝水', 'food_log (写库回执)', {
|
|
369
|
-
recordId: r.id,
|
|
436
|
+
recordId: r.id, ids: r.id === null ? [] : [r.id], writtenFields: [...F.water],
|
|
370
437
|
items: [{ id: r.id ?? undefined, date: r.date, status: '成功', reason: '', detail: ml + 'ml' }],
|
|
371
438
|
}));
|
|
372
439
|
}
|
|
@@ -380,7 +447,8 @@ function dispatchInner(key, params, db) {
|
|
|
380
447
|
const r = logWeight(db, kg, optStr(params, 'note') ?? '', date, optStr(params, 'time'));
|
|
381
448
|
const bmiText = r.bmi === null ? 'BMI 待补身高(补档案:calorie-cmd-read calorie.profile.set)' : 'BMI ' + r.bmi;
|
|
382
449
|
return out(R('记体重', 'create', '已记体重 ' + r.kg + ' kg(' + bmiText + ' · ' + r.date + ' ' + r.time + ')', '记体重', 'weight_log (写库回执)', {
|
|
383
|
-
recordId: r.id,
|
|
450
|
+
recordId: r.id, ids: [r.id], writtenFields: [...F.weight],
|
|
451
|
+
items: [{ id: r.id, date: r.date, status: '成功', reason: '', detail: r.kg + 'kg' }],
|
|
384
452
|
}));
|
|
385
453
|
}
|
|
386
454
|
case 'calorie.weight.update': {
|
|
@@ -396,13 +464,16 @@ function dispatchInner(key, params, db) {
|
|
|
396
464
|
const r = updateWeight(db, id, kg, note);
|
|
397
465
|
const bmiTextU = r.bmi === null ? 'BMI 待补身高(补档案:calorie-cmd-read calorie.profile.set)' : 'BMI ' + r.bmi;
|
|
398
466
|
return out(R('改体重记录', 'update', '已更新体重 #' + id + ':' + r.oldWeight + '→' + r.newWeight + ' kg(' + bmiTextU + ')', '改体重记录', 'weight_log (写库回执)', {
|
|
399
|
-
recordId: id,
|
|
467
|
+
recordId: id, ids: [id], writtenFields: [...(kg !== undefined ? ['kg'] : []), ...(note !== undefined ? ['note'] : [])],
|
|
468
|
+
items: [{ id, status: '已更新', reason: '' }],
|
|
400
469
|
}));
|
|
401
470
|
}
|
|
402
471
|
if (date !== undefined) {
|
|
403
472
|
assertISO(date, 'date');
|
|
404
473
|
const r = updateWeightByDate(db, date, kg, note);
|
|
405
|
-
return out(R('改某日体重', 'update', '已更新 ' + date + ' 体重 ' + r.hitCount + ' 条', '改某日体重', 'weight_log (写库回执)', {
|
|
474
|
+
return out(R('改某日体重', 'update', '已更新 ' + date + ' 体重 ' + r.hitCount + ' 条', '改某日体重', 'weight_log (写库回执)', {
|
|
475
|
+
ids: [], idSource: 'condition', writtenFields: [...(kg !== undefined ? ['kg'] : []), ...(note !== undefined ? ['note'] : [])],
|
|
476
|
+
}));
|
|
406
477
|
}
|
|
407
478
|
fail(2, '缺参数 id 或 date(二选一)');
|
|
408
479
|
throw new Error('unreachable');
|
|
@@ -417,13 +488,13 @@ function dispatchInner(key, params, db) {
|
|
|
417
488
|
fail(2, 'id 须为正整数');
|
|
418
489
|
const r = deleteWeight(db, id);
|
|
419
490
|
return out(R('删体重记录', 'delete', '已删除体重 #' + id + '(' + r.date + ' ' + r.weight_kg + ' kg · ' + HARD_INNER + ')', '删体重记录', 'weight_log (写库回执)', {
|
|
420
|
-
recordId: id, items: [{ id, status: deleteStatus('hard'), reason: '' }],
|
|
491
|
+
recordId: id, ids: [id], writtenFields: [], items: [{ id, status: deleteStatus('hard'), reason: '' }],
|
|
421
492
|
}));
|
|
422
493
|
}
|
|
423
494
|
if (date !== undefined) {
|
|
424
495
|
assertISO(date, 'date');
|
|
425
496
|
const r = deleteWeightByDate(db, date);
|
|
426
|
-
return out(R('删某日体重', 'delete', '已删除 ' + date + ' 体重 ' + r.deletedCount + ' 条' + HARD_WORDING, '删某日体重', 'weight_log (写库回执)', {}));
|
|
497
|
+
return out(R('删某日体重', 'delete', '已删除 ' + date + ' 体重 ' + r.deletedCount + ' 条' + HARD_WORDING, '删某日体重', 'weight_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
427
498
|
}
|
|
428
499
|
if (start !== undefined || end !== undefined) {
|
|
429
500
|
if (start === undefined || end === undefined)
|
|
@@ -433,7 +504,7 @@ function dispatchInner(key, params, db) {
|
|
|
433
504
|
if (start > end)
|
|
434
505
|
fail(2, 'start 不得晚于 end');
|
|
435
506
|
const r = deleteWeightRange(db, start, end);
|
|
436
|
-
return out(R('批量删体重', 'delete', '已删除 ' + start + '~' + end + ' 体重 ' + r.deletedCount + ' 条' + HARD_WORDING, '批量删体重', 'weight_log (写库回执)', {}));
|
|
507
|
+
return out(R('批量删体重', 'delete', '已删除 ' + start + '~' + end + ' 体重 ' + r.deletedCount + ' 条' + HARD_WORDING, '批量删体重', 'weight_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
437
508
|
}
|
|
438
509
|
fail(2, '缺参数 id/date/start+end(三选一)');
|
|
439
510
|
throw new Error('unreachable');
|
|
@@ -447,7 +518,8 @@ function dispatchInner(key, params, db) {
|
|
|
447
518
|
return { date: o['date'] === undefined ? undefined : String(o['date']), kg: o['kg'] };
|
|
448
519
|
}));
|
|
449
520
|
return out(R('批量补录体重', 'create', '批量记体重:写入 ' + r.wrote + ',跳过 ' + r.skipped + ',失败 ' + r.failed, '批量补录体重', 'weight_log (写库回执)', {
|
|
450
|
-
noChange: r.wrote === 0,
|
|
521
|
+
noChange: r.wrote === 0, ids: [], idSource: 'condition',
|
|
522
|
+
writtenFields: r.wrote > 0 ? [...F.weightBatch] : [],
|
|
451
523
|
items: r.items.filter((x) => x.status === '失败').slice(0, 20).map((x) => ({ status: '失败', reason: x.reason, detail: String(x.date) })),
|
|
452
524
|
}));
|
|
453
525
|
}
|
|
@@ -461,7 +533,8 @@ function dispatchInner(key, params, db) {
|
|
|
461
533
|
if (r.copied + r.skipped === 0)
|
|
462
534
|
throw new CalorieRenderError('missing-data', '昨日无运动记录可复制');
|
|
463
535
|
return out(R('复制昨日运动', 'create', '已复制昨日运动→' + target + ':复制 ' + r.copied + ',跳过 ' + r.skipped, '复制昨日运动', 'exercise_log (写库回执)', {
|
|
464
|
-
noChange: r.copied === 0,
|
|
536
|
+
noChange: r.copied === 0, ids: [], idSource: 'condition',
|
|
537
|
+
writtenFields: r.copied > 0 ? [...F.exercise] : [],
|
|
465
538
|
}));
|
|
466
539
|
}
|
|
467
540
|
if (params['items'] !== undefined) {
|
|
@@ -470,13 +543,15 @@ function dispatchInner(key, params, db) {
|
|
|
470
543
|
fail(2, 'items 至多 200 条');
|
|
471
544
|
const r = batchAdd(db, items.map((e, i) => oneExercise((e ?? {}), '(第' + i + '条)')));
|
|
472
545
|
return out(R('记运动', 'create', '批量记运动:新增 ' + r.added + ' 条', '批量补记运动', 'exercise_log (写库回执)', {
|
|
473
|
-
recordId: r.ids[0] ?? null,
|
|
546
|
+
recordId: r.ids[0] ?? null, ids: r.ids, idSource: r.ids.length > 0 ? 'record' : 'condition',
|
|
547
|
+
writtenFields: r.added > 0 ? [...F.exercise] : [],
|
|
474
548
|
}));
|
|
475
549
|
}
|
|
476
550
|
const input = oneExercise(params, '');
|
|
477
551
|
const r = addRecord(db, input);
|
|
478
552
|
return out(R('记运动', 'create', '已记运动:' + input.exerciseType + ' ' + input.caloriesBurned + ' 卡' + (input.minutes ? ' · ' + input.minutes + ' 分钟' : '') + '(' + input.date + ')', '记运动', 'exercise_log (写库回执)', {
|
|
479
|
-
recordId: r.id,
|
|
553
|
+
recordId: r.id, ids: [r.id], writtenFields: [...F.exercise],
|
|
554
|
+
items: [{ id: r.id, date: input.date, status: '成功', reason: '', detail: input.exerciseType }],
|
|
480
555
|
}));
|
|
481
556
|
}
|
|
482
557
|
case 'calorie.exercise.update': {
|
|
@@ -514,7 +589,8 @@ function dispatchInner(key, params, db) {
|
|
|
514
589
|
fail(2, 'id 须为正整数');
|
|
515
590
|
updateRecord(db, id, fields);
|
|
516
591
|
return out(R('改运动记录', 'update', '已更新运动 #' + id + '(' + Object.keys(fields).join('、') + ')', '改运动记录', 'exercise_log (写库回执)', {
|
|
517
|
-
recordId: id,
|
|
592
|
+
recordId: id, ids: [id], writtenFields: cliNames(Object.keys(fields)),
|
|
593
|
+
items: [{ id, status: '已更新', reason: '' }],
|
|
518
594
|
}));
|
|
519
595
|
}
|
|
520
596
|
if (date !== undefined) {
|
|
@@ -522,7 +598,9 @@ function dispatchInner(key, params, db) {
|
|
|
522
598
|
const r = updateDay(db, date, fields);
|
|
523
599
|
if (r.matched === 0)
|
|
524
600
|
throw new CalorieRenderError('missing-data', '无运动记录(' + date + ')');
|
|
525
|
-
return out(R('改某日运动', 'update', '已更新 ' + date + ' 运动 ' + r.matched + ' 条', '改某日运动', 'exercise_log (写库回执)', {
|
|
601
|
+
return out(R('改某日运动', 'update', '已更新 ' + date + ' 运动 ' + r.matched + ' 条', '改某日运动', 'exercise_log (写库回执)', {
|
|
602
|
+
ids: [], idSource: 'condition', writtenFields: cliNames(Object.keys(fields)),
|
|
603
|
+
}));
|
|
526
604
|
}
|
|
527
605
|
fail(2, '缺参数 id 或 date(二选一)');
|
|
528
606
|
throw new Error('unreachable');
|
|
@@ -536,8 +614,8 @@ function dispatchInner(key, params, db) {
|
|
|
536
614
|
if (!Number.isInteger(id) || id <= 0)
|
|
537
615
|
fail(2, 'id 须为正整数');
|
|
538
616
|
deleteRecord(db, id);
|
|
539
|
-
return out(R('删运动记录', 'delete', '已删除运动 #' + id +
|
|
540
|
-
recordId: id, items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
617
|
+
return out(R('删运动记录', 'delete', '已删除运动 #' + id + SOFT_EXCLUDED, '删运动记录', 'exercise_log (写库回执)', {
|
|
618
|
+
recordId: id, ids: [id], writtenFields: ['is_deleted'], items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
541
619
|
}));
|
|
542
620
|
}
|
|
543
621
|
if (date !== undefined) {
|
|
@@ -545,7 +623,7 @@ function dispatchInner(key, params, db) {
|
|
|
545
623
|
const n = deleteDay(db, date);
|
|
546
624
|
if (n === 0)
|
|
547
625
|
throw new CalorieRenderError('missing-data', '无运动记录(' + date + ')');
|
|
548
|
-
return out(R('删某日运动', 'delete', '已删除 ' + date + ' 运动 ' + n + ' 条' +
|
|
626
|
+
return out(R('删某日运动', 'delete', '已删除 ' + date + ' 运动 ' + n + ' 条' + SOFT_EXCLUDED, '删某日运动', 'exercise_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: ['is_deleted'] }));
|
|
549
627
|
}
|
|
550
628
|
if (from !== undefined || to !== undefined) {
|
|
551
629
|
if (from === undefined || to === undefined)
|
|
@@ -557,7 +635,7 @@ function dispatchInner(key, params, db) {
|
|
|
557
635
|
const n = deleteRange(db, from, to);
|
|
558
636
|
if (n === 0)
|
|
559
637
|
throw new CalorieRenderError('missing-data', '无运动记录(' + from + '~' + to + ')');
|
|
560
|
-
return out(R('批量删运动', 'delete', '已删除 ' + from + '~' + to + ' 运动 ' + n + ' 条' +
|
|
638
|
+
return out(R('批量删运动', 'delete', '已删除 ' + from + '~' + to + ' 运动 ' + n + ' 条' + SOFT_EXCLUDED, '批量删运动', 'exercise_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: ['is_deleted'] }));
|
|
561
639
|
}
|
|
562
640
|
fail(2, '缺参数 id/date/from+to(三选一)');
|
|
563
641
|
throw new Error('unreachable');
|
|
@@ -584,7 +662,9 @@ function dispatchInner(key, params, db) {
|
|
|
584
662
|
catch {
|
|
585
663
|
distance = null;
|
|
586
664
|
}
|
|
587
|
-
const receipt = buildAddReceipt(added, { tag, note: optStr(params, 'note'), distance, failedCount: srcPaths.length - added.length || undefined })
|
|
665
|
+
const receipt = withM5(buildAddReceipt(added, { tag, note: optStr(params, 'note'), distance, failedCount: srcPaths.length - added.length || undefined }), {
|
|
666
|
+
ids: added.map((a) => a.id), writtenFields: [...F.photo],
|
|
667
|
+
});
|
|
588
668
|
return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
|
|
589
669
|
}
|
|
590
670
|
case 'calorie.photo.remove': {
|
|
@@ -594,7 +674,7 @@ function dispatchInner(key, params, db) {
|
|
|
594
674
|
if (!snap)
|
|
595
675
|
throw new CalorieRenderError('missing-data', '身材照 #' + id + ' 不存在');
|
|
596
676
|
deletePhoto(db, dir, id);
|
|
597
|
-
const receipt = buildDeleteReceipt(snap);
|
|
677
|
+
const receipt = withM5(buildDeleteReceipt(snap), { ids: [id], writtenFields: [] });
|
|
598
678
|
return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
|
|
599
679
|
}
|
|
600
680
|
case 'calorie.photo.tag': {
|
|
@@ -628,7 +708,7 @@ function dispatchInner(key, params, db) {
|
|
|
628
708
|
scene = '删照片标签';
|
|
629
709
|
}
|
|
630
710
|
const after = getPhotoRow(db, id)?.tag_list ?? before;
|
|
631
|
-
const receipt = buildTagReceipt(id, before, after, scene);
|
|
711
|
+
const receipt = withM5(buildTagReceipt(id, before, after, scene), { ids: [id], writtenFields: op === 'set' ? ['tags'] : ['tag'] });
|
|
632
712
|
return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
|
|
633
713
|
}
|
|
634
714
|
case 'calorie.product.add': {
|
|
@@ -643,7 +723,8 @@ function dispatchInner(key, params, db) {
|
|
|
643
723
|
sodium: needNum(params, 'sodium'), note: optStr(params, 'note'),
|
|
644
724
|
});
|
|
645
725
|
return out(R('存食品', 'create', '已存食品 #' + r.id + '(' + productName.trim() + ')', '存食品', 'nutrition_products (写库回执)', {
|
|
646
|
-
recordId: r.id,
|
|
726
|
+
recordId: r.id, ids: [r.id], writtenFields: [...F.product],
|
|
727
|
+
items: [{ id: r.id, status: '成功', reason: '', detail: productName.trim() }],
|
|
647
728
|
}));
|
|
648
729
|
}
|
|
649
730
|
case 'calorie.product.update': {
|
|
@@ -668,7 +749,8 @@ function dispatchInner(key, params, db) {
|
|
|
668
749
|
if (!r.updated)
|
|
669
750
|
throw new CalorieRenderError('missing-data', '食品 #' + id + ' 不存在');
|
|
670
751
|
return out(R('改食品', 'update', '已更新食品 #' + id + '(' + Object.keys(fields).join('、') + ')', '改食品', 'nutrition_products (写库回执)', {
|
|
671
|
-
recordId: id,
|
|
752
|
+
recordId: id, ids: [id], writtenFields: cliNames(Object.keys(fields)),
|
|
753
|
+
items: [{ id, status: '已更新', reason: '' }],
|
|
672
754
|
}));
|
|
673
755
|
}
|
|
674
756
|
case 'calorie.product.deprecate': {
|
|
@@ -680,7 +762,7 @@ function dispatchInner(key, params, db) {
|
|
|
680
762
|
fail(2, String(r.error ?? '废弃失败'));
|
|
681
763
|
}
|
|
682
764
|
return out(R('下架食品', 'update', '已下架食品 #' + id + '(' + (r.name ?? '') + ' · ' + SOFT_EXCLUDED_INNER + ')', '下架食品', 'nutrition_products (写库回执)', {
|
|
683
|
-
recordId: id, items: [{ id, status: deleteStatus('soft', '已下架'), reason: '' }],
|
|
765
|
+
recordId: id, ids: [id], writtenFields: ['is_deprecated'], items: [{ id, status: deleteStatus('soft', '已下架'), reason: '' }],
|
|
684
766
|
}));
|
|
685
767
|
}
|
|
686
768
|
case 'calorie.profile.set':
|
|
@@ -713,7 +795,7 @@ function dispatchInner(key, params, db) {
|
|
|
713
795
|
const r = updateProfile(db, picked);
|
|
714
796
|
const after = r.after;
|
|
715
797
|
return out(R(scene, 'update', '已' + scene + '(身高 ' + (after.height_cm ?? '—') + ' · 年龄 ' + (after.age ?? '—') + ' · 活动量 ' + (after.activity_level ?? '—') + ')', wake, 'user_profile (写库回执)', {
|
|
716
|
-
recordId: 1, noChange: r.changed.length === 0,
|
|
798
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: Object.keys(picked), noChange: r.changed.length === 0,
|
|
717
799
|
}));
|
|
718
800
|
}
|
|
719
801
|
case 'calorie.profile.activity': {
|
|
@@ -722,7 +804,7 @@ function dispatchInner(key, params, db) {
|
|
|
722
804
|
fail(2, '缺参数 activityLevel');
|
|
723
805
|
const r = setActivityLevel(db, level);
|
|
724
806
|
return out(R('设活动量', 'update', '已设活动量:' + (r.before ?? '—') + '→' + r.after, '设活动量', 'user_profile (写库回执)', {
|
|
725
|
-
recordId: 1, noChange: r.before === r.after,
|
|
807
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: ['activityLevel'], noChange: r.before === r.after,
|
|
726
808
|
}));
|
|
727
809
|
}
|
|
728
810
|
case 'calorie.goal.set': {
|
|
@@ -743,7 +825,7 @@ function dispatchInner(key, params, db) {
|
|
|
743
825
|
const r = setNutritionGoal(db, { calorie, protein, carbs, fat, water });
|
|
744
826
|
const tail = r.consistent ? ' · 宏量自洽' : ' · ⚠宏量换算差 ' + r.diffKcal + ' 卡(>50 建议复核)';
|
|
745
827
|
return out(R('定营养目标', had ? 'update' : 'create', '已定营养目标:' + r.calorieGoal + ' 卡·蛋白 ' + r.proteinGoal + '·碳水 ' + r.carbsGoal + '·脂肪 ' + r.fatGoal + (r.waterGoal === null ? '' : '·饮水 ' + r.waterGoal) + tail, '定营养目标', 'daily_goal (写库回执)', {
|
|
746
|
-
recordId: 1,
|
|
828
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: goalSetWrittenFields(water !== undefined),
|
|
747
829
|
}));
|
|
748
830
|
}
|
|
749
831
|
case 'calorie.goal.water': {
|
|
@@ -752,7 +834,7 @@ function dispatchInner(key, params, db) {
|
|
|
752
834
|
throw new CalorieRenderError('missing-data', '尚无营养目标行(先定营养目标)');
|
|
753
835
|
const r = updateWaterGoal(db, water);
|
|
754
836
|
return out(R('定饮水目标', 'update', '已定饮水目标:' + (r.oldWaterGoal ?? '—') + '→' + r.newWaterGoal + ' ml', '定饮水目标', 'daily_goal (写库回执)', {
|
|
755
|
-
recordId: 1, noChange: r.oldWaterGoal === r.newWaterGoal,
|
|
837
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: ['water'], noChange: r.oldWaterGoal === r.newWaterGoal,
|
|
756
838
|
}));
|
|
757
839
|
}
|
|
758
840
|
case 'calorie.goal.weight': {
|
|
@@ -763,19 +845,20 @@ function dispatchInner(key, params, db) {
|
|
|
763
845
|
kg, deadline: params['deadline'], startKg: params['startKg'], startDate: params['startDate'],
|
|
764
846
|
});
|
|
765
847
|
return out(R('定体重目标', 'update', '已定体重目标 ' + r.weightGoal + ' kg' + (r.deadline ? '(截止 ' + r.deadline + ')' : '') + (r.startKg !== null ? ' · 起点 ' + r.startKg + ' kg' : ''), '定体重目标', 'daily_goal (写库回执)', {
|
|
766
|
-
recordId: 1,
|
|
848
|
+
recordId: 1, ids: [1], idSource: 'singleton',
|
|
849
|
+
writtenFields: provided(params, ['kg', 'deadline', 'startKg', 'startDate']),
|
|
767
850
|
}));
|
|
768
851
|
}
|
|
769
852
|
case 'calorie.goal.pause': {
|
|
770
853
|
const r = pauseAllGoals(db);
|
|
771
854
|
return out(R('暂停所有目标', 'update', '已暂停所有目标(记录照常,仅目标暂停)', '暂停所有目标', 'daily_goal (写库回执)', {
|
|
772
|
-
recordId: r.id,
|
|
855
|
+
recordId: r.id, ids: [r.id], idSource: 'singleton', writtenFields: ['goal_paused'],
|
|
773
856
|
}));
|
|
774
857
|
}
|
|
775
858
|
case 'calorie.goal.resume': {
|
|
776
859
|
const r = resumeAllGoals(db);
|
|
777
860
|
return out(R('重启所有目标', 'update', '已重启所有目标(恢复正常)', '重启所有目标', 'daily_goal (写库回执)', {
|
|
778
|
-
recordId: r.id,
|
|
861
|
+
recordId: r.id, ids: [r.id], idSource: 'singleton', writtenFields: ['goal_paused'],
|
|
779
862
|
}));
|
|
780
863
|
}
|
|
781
864
|
case 'calorie.body.composition-add': {
|
|
@@ -796,14 +879,15 @@ function dispatchInner(key, params, db) {
|
|
|
796
879
|
const r = addComposition(db, input);
|
|
797
880
|
const label = SOURCE_LABELS[source] ?? source;
|
|
798
881
|
return out(R('记体脂', 'create', '已记体脂:' + date + ' ' + label + ' ' + r.bodyFatPct + '%', '记体脂', 'body_composition (写库回执)', {
|
|
799
|
-
recordId: r.id,
|
|
882
|
+
recordId: r.id, ids: [r.id], writtenFields: definedKeys(input),
|
|
883
|
+
items: [{ id: r.id, date, status: '成功', reason: '', detail: r.bodyFatPct + '%' }],
|
|
800
884
|
}));
|
|
801
885
|
}
|
|
802
886
|
case 'calorie.body.composition-remove': {
|
|
803
887
|
const id = needId(params);
|
|
804
888
|
deleteComposition(db, id);
|
|
805
889
|
return out(R('删体脂', 'delete', '已删除体脂记录 #' + id + SOFT_EXCLUDED, '删体脂', 'body_composition (写库回执)', {
|
|
806
|
-
recordId: id, items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
890
|
+
recordId: id, ids: [id], writtenFields: ['is_deprecated'], items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
807
891
|
}));
|
|
808
892
|
}
|
|
809
893
|
case 'calorie.body.measure-add': {
|
|
@@ -825,14 +909,15 @@ function dispatchInner(key, params, db) {
|
|
|
825
909
|
return camel + ' ' + String(input[f]);
|
|
826
910
|
}).join('、');
|
|
827
911
|
return out(R('记围度', 'create', '已记围度:' + date + '(' + filledCn + ')', '记围度', 'body_measurements (写库回执)', {
|
|
828
|
-
recordId: r.id,
|
|
912
|
+
recordId: r.id, ids: [r.id], writtenFields: measureCliNames(definedKeys(input)),
|
|
913
|
+
items: [{ id: r.id, date, status: '成功', reason: '', detail: filledCn }],
|
|
829
914
|
}));
|
|
830
915
|
}
|
|
831
916
|
case 'calorie.body.measure-remove': {
|
|
832
917
|
const id = needId(params);
|
|
833
918
|
deleteMeasurement(db, id);
|
|
834
919
|
return out(R('删围度', 'delete', '已删除围度记录 #' + id + SOFT_EXCLUDED, '删围度', 'body_measurements (写库回执)', {
|
|
835
|
-
recordId: id, items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
920
|
+
recordId: id, ids: [id], writtenFields: ['is_deprecated'], items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
836
921
|
}));
|
|
837
922
|
}
|
|
838
923
|
default:
|