skill-calorie 0.1.1 → 0.2.1
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 +207 -0
- 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 +480 -53
- 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 +19 -1
- package/dist/cli/write.d.ts.map +1 -1
- package/dist/cli/write.js +148 -52
- package/dist/cli/write.js.map +1 -1
- package/dist/db/readonly.d.ts +3 -0
- package/dist/db/readonly.d.ts.map +1 -0
- package/dist/db/readonly.js +22 -0
- package/dist/db/readonly.js.map +1 -0
- package/dist/output.d.ts +82 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +305 -0
- package/dist/output.js.map +1 -0
- package/dist/render/copy.d.ts +20 -0
- package/dist/render/copy.d.ts.map +1 -0
- package/dist/render/copy.js +36 -0
- package/dist/render/copy.js.map +1 -0
- 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 +12 -0
- package/dist/render/html.d.ts.map +1 -1
- package/dist/render/html.js +21 -5
- package/dist/render/html.js.map +1 -1
- package/dist/render/index.d.ts +8 -1
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/index.js +5 -1
- 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 +6 -1
- package/dist/render/photo.d.ts.map +1 -1
- package/dist/render/photo.js +13 -4
- 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/templates.d.ts +4 -0
- package/dist/render/templates.d.ts.map +1 -0
- package/dist/render/templates.js +29 -0
- package/dist/render/templates.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 +141 -0
- package/dist/triggers/routing.d.ts.map +1 -0
- package/dist/triggers/routing.js +678 -0
- package/dist/triggers/routing.js.map +1 -0
- package/package.json +5 -3
- package/templates/diet.html +40 -0
- package/templates/exercise.html +40 -0
- package/templates/goal.html +40 -0
- package/templates/help.html +40 -0
- package/templates/home.html +40 -0
- package/templates/photo-gallery.html +40 -0
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';
|
|
@@ -72,6 +72,68 @@ function assertISO(v, field) {
|
|
|
72
72
|
function esc(s) {
|
|
73
73
|
return String(s ?? '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
74
74
|
}
|
|
75
|
+
/** #101 · 删除口径单一来源(prose 词条 + `items[].status` 状态串同源派生)。
|
|
76
|
+
* `recoverable` 恒 false:全仓 77 键 0 个 restore/undo/recover 入口,故不得出现「可恢复」承诺。 */
|
|
77
|
+
const SOFT_EXCLUDED_INNER = '软删除:行保留,已从查询与统计中排除;暂无恢复入口';
|
|
78
|
+
const SOFT_EXCLUDED = '(' + SOFT_EXCLUDED_INNER + ')';
|
|
79
|
+
const HARD_INNER = '硬删除,不可恢复';
|
|
80
|
+
const HARD_WORDING = '(' + HARD_INNER + ')';
|
|
81
|
+
/** 状态串与 prose 同源:kind 决定「软/硬」,两者一律带「不可恢复」。 */
|
|
82
|
+
function deleteStatus(kind, base = '已删除') {
|
|
83
|
+
return base + (kind === 'soft' ? '(软,不可恢复)' : '(硬,不可恢复)');
|
|
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
|
+
}
|
|
75
137
|
/** C6 #43 · 写收据 HTML 结构化分项:摘要 + 操作元 + items 逐条(id/日期/状态/原因/明细)。 */
|
|
76
138
|
function receiptHtml(scene, summary, op, recordId, items) {
|
|
77
139
|
const list = (items ?? []).length > 0
|
|
@@ -166,12 +228,17 @@ function oneExercise(item, i) {
|
|
|
166
228
|
isBackfill: (g('backfill') ?? g('isBackfill')) === true,
|
|
167
229
|
};
|
|
168
230
|
}
|
|
169
|
-
/** 写分发(唯一出口 cmd_read 内调用;未知键上游已拦,此处再拦一道)。
|
|
231
|
+
/** 写分发(唯一出口 cmd_read 内调用;未知键上游已拦,此处再拦一道)。
|
|
232
|
+
* #97 · M5:`affectedRows` 在此统一注入——写库前后各取一次 SQLite `total_changes()`,
|
|
233
|
+
* 增量即「影响 N 行」(35 键单一来源,逐键不各自自报)。 */
|
|
170
234
|
export function dispatchWrite(key, params, db) {
|
|
171
235
|
if (!isCalorieWriteKey(key))
|
|
172
236
|
fail(3, '未知 calorie 写键:' + key);
|
|
237
|
+
const before = totalChanges(db);
|
|
173
238
|
try {
|
|
174
|
-
|
|
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 };
|
|
175
242
|
}
|
|
176
243
|
catch (e) {
|
|
177
244
|
if (e instanceof ValidationError)
|
|
@@ -214,11 +281,13 @@ function dispatchInner(key, params, db) {
|
|
|
214
281
|
if (r.duplicate) {
|
|
215
282
|
return out(R('记一餐', 'create', String(r.message ?? '重复记录已跳过'), '记一餐', 'food_log (写库回执)', {
|
|
216
283
|
recordId: r.dupId ?? null, noChange: true,
|
|
284
|
+
ids: r.dupId ? [r.dupId] : [], idSource: r.dupId ? 'record' : 'none', writtenFields: [],
|
|
217
285
|
}));
|
|
218
286
|
}
|
|
219
287
|
const remain = r.remainingCal === null || r.remainingCal === undefined ? '' : ' · 今日剩 ' + r.remainingCal + ' 卡';
|
|
220
288
|
return out(R('记一餐', 'create', '已记一餐:' + r.food_name + ' ' + r.date + ' ' + r.time + '(' + r.meal + ')' + remain, '记一餐', 'food_log (写库回执)', {
|
|
221
|
-
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 }],
|
|
222
291
|
}));
|
|
223
292
|
}
|
|
224
293
|
case 'calorie.diet.update': {
|
|
@@ -243,15 +312,15 @@ function dispatchInner(key, params, db) {
|
|
|
243
312
|
assertISO(fields['date'], 'date');
|
|
244
313
|
const r = updateMeal(db, id, fields);
|
|
245
314
|
return out(R('改饮食记录', 'update', '已更新饮食 #' + id + '(' + (r.changed.length ? r.changed.join('、') : '无实际变化') + ')', '改饮食记录', 'food_log (写库回执)', {
|
|
246
|
-
recordId: id, noChange: r.changed.length === 0,
|
|
315
|
+
recordId: id, ids: [id], writtenFields: cliNames(Object.keys(fields)), noChange: r.changed.length === 0,
|
|
247
316
|
items: [{ id, status: '已更新', reason: '', detail: r.changed.join(',') || '无变化' }],
|
|
248
317
|
}));
|
|
249
318
|
}
|
|
250
319
|
case 'calorie.diet.remove': {
|
|
251
320
|
const id = needId(params);
|
|
252
321
|
const r = deleteMeal(db, id);
|
|
253
|
-
return out(R('删饮食记录', 'delete', '已删除饮食 #' + id + '(' + r.food_name + ' ' + r.calories + '
|
|
254
|
-
recordId: id, items: [{ id, status: '
|
|
322
|
+
return out(R('删饮食记录', 'delete', '已删除饮食 #' + id + '(' + r.food_name + ' ' + r.calories + ' 卡 · ' + HARD_INNER + ')', '删饮食记录', 'food_log (写库回执)', {
|
|
323
|
+
recordId: id, ids: [id], writtenFields: [], items: [{ id, status: deleteStatus('hard'), reason: '', detail: r.food_name }],
|
|
255
324
|
}));
|
|
256
325
|
}
|
|
257
326
|
case 'calorie.diet.batch': {
|
|
@@ -271,7 +340,8 @@ function dispatchInner(key, params, db) {
|
|
|
271
340
|
};
|
|
272
341
|
}));
|
|
273
342
|
return out(R('批量补记饮食', 'create', '批量记饮食:新增 ' + r.added + ',跳过 ' + r.skipped + ',失败 ' + r.failed, '批量补记饮食', 'food_log (写库回执)', {
|
|
274
|
-
noChange: r.added === 0,
|
|
343
|
+
noChange: r.added === 0, ids: [], idSource: 'condition',
|
|
344
|
+
writtenFields: r.added > 0 ? [...F.diet] : [],
|
|
275
345
|
items: r.failures.slice(0, 20).map(([idx, reason]) => ({ status: '失败', reason, detail: '第' + idx + '条' })),
|
|
276
346
|
}));
|
|
277
347
|
}
|
|
@@ -285,7 +355,8 @@ function dispatchInner(key, params, db) {
|
|
|
285
355
|
if (r.copied + r.skipped === 0)
|
|
286
356
|
throw new CalorieRenderError('missing-data', '来源无饮食记录(' + from + ')');
|
|
287
357
|
return out(R('复制昨日饮食', 'create', '已复制饮食 ' + from + '→' + to + ':复制 ' + r.copied + ',跳过 ' + r.skipped, '复制昨日饮食', 'food_log (写库回执)', {
|
|
288
|
-
noChange: r.copied === 0,
|
|
358
|
+
noChange: r.copied === 0, ids: [], idSource: 'condition',
|
|
359
|
+
writtenFields: r.copied > 0 ? [...F.diet] : [],
|
|
289
360
|
}));
|
|
290
361
|
}
|
|
291
362
|
case 'calorie.diet.update-by-date': {
|
|
@@ -305,7 +376,9 @@ function dispatchInner(key, params, db) {
|
|
|
305
376
|
const r = updateMealsByDate(db, date, fields);
|
|
306
377
|
if (r.matched === 0)
|
|
307
378
|
throw new CalorieRenderError('missing-data', '无饮食记录(' + date + ')');
|
|
308
|
-
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
|
+
}));
|
|
309
382
|
}
|
|
310
383
|
case 'calorie.diet.remove-by-date': {
|
|
311
384
|
const date = needStr(params, 'date');
|
|
@@ -313,7 +386,7 @@ function dispatchInner(key, params, db) {
|
|
|
313
386
|
const r = deleteMealsByDate(db, date);
|
|
314
387
|
if (r.deleted === 0)
|
|
315
388
|
throw new CalorieRenderError('missing-data', '无饮食记录(' + date + ')');
|
|
316
|
-
return out(R('删某日饮食', 'delete', '已删除 ' + date + ' 饮食 ' + r.deleted + ' 条', '删某日饮食', 'food_log (写库回执)', {}));
|
|
389
|
+
return out(R('删某日饮食', 'delete', '已删除 ' + date + ' 饮食 ' + r.deleted + ' 条' + HARD_WORDING, '删某日饮食', 'food_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
317
390
|
}
|
|
318
391
|
case 'calorie.diet.remove-by-range': {
|
|
319
392
|
const start = needStr(params, 'start');
|
|
@@ -325,7 +398,7 @@ function dispatchInner(key, params, db) {
|
|
|
325
398
|
const r = deleteMealsByRange(db, start, end);
|
|
326
399
|
if (r.deleted === 0)
|
|
327
400
|
throw new CalorieRenderError('missing-data', '无饮食记录(' + start + '~' + end + ')');
|
|
328
|
-
return out(R('批量删饮食', 'delete', '已删除 ' + start + '~' + end + ' 饮食 ' + r.deleted + ' 条', '批量删饮食', 'food_log (写库回执)', {}));
|
|
401
|
+
return out(R('批量删饮食', 'delete', '已删除 ' + start + '~' + end + ' 饮食 ' + r.deleted + ' 条' + HARD_WORDING, '批量删饮食', 'food_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
329
402
|
}
|
|
330
403
|
case 'calorie.diet.remove-by-type': {
|
|
331
404
|
const date = needStr(params, 'date');
|
|
@@ -336,7 +409,7 @@ function dispatchInner(key, params, db) {
|
|
|
336
409
|
const r = deleteMealsByType(db, date, mealType);
|
|
337
410
|
if (r.deleted === 0)
|
|
338
411
|
throw new CalorieRenderError('missing-data', date + ' 无' + mealType + '记录');
|
|
339
|
-
return out(R('删一餐', 'delete', '已删除 ' + date + ' ' + mealType + ' ' + r.deleted + ' 条', '删一餐', 'food_log (写库回执)', {}));
|
|
412
|
+
return out(R('删一餐', 'delete', '已删除 ' + date + ' ' + mealType + ' ' + r.deleted + ' 条' + HARD_WORDING, '删一餐', 'food_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
340
413
|
}
|
|
341
414
|
case 'calorie.water.log': {
|
|
342
415
|
const ml = needNum(params, 'ml');
|
|
@@ -350,12 +423,17 @@ function dispatchInner(key, params, db) {
|
|
|
350
423
|
note: optStr(params, 'note'), date, time: optStr(params, 'time'),
|
|
351
424
|
});
|
|
352
425
|
if (r.duplicate) {
|
|
353
|
-
|
|
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
|
+
}));
|
|
354
432
|
}
|
|
355
433
|
const day = date ?? todayISO();
|
|
356
434
|
const sum = getDailySummary(db, day);
|
|
357
435
|
return out(R('记喝水', 'create', '已记喝水 ' + ml + ' ml(' + day + ' 累计 ' + sum.waterMl + ' ml)', '记喝水', 'food_log (写库回执)', {
|
|
358
|
-
recordId: r.id,
|
|
436
|
+
recordId: r.id, ids: r.id === null ? [] : [r.id], writtenFields: [...F.water],
|
|
359
437
|
items: [{ id: r.id ?? undefined, date: r.date, status: '成功', reason: '', detail: ml + 'ml' }],
|
|
360
438
|
}));
|
|
361
439
|
}
|
|
@@ -369,7 +447,8 @@ function dispatchInner(key, params, db) {
|
|
|
369
447
|
const r = logWeight(db, kg, optStr(params, 'note') ?? '', date, optStr(params, 'time'));
|
|
370
448
|
const bmiText = r.bmi === null ? 'BMI 待补身高(补档案:calorie-cmd-read calorie.profile.set)' : 'BMI ' + r.bmi;
|
|
371
449
|
return out(R('记体重', 'create', '已记体重 ' + r.kg + ' kg(' + bmiText + ' · ' + r.date + ' ' + r.time + ')', '记体重', 'weight_log (写库回执)', {
|
|
372
|
-
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' }],
|
|
373
452
|
}));
|
|
374
453
|
}
|
|
375
454
|
case 'calorie.weight.update': {
|
|
@@ -385,13 +464,16 @@ function dispatchInner(key, params, db) {
|
|
|
385
464
|
const r = updateWeight(db, id, kg, note);
|
|
386
465
|
const bmiTextU = r.bmi === null ? 'BMI 待补身高(补档案:calorie-cmd-read calorie.profile.set)' : 'BMI ' + r.bmi;
|
|
387
466
|
return out(R('改体重记录', 'update', '已更新体重 #' + id + ':' + r.oldWeight + '→' + r.newWeight + ' kg(' + bmiTextU + ')', '改体重记录', 'weight_log (写库回执)', {
|
|
388
|
-
recordId: id,
|
|
467
|
+
recordId: id, ids: [id], writtenFields: [...(kg !== undefined ? ['kg'] : []), ...(note !== undefined ? ['note'] : [])],
|
|
468
|
+
items: [{ id, status: '已更新', reason: '' }],
|
|
389
469
|
}));
|
|
390
470
|
}
|
|
391
471
|
if (date !== undefined) {
|
|
392
472
|
assertISO(date, 'date');
|
|
393
473
|
const r = updateWeightByDate(db, date, kg, note);
|
|
394
|
-
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
|
+
}));
|
|
395
477
|
}
|
|
396
478
|
fail(2, '缺参数 id 或 date(二选一)');
|
|
397
479
|
throw new Error('unreachable');
|
|
@@ -405,14 +487,14 @@ function dispatchInner(key, params, db) {
|
|
|
405
487
|
if (!Number.isInteger(id) || id <= 0)
|
|
406
488
|
fail(2, 'id 须为正整数');
|
|
407
489
|
const r = deleteWeight(db, id);
|
|
408
|
-
return out(R('删体重记录', 'delete', '已删除体重 #' + id + '(' + r.date + ' ' + r.weight_kg + ' kg)', '删体重记录', 'weight_log (写库回执)', {
|
|
409
|
-
recordId: id, items: [{ id, status: '
|
|
490
|
+
return out(R('删体重记录', 'delete', '已删除体重 #' + id + '(' + r.date + ' ' + r.weight_kg + ' kg · ' + HARD_INNER + ')', '删体重记录', 'weight_log (写库回执)', {
|
|
491
|
+
recordId: id, ids: [id], writtenFields: [], items: [{ id, status: deleteStatus('hard'), reason: '' }],
|
|
410
492
|
}));
|
|
411
493
|
}
|
|
412
494
|
if (date !== undefined) {
|
|
413
495
|
assertISO(date, 'date');
|
|
414
496
|
const r = deleteWeightByDate(db, date);
|
|
415
|
-
return out(R('删某日体重', 'delete', '已删除 ' + date + ' 体重 ' + r.deletedCount + ' 条', '删某日体重', 'weight_log (写库回执)', {}));
|
|
497
|
+
return out(R('删某日体重', 'delete', '已删除 ' + date + ' 体重 ' + r.deletedCount + ' 条' + HARD_WORDING, '删某日体重', 'weight_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
416
498
|
}
|
|
417
499
|
if (start !== undefined || end !== undefined) {
|
|
418
500
|
if (start === undefined || end === undefined)
|
|
@@ -422,7 +504,7 @@ function dispatchInner(key, params, db) {
|
|
|
422
504
|
if (start > end)
|
|
423
505
|
fail(2, 'start 不得晚于 end');
|
|
424
506
|
const r = deleteWeightRange(db, start, end);
|
|
425
|
-
return out(R('批量删体重', 'delete', '已删除 ' + start + '~' + end + ' 体重 ' + r.deletedCount + ' 条', '批量删体重', 'weight_log (写库回执)', {}));
|
|
507
|
+
return out(R('批量删体重', 'delete', '已删除 ' + start + '~' + end + ' 体重 ' + r.deletedCount + ' 条' + HARD_WORDING, '批量删体重', 'weight_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: [] }));
|
|
426
508
|
}
|
|
427
509
|
fail(2, '缺参数 id/date/start+end(三选一)');
|
|
428
510
|
throw new Error('unreachable');
|
|
@@ -436,7 +518,8 @@ function dispatchInner(key, params, db) {
|
|
|
436
518
|
return { date: o['date'] === undefined ? undefined : String(o['date']), kg: o['kg'] };
|
|
437
519
|
}));
|
|
438
520
|
return out(R('批量补录体重', 'create', '批量记体重:写入 ' + r.wrote + ',跳过 ' + r.skipped + ',失败 ' + r.failed, '批量补录体重', 'weight_log (写库回执)', {
|
|
439
|
-
noChange: r.wrote === 0,
|
|
521
|
+
noChange: r.wrote === 0, ids: [], idSource: 'condition',
|
|
522
|
+
writtenFields: r.wrote > 0 ? [...F.weightBatch] : [],
|
|
440
523
|
items: r.items.filter((x) => x.status === '失败').slice(0, 20).map((x) => ({ status: '失败', reason: x.reason, detail: String(x.date) })),
|
|
441
524
|
}));
|
|
442
525
|
}
|
|
@@ -450,7 +533,8 @@ function dispatchInner(key, params, db) {
|
|
|
450
533
|
if (r.copied + r.skipped === 0)
|
|
451
534
|
throw new CalorieRenderError('missing-data', '昨日无运动记录可复制');
|
|
452
535
|
return out(R('复制昨日运动', 'create', '已复制昨日运动→' + target + ':复制 ' + r.copied + ',跳过 ' + r.skipped, '复制昨日运动', 'exercise_log (写库回执)', {
|
|
453
|
-
noChange: r.copied === 0,
|
|
536
|
+
noChange: r.copied === 0, ids: [], idSource: 'condition',
|
|
537
|
+
writtenFields: r.copied > 0 ? [...F.exercise] : [],
|
|
454
538
|
}));
|
|
455
539
|
}
|
|
456
540
|
if (params['items'] !== undefined) {
|
|
@@ -459,13 +543,15 @@ function dispatchInner(key, params, db) {
|
|
|
459
543
|
fail(2, 'items 至多 200 条');
|
|
460
544
|
const r = batchAdd(db, items.map((e, i) => oneExercise((e ?? {}), '(第' + i + '条)')));
|
|
461
545
|
return out(R('记运动', 'create', '批量记运动:新增 ' + r.added + ' 条', '批量补记运动', 'exercise_log (写库回执)', {
|
|
462
|
-
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] : [],
|
|
463
548
|
}));
|
|
464
549
|
}
|
|
465
550
|
const input = oneExercise(params, '');
|
|
466
551
|
const r = addRecord(db, input);
|
|
467
552
|
return out(R('记运动', 'create', '已记运动:' + input.exerciseType + ' ' + input.caloriesBurned + ' 卡' + (input.minutes ? ' · ' + input.minutes + ' 分钟' : '') + '(' + input.date + ')', '记运动', 'exercise_log (写库回执)', {
|
|
468
|
-
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 }],
|
|
469
555
|
}));
|
|
470
556
|
}
|
|
471
557
|
case 'calorie.exercise.update': {
|
|
@@ -503,7 +589,8 @@ function dispatchInner(key, params, db) {
|
|
|
503
589
|
fail(2, 'id 须为正整数');
|
|
504
590
|
updateRecord(db, id, fields);
|
|
505
591
|
return out(R('改运动记录', 'update', '已更新运动 #' + id + '(' + Object.keys(fields).join('、') + ')', '改运动记录', 'exercise_log (写库回执)', {
|
|
506
|
-
recordId: id,
|
|
592
|
+
recordId: id, ids: [id], writtenFields: cliNames(Object.keys(fields)),
|
|
593
|
+
items: [{ id, status: '已更新', reason: '' }],
|
|
507
594
|
}));
|
|
508
595
|
}
|
|
509
596
|
if (date !== undefined) {
|
|
@@ -511,7 +598,9 @@ function dispatchInner(key, params, db) {
|
|
|
511
598
|
const r = updateDay(db, date, fields);
|
|
512
599
|
if (r.matched === 0)
|
|
513
600
|
throw new CalorieRenderError('missing-data', '无运动记录(' + date + ')');
|
|
514
|
-
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
|
+
}));
|
|
515
604
|
}
|
|
516
605
|
fail(2, '缺参数 id 或 date(二选一)');
|
|
517
606
|
throw new Error('unreachable');
|
|
@@ -525,8 +614,8 @@ function dispatchInner(key, params, db) {
|
|
|
525
614
|
if (!Number.isInteger(id) || id <= 0)
|
|
526
615
|
fail(2, 'id 须为正整数');
|
|
527
616
|
deleteRecord(db, id);
|
|
528
|
-
return out(R('删运动记录', 'delete', '已删除运动 #' + id, '删运动记录', 'exercise_log (写库回执)', {
|
|
529
|
-
recordId: id, items: [{ id, status: '
|
|
617
|
+
return out(R('删运动记录', 'delete', '已删除运动 #' + id + SOFT_EXCLUDED, '删运动记录', 'exercise_log (写库回执)', {
|
|
618
|
+
recordId: id, ids: [id], writtenFields: ['is_deleted'], items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
530
619
|
}));
|
|
531
620
|
}
|
|
532
621
|
if (date !== undefined) {
|
|
@@ -534,7 +623,7 @@ function dispatchInner(key, params, db) {
|
|
|
534
623
|
const n = deleteDay(db, date);
|
|
535
624
|
if (n === 0)
|
|
536
625
|
throw new CalorieRenderError('missing-data', '无运动记录(' + date + ')');
|
|
537
|
-
return out(R('删某日运动', 'delete', '已删除 ' + date + ' 运动 ' + n + ' 条', '删某日运动', 'exercise_log (写库回执)', {}));
|
|
626
|
+
return out(R('删某日运动', 'delete', '已删除 ' + date + ' 运动 ' + n + ' 条' + SOFT_EXCLUDED, '删某日运动', 'exercise_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: ['is_deleted'] }));
|
|
538
627
|
}
|
|
539
628
|
if (from !== undefined || to !== undefined) {
|
|
540
629
|
if (from === undefined || to === undefined)
|
|
@@ -546,7 +635,7 @@ function dispatchInner(key, params, db) {
|
|
|
546
635
|
const n = deleteRange(db, from, to);
|
|
547
636
|
if (n === 0)
|
|
548
637
|
throw new CalorieRenderError('missing-data', '无运动记录(' + from + '~' + to + ')');
|
|
549
|
-
return out(R('批量删运动', 'delete', '已删除 ' + from + '~' + to + ' 运动 ' + n + ' 条', '批量删运动', 'exercise_log (写库回执)', {}));
|
|
638
|
+
return out(R('批量删运动', 'delete', '已删除 ' + from + '~' + to + ' 运动 ' + n + ' 条' + SOFT_EXCLUDED, '批量删运动', 'exercise_log (写库回执)', { ids: [], idSource: 'condition', writtenFields: ['is_deleted'] }));
|
|
550
639
|
}
|
|
551
640
|
fail(2, '缺参数 id/date/from+to(三选一)');
|
|
552
641
|
throw new Error('unreachable');
|
|
@@ -573,7 +662,9 @@ function dispatchInner(key, params, db) {
|
|
|
573
662
|
catch {
|
|
574
663
|
distance = null;
|
|
575
664
|
}
|
|
576
|
-
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
|
+
});
|
|
577
668
|
return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
|
|
578
669
|
}
|
|
579
670
|
case 'calorie.photo.remove': {
|
|
@@ -583,7 +674,7 @@ function dispatchInner(key, params, db) {
|
|
|
583
674
|
if (!snap)
|
|
584
675
|
throw new CalorieRenderError('missing-data', '身材照 #' + id + ' 不存在');
|
|
585
676
|
deletePhoto(db, dir, id);
|
|
586
|
-
const receipt = buildDeleteReceipt(snap);
|
|
677
|
+
const receipt = withM5(buildDeleteReceipt(snap), { ids: [id], writtenFields: [] });
|
|
587
678
|
return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
|
|
588
679
|
}
|
|
589
680
|
case 'calorie.photo.tag': {
|
|
@@ -617,7 +708,7 @@ function dispatchInner(key, params, db) {
|
|
|
617
708
|
scene = '删照片标签';
|
|
618
709
|
}
|
|
619
710
|
const after = getPhotoRow(db, id)?.tag_list ?? before;
|
|
620
|
-
const receipt = buildTagReceipt(id, before, after, scene);
|
|
711
|
+
const receipt = withM5(buildTagReceipt(id, before, after, scene), { ids: [id], writtenFields: op === 'set' ? ['tags'] : ['tag'] });
|
|
621
712
|
return { data: { ok: true, message: receipt.summary, receipt }, html: receiptHtml(receipt.scene, receipt.summary, receipt.op, receipt.recordId, receipt.items) };
|
|
622
713
|
}
|
|
623
714
|
case 'calorie.product.add': {
|
|
@@ -632,7 +723,8 @@ function dispatchInner(key, params, db) {
|
|
|
632
723
|
sodium: needNum(params, 'sodium'), note: optStr(params, 'note'),
|
|
633
724
|
});
|
|
634
725
|
return out(R('存食品', 'create', '已存食品 #' + r.id + '(' + productName.trim() + ')', '存食品', 'nutrition_products (写库回执)', {
|
|
635
|
-
recordId: r.id,
|
|
726
|
+
recordId: r.id, ids: [r.id], writtenFields: [...F.product],
|
|
727
|
+
items: [{ id: r.id, status: '成功', reason: '', detail: productName.trim() }],
|
|
636
728
|
}));
|
|
637
729
|
}
|
|
638
730
|
case 'calorie.product.update': {
|
|
@@ -657,7 +749,8 @@ function dispatchInner(key, params, db) {
|
|
|
657
749
|
if (!r.updated)
|
|
658
750
|
throw new CalorieRenderError('missing-data', '食品 #' + id + ' 不存在');
|
|
659
751
|
return out(R('改食品', 'update', '已更新食品 #' + id + '(' + Object.keys(fields).join('、') + ')', '改食品', 'nutrition_products (写库回执)', {
|
|
660
|
-
recordId: id,
|
|
752
|
+
recordId: id, ids: [id], writtenFields: cliNames(Object.keys(fields)),
|
|
753
|
+
items: [{ id, status: '已更新', reason: '' }],
|
|
661
754
|
}));
|
|
662
755
|
}
|
|
663
756
|
case 'calorie.product.deprecate': {
|
|
@@ -668,8 +761,8 @@ function dispatchInner(key, params, db) {
|
|
|
668
761
|
throw new CalorieRenderError('missing-data', '食品 #' + id + ' 不存在');
|
|
669
762
|
fail(2, String(r.error ?? '废弃失败'));
|
|
670
763
|
}
|
|
671
|
-
return out(R('下架食品', 'update', '已下架食品 #' + id + '(' + (r.name ?? '') + '
|
|
672
|
-
recordId: id, items: [{ id, status: '已下架', reason: '' }],
|
|
764
|
+
return out(R('下架食品', 'update', '已下架食品 #' + id + '(' + (r.name ?? '') + ' · ' + SOFT_EXCLUDED_INNER + ')', '下架食品', 'nutrition_products (写库回执)', {
|
|
765
|
+
recordId: id, ids: [id], writtenFields: ['is_deprecated'], items: [{ id, status: deleteStatus('soft', '已下架'), reason: '' }],
|
|
673
766
|
}));
|
|
674
767
|
}
|
|
675
768
|
case 'calorie.profile.set':
|
|
@@ -702,7 +795,7 @@ function dispatchInner(key, params, db) {
|
|
|
702
795
|
const r = updateProfile(db, picked);
|
|
703
796
|
const after = r.after;
|
|
704
797
|
return out(R(scene, 'update', '已' + scene + '(身高 ' + (after.height_cm ?? '—') + ' · 年龄 ' + (after.age ?? '—') + ' · 活动量 ' + (after.activity_level ?? '—') + ')', wake, 'user_profile (写库回执)', {
|
|
705
|
-
recordId: 1, noChange: r.changed.length === 0,
|
|
798
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: Object.keys(picked), noChange: r.changed.length === 0,
|
|
706
799
|
}));
|
|
707
800
|
}
|
|
708
801
|
case 'calorie.profile.activity': {
|
|
@@ -711,7 +804,7 @@ function dispatchInner(key, params, db) {
|
|
|
711
804
|
fail(2, '缺参数 activityLevel');
|
|
712
805
|
const r = setActivityLevel(db, level);
|
|
713
806
|
return out(R('设活动量', 'update', '已设活动量:' + (r.before ?? '—') + '→' + r.after, '设活动量', 'user_profile (写库回执)', {
|
|
714
|
-
recordId: 1, noChange: r.before === r.after,
|
|
807
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: ['activityLevel'], noChange: r.before === r.after,
|
|
715
808
|
}));
|
|
716
809
|
}
|
|
717
810
|
case 'calorie.goal.set': {
|
|
@@ -732,7 +825,7 @@ function dispatchInner(key, params, db) {
|
|
|
732
825
|
const r = setNutritionGoal(db, { calorie, protein, carbs, fat, water });
|
|
733
826
|
const tail = r.consistent ? ' · 宏量自洽' : ' · ⚠宏量换算差 ' + r.diffKcal + ' 卡(>50 建议复核)';
|
|
734
827
|
return out(R('定营养目标', had ? 'update' : 'create', '已定营养目标:' + r.calorieGoal + ' 卡·蛋白 ' + r.proteinGoal + '·碳水 ' + r.carbsGoal + '·脂肪 ' + r.fatGoal + (r.waterGoal === null ? '' : '·饮水 ' + r.waterGoal) + tail, '定营养目标', 'daily_goal (写库回执)', {
|
|
735
|
-
recordId: 1,
|
|
828
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: goalSetWrittenFields(water !== undefined),
|
|
736
829
|
}));
|
|
737
830
|
}
|
|
738
831
|
case 'calorie.goal.water': {
|
|
@@ -741,7 +834,7 @@ function dispatchInner(key, params, db) {
|
|
|
741
834
|
throw new CalorieRenderError('missing-data', '尚无营养目标行(先定营养目标)');
|
|
742
835
|
const r = updateWaterGoal(db, water);
|
|
743
836
|
return out(R('定饮水目标', 'update', '已定饮水目标:' + (r.oldWaterGoal ?? '—') + '→' + r.newWaterGoal + ' ml', '定饮水目标', 'daily_goal (写库回执)', {
|
|
744
|
-
recordId: 1, noChange: r.oldWaterGoal === r.newWaterGoal,
|
|
837
|
+
recordId: 1, ids: [1], idSource: 'singleton', writtenFields: ['water'], noChange: r.oldWaterGoal === r.newWaterGoal,
|
|
745
838
|
}));
|
|
746
839
|
}
|
|
747
840
|
case 'calorie.goal.weight': {
|
|
@@ -752,19 +845,20 @@ function dispatchInner(key, params, db) {
|
|
|
752
845
|
kg, deadline: params['deadline'], startKg: params['startKg'], startDate: params['startDate'],
|
|
753
846
|
});
|
|
754
847
|
return out(R('定体重目标', 'update', '已定体重目标 ' + r.weightGoal + ' kg' + (r.deadline ? '(截止 ' + r.deadline + ')' : '') + (r.startKg !== null ? ' · 起点 ' + r.startKg + ' kg' : ''), '定体重目标', 'daily_goal (写库回执)', {
|
|
755
|
-
recordId: 1,
|
|
848
|
+
recordId: 1, ids: [1], idSource: 'singleton',
|
|
849
|
+
writtenFields: provided(params, ['kg', 'deadline', 'startKg', 'startDate']),
|
|
756
850
|
}));
|
|
757
851
|
}
|
|
758
852
|
case 'calorie.goal.pause': {
|
|
759
853
|
const r = pauseAllGoals(db);
|
|
760
854
|
return out(R('暂停所有目标', 'update', '已暂停所有目标(记录照常,仅目标暂停)', '暂停所有目标', 'daily_goal (写库回执)', {
|
|
761
|
-
recordId: r.id,
|
|
855
|
+
recordId: r.id, ids: [r.id], idSource: 'singleton', writtenFields: ['goal_paused'],
|
|
762
856
|
}));
|
|
763
857
|
}
|
|
764
858
|
case 'calorie.goal.resume': {
|
|
765
859
|
const r = resumeAllGoals(db);
|
|
766
860
|
return out(R('重启所有目标', 'update', '已重启所有目标(恢复正常)', '重启所有目标', 'daily_goal (写库回执)', {
|
|
767
|
-
recordId: r.id,
|
|
861
|
+
recordId: r.id, ids: [r.id], idSource: 'singleton', writtenFields: ['goal_paused'],
|
|
768
862
|
}));
|
|
769
863
|
}
|
|
770
864
|
case 'calorie.body.composition-add': {
|
|
@@ -785,14 +879,15 @@ function dispatchInner(key, params, db) {
|
|
|
785
879
|
const r = addComposition(db, input);
|
|
786
880
|
const label = SOURCE_LABELS[source] ?? source;
|
|
787
881
|
return out(R('记体脂', 'create', '已记体脂:' + date + ' ' + label + ' ' + r.bodyFatPct + '%', '记体脂', 'body_composition (写库回执)', {
|
|
788
|
-
recordId: r.id,
|
|
882
|
+
recordId: r.id, ids: [r.id], writtenFields: definedKeys(input),
|
|
883
|
+
items: [{ id: r.id, date, status: '成功', reason: '', detail: r.bodyFatPct + '%' }],
|
|
789
884
|
}));
|
|
790
885
|
}
|
|
791
886
|
case 'calorie.body.composition-remove': {
|
|
792
887
|
const id = needId(params);
|
|
793
888
|
deleteComposition(db, id);
|
|
794
|
-
return out(R('删体脂', 'delete', '已删除体脂记录 #' + id +
|
|
795
|
-
recordId: id, items: [{ id, status: '
|
|
889
|
+
return out(R('删体脂', 'delete', '已删除体脂记录 #' + id + SOFT_EXCLUDED, '删体脂', 'body_composition (写库回执)', {
|
|
890
|
+
recordId: id, ids: [id], writtenFields: ['is_deprecated'], items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
796
891
|
}));
|
|
797
892
|
}
|
|
798
893
|
case 'calorie.body.measure-add': {
|
|
@@ -814,14 +909,15 @@ function dispatchInner(key, params, db) {
|
|
|
814
909
|
return camel + ' ' + String(input[f]);
|
|
815
910
|
}).join('、');
|
|
816
911
|
return out(R('记围度', 'create', '已记围度:' + date + '(' + filledCn + ')', '记围度', 'body_measurements (写库回执)', {
|
|
817
|
-
recordId: r.id,
|
|
912
|
+
recordId: r.id, ids: [r.id], writtenFields: measureCliNames(definedKeys(input)),
|
|
913
|
+
items: [{ id: r.id, date, status: '成功', reason: '', detail: filledCn }],
|
|
818
914
|
}));
|
|
819
915
|
}
|
|
820
916
|
case 'calorie.body.measure-remove': {
|
|
821
917
|
const id = needId(params);
|
|
822
918
|
deleteMeasurement(db, id);
|
|
823
|
-
return out(R('删围度', 'delete', '已删除围度记录 #' + id +
|
|
824
|
-
recordId: id, items: [{ id, status: '
|
|
919
|
+
return out(R('删围度', 'delete', '已删除围度记录 #' + id + SOFT_EXCLUDED, '删围度', 'body_measurements (写库回执)', {
|
|
920
|
+
recordId: id, ids: [id], writtenFields: ['is_deprecated'], items: [{ id, status: deleteStatus('soft'), reason: '' }],
|
|
825
921
|
}));
|
|
826
922
|
}
|
|
827
923
|
default:
|