清洁

清洁计划、真正做过的那些任务,以及证明它做过的照片——三个集合,以及一条缺失的记录证明不了什么。

应用里的清洁,是一份计划加上它的凭证。餐厅写下要清洁什么、在哪里、多久一次;员工 做完一项就勾掉一项;其中有些任务要求拍一张照片。三个集合,每一层一个,而它们之间 的连接才是大部分工作所在。

集合权限范围一条记录是什么
cleaning-taskscleaning-tasks:read计划里的一行:必须清洁什么、在哪里、多久一次、是否需要一张照片。
cleaning-task-recordscleaning-task-records:read一项确实做过的任务——什么时候做的,谁做的。
cleaning-task-picturescleaning-task-pictures:read证明它做过的那张照片。带文件。

这三个共用同一个快照信封iddeletedcapturedAtreceivedAtsequence 以及其余字段,都摆在下文描述的 data 旁边。

cleaning-tasks

计划本身。它们很少变——一家餐厅把计划写一次,等厨房变了再改它——所以这是你遍历得 最少、缓存得最久的那个集合。

字段类型含义
indexinteger, 必填这项任务在计划中的位置,按应用给它们排的顺序。
namestring, 必填必须清洁的是什么,用这家餐厅自己的说法。
descriptionstring | null更长的说明,在有人写了的时候。
areaIdstring | null它所属的那个区域。在一家不把自己划分成区域的餐厅里为 null
recurrenceinteger, 必填这项任务多久轮到一次,就是一个普通整数。
requirePhotoProofboolean | null任务要求拍照时为 truenull 表示应用从来没设置过它。
isDeletedboolean, 必填应用自己的软删除。请读下面那条说明。
{
  "id": "c9a4f7e2-1b83-4d05-a6f1-90e3b7c4d218",
  "collection": "cleaning-tasks",
  "deleted": false,
  "capturedAt": "1789601204773",
  "receivedAt": "1789601399042",
  "sequence": "1",
  "data": {
    "index": 3,
    "name": "Nettoyage de la trancheuse",
    "description": "Démonter la lame, dégraisser, désinfecter.",
    "areaId": "7d2c0b61-4e8a-49f3-9c27-5a1b8e60d3f4",
    "recurrence": 1,
    "requirePhotoProof": true,
    "isDeleted": false
  }
}

recurrence 不带单位。 它是一个整数,而记录并没有说它数的是什么。请照应用 显示它的样子显示它,而不要靠猜渲染出一句「每 1 天」。

isDeleted 不是信封上的 deleted 一项被餐厅从计划里移走的任务,回来时是 isDeleted: truedeleted: false:它仍然是一条活着的记录,描述的是一行已经不 再适用的内容。两个都要筛,并且把被删掉的那些留着——老记录仍然指向它们。

cleaning-task-records

一项做过的任务。有意做得很小:哪项任务、什么时候、谁。

字段类型含义
timestampstring, 必填这项任务是什么时候做的,以字符串表示的 Unix 毫秒时间戳。
cleaningTaskIdstring, 必填它所满足的那条 cleaning-tasks 记录。
userIdstring | null做这件事的那名员工,在应用记下了一个的时候。
{
  "id": "5f8b3d16-2c90-4a77-b4e8-31d0a9c65f7b",
  "collection": "cleaning-task-records",
  "deleted": false,
  "capturedAt": "1789775012558",
  "receivedAt": "1789775204910",
  "sequence": "2",
  "data": {
    "timestamp": "1789774800000",
    "cleaningTaskId": "c9a4f7e2-1b83-4d05-a6f1-90e3b7c4d218",
    "userId": "2774953d-8d9b-4a68-8ec4-1209edd90777"
  }
}

cleaningTaskId 是必填的,所以每条记录都有一项任务。反过来并不成立:一项任务可以 在计划里待上几周而没有任何记录对着它,而那正是一项每周任务在星期二时的正常状态。

cleaning-task-pictures

那张照片。它带着一个文件,这让它成为这里唯一一个你没法只靠 records 端点就消费完 的集合。

字段类型含义
timestampstring, 必填这张照片是什么时候拍的,以字符串表示的 Unix 毫秒时间戳。
cleaningTaskRecordIdstring | null它所证明的那条 cleaning-task-records 记录。可以为 null——见下文。
partnumber | null没有文档说明,而且是一个小数,不是一个计数器。请原样带着它,而不要去解读它——和追溯标签上的那条提醒一样。
groupIdstring | null把那些照片绑在一起。共用一个 groupId 的记录属于同一份凭证。
assetobject, 必填点出那个文件:objectKeystatus 始终都有,一旦上传完成,还会有 contentTypebyteLengthsha256
{
  "id": "2d71c8a9-6e34-4b0f-8517-ac93e25d0b46",
  "collection": "cleaning-task-pictures",
  "deleted": false,
  "capturedAt": "1789775118330",
  "receivedAt": "1789775301774",
  "sequence": "3",
  "data": {
    "timestamp": "1789774860000",
    "cleaningTaskRecordId": "5f8b3d16-2c90-4a77-b4e8-31d0a9c65f7b",
    "part": 1,
    "groupId": "a03e5c88-71bd-4f92-b6d4-2e8f10c73a5b",
    "asset": {
      "objectKey": "cleaning-task-pictures/2d71c8a9/1.jpg",
      "status": "uploaded",
      "contentType": "image/jpeg",
      "byteLength": 812043,
      "sha256": "9f2a…"
    }
  }
}

字节内容要再调一次,针对这条记录自己的 id:

GET /v1/restaurants/{restaurantId}/collections/cleaning-task-pictures/records/{recordId}/assets

它返回一组签名 URL,每个有效十五分钟,每个都自带授权,所以下载不需要任何请求 头。现在就把字节取下来,存字节而不要存链接,需要一个新 URL 的时候再调一次这个端 点。那些规则——什么会出现、sha256 是做什么用的、为什么 contentType 值得一读 ——都在集合那一页上,而且在这里是一样的。

asset.status 告诉你有没有字节可取。 它是 pendinguploaded。一个 pending 的附件,是设备已经宣告、却还没送完的一张照片:记录是真的,文件还不是, 而 assets 端点作答时不会带上它。在你把一个空的附件列表当成一张从未拍过的照片之 前,先读这个状态。

cleaningTaskRecordId 可以为 null。 一张照片可以在不指向任何记录的情况下存 在,所以一个假定它总是在那儿的连接,会把照片丢在地上。把它们单独计数,而不要悄无 声息地扔掉。

把三个连起来

你几乎肯定想要的那个结构,是每做过一项任务一行,带上它在计划里的那一条和它的那些 照片:

  • cleaning-task-records.cleaningTaskIdcleaning-tasks.id
  • cleaning-task-pictures.cleaningTaskRecordIdcleaning-task-records.id

三次遍历,在你这边做连接。没有哪个端点会替你做这件事,也没有按任务或按日期的过 滤——你把每个集合整个走完,然后对账,就像信封那一页描述的 那样。

capturedAt 排序,而不是 receivedAt:一台放在冷库里的平板会在找到信号时才上 传,而一张 09:00 拍下的照片可能 14:00 才到——比它所属的那条记录晚,也可能比它早。

一条缺失的记录并不意味着什么

计划里一条没有对应记录的条目,并不能证明这项任务被跳过了。 它可能是在一台还没 上传的设备上做的,也可能是在为那家餐厅开启采集之前做的。一项标着 requirePhotoProof: true 却没有照片对着它的任务也一样:那张照片可能正在上传途中, 而一个还没上传完的附件是缺席,而不是坏掉。

所以不要拿这个 API 印出一个清洁完成率,然后把它叫作合规。你能诚实地说出来的,是 什么东西到了、以及什么时候到的——在你把一个数字摆到任何人面前之前,先看 关于完整性的说明

最后更新于 2026-09-20。