{
  "openapi": "3.0.3",
  "info": {
    "title": "VWCG-1344 生日妈妈菜增量接口",
    "version": "1.0.0",
    "description": "PC 端生日妈妈菜维护与安徽智慧食堂大屏当天生日展示。Excel 导入为单接口：有效行导入、无效行返回原因；同一人员最后一条有效菜品覆盖。"
  },
  "servers": [{ "url": "/" }],
  "tags": [
    { "name": "生日妈妈菜", "description": "PC 餐厅管理" },
    { "name": "安徽智慧食堂大屏", "description": "匿名大屏查询" }
  ],
  "paths": {
    "/p/BirthdayDish/lists": {
      "post": {
        "tags": ["生日妈妈菜"],
        "summary": "分页查询生日人员及妈妈菜",
        "description": "按具体餐厅、日期、填写状态和关键词查询。日期开始、结束同时为空表示不限生日日期；只填写一端失败。餐厅 ID 会与当前账号餐厅权限取交集，部门和 store_id 也由后端收口。",
        "security": [{ "Token": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/BirthdayDishListRequest" },
              "example": { "restaurant_id": 1, "date_start": "2026-09-07", "date_end": "2026-09-13", "fill_status": "unfilled", "keyword": "0012", "page": 1, "page_size": 20 }
            }
          }
        },
        "responses": {
          "200": {
            "description": "业务结果，code=0 为成功",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BirthdayDishListResponse" } } }
          }
        }
      }
    },
    "/p/BirthdayDish/dishOptions": {
      "post": {
        "tags": ["生日妈妈菜"],
        "summary": "查询可关联菜品",
        "description": "仅返回当前 store_id 下 status=1、is_del=1、is_delete=0 的菜品。传 page 时返回分页对象，用于虚拟列表滚动加载；不传 page 时保持旧版菜品数组响应。精确同名结果优先，用于直接输入后自动解析 dishes_uuid。",
        "security": [{ "Token": [] }],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "keyword": { "type": "string", "maxLength": 128 },
                  "page": { "type": "integer", "minimum": 1, "description": "传入后启用分页响应；不传保持旧版数组响应" },
                  "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
                }
              },
              "example": { "keyword": "番茄炒蛋", "page": 1, "limit": 20 }
            }
          }
        },
        "responses": {
          "200": {
            "description": "菜品候选",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DishOptionsResponse" } } }
          }
        }
      }
    },
    "/p/BirthdayDish/save": {
      "post": {
        "tags": ["生日妈妈菜"],
        "summary": "单人保存妈妈菜",
        "description": "新增时 version=0；更新时必须提交列表返回的当前 version。服务端强制写入 getStoreId()。",
        "security": [{ "Token": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": { "$ref": "#/components/schemas/BirthdayDishSingleSaveRequest" },
              "example": { "restaurant_id": 1, "staff_uuid": "STAFF-UUID", "dishes_uuid": "DISH-UUID", "version": 0 }
            }
          }
        },
        "responses": {
          "200": {
            "description": "保存成功或版本冲突等业务失败",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SaveResponse" } } }
          }
        }
      }
    },
    "/p/BirthdayDish/batchSave": {
      "post": {
        "tags": ["生日妈妈菜"],
        "summary": "页面批量保存妈妈菜",
        "description": "最多 200 人；items 任意一行失败时整个批次零写入。",
        "security": [{ "Token": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": ["restaurant_id", "items"],
                "properties": {
                  "restaurant_id": { "type": "integer", "minimum": 1, "description": "当前级联筛选选中的具体餐厅 ID" },
                  "items": {
                    "type": "string",
                    "description": "BirthdayDishSaveItem 数组的 JSON 字符串"
                  }
                }
              },
              "example": { "restaurant_id": 1, "items": "[{\"staff_uuid\":\"STAFF-UUID\",\"dishes_uuid\":\"DISH-UUID\",\"version\":0}]" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "整批结果",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchSaveResponse" } } }
          }
        }
      }
    },
    "/p/BirthdayDish/template": {
      "get": {
        "tags": ["生日妈妈菜"],
        "summary": "下载生日妈妈菜 Excel 模板",
        "description": "模板列固定为人员编号、姓名、妈妈菜；人员编号按文本写入。当前筛选超过 1000 人时拒绝下载。",
        "security": [{ "Token": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/RestaurantId" },
          { "$ref": "#/components/parameters/DateStart" },
          { "$ref": "#/components/parameters/DateEnd" },
          { "$ref": "#/components/parameters/FillStatus" },
          { "$ref": "#/components/parameters/Keyword" }
        ],
        "responses": {
          "200": {
            "description": "成功时为 xlsx 文件；失败时为 JSON 业务错误",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": { "schema": { "type": "string", "format": "binary" } },
              "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } }
            }
          }
        }
      }
    },
    "/p/BirthdayDish/import": {
      "post": {
        "tags": ["生日妈妈菜"],
        "summary": "导入生日妈妈菜 Excel",
        "description": "仅一个导入接口。支持 xls/xlsx，最大 5MB、1000 行；表头固定。人员编号非必填：有编号时优先按编号匹配，编号为空时按当前餐厅/部门权限内姓名唯一精确匹配，同名多人要求补充编号。妈妈菜按菜品名称唯一精确匹配。有效行导入，无效行返回原因；同一人员最后一条有效菜品覆盖。文件级错误整份拒绝，SQL 异常时全部有效行回滚。",
        "security": [{ "Token": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["restaurant_id", "file"],
                "properties": {
                  "restaurant_id": { "type": "integer", "minimum": 1, "description": "当前级联筛选选中的具体餐厅 ID" },
                  "file": { "type": "string", "format": "binary" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "导入成功或逐行错误",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchSaveResponse" } } }
          }
        }
      }
    },
    "/api/screen/anhuiBoard": {
      "get": {
        "tags": ["安徽智慧食堂大屏"],
        "summary": "查询安徽智慧食堂大屏",
        "description": "保持匿名访问。date 只控制周菜谱参考周；生日人员始终由服务器当天日期查询，不允许通过 date 枚举历史生日。妈妈菜上屏不校验菜品启停/逻辑删除状态；物理菜品不存在时返回默认菜名。",
        "security": [],
        "parameters": [
          { "name": "restaurant_id", "in": "query", "required": true, "schema": { "type": "integer", "minimum": 1 }, "example": 1 },
          { "name": "date", "in": "query", "required": false, "description": "周菜谱参考日期，不影响生日人员日期", "schema": { "type": "string", "format": "date" } }
        ],
        "responses": {
          "200": {
            "description": "大屏快照",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnhuiBoardResponse" } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "Token": { "type": "apiKey", "in": "header", "name": "token", "description": "PC 登录 token" }
    },
    "parameters": {
      "RestaurantId": { "name": "restaurant_id", "in": "query", "required": true, "description": "当前级联筛选选中的具体餐厅 ID", "schema": { "type": "integer", "minimum": 1 } },
      "DateStart": { "name": "date_start", "in": "query", "required": false, "description": "与 date_end 同时为空时不限制生日日期", "schema": { "type": "string", "format": "date" } },
      "DateEnd": { "name": "date_end", "in": "query", "required": false, "description": "与 date_start 同时为空时不限制生日日期", "schema": { "type": "string", "format": "date" } },
      "FillStatus": { "name": "fill_status", "in": "query", "required": false, "schema": { "type": "string", "enum": ["", "unfilled", "filled"] } },
      "Keyword": { "name": "keyword", "in": "query", "required": false, "schema": { "type": "string" } }
    },
    "schemas": {
      "BirthdayDishListRequest": {
        "type": "object",
        "required": ["restaurant_id"],
        "properties": {
          "restaurant_id": { "type": "integer", "minimum": 1, "description": "当前级联筛选选中的具体餐厅 ID" },
          "date_start": { "type": "string", "format": "date", "description": "与 date_end 同时为空时不限生日日期" },
          "date_end": { "type": "string", "format": "date", "description": "与 date_start 同时为空时不限生日日期；非空范围最多 366 天" },
          "fill_status": { "type": "string", "enum": ["", "unfilled", "filled"] },
          "keyword": { "type": "string", "description": "人员编号、姓名或妈妈菜" },
          "page": { "type": "integer", "minimum": 1, "default": 1 },
          "page_size": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
        }
      },
      "BirthdayDishListItem": {
        "type": "object",
        "properties": {
          "staff_uuid": { "type": "string" },
          "personnel_umber": { "type": "string", "description": "保留前导零" },
          "staff_name": { "type": "string" },
          "birthday": { "type": "string", "format": "date" },
          "dishes_uuid": { "type": "string", "nullable": true },
          "dish_name": { "type": "string" },
          "dish_image": { "type": "string" },
          "fill_status": { "type": "string", "enum": ["待填写", "已填写"] },
          "version": { "type": "integer" },
          "update_time": { "type": "string", "nullable": true }
        }
      },
      "BirthdayDishSaveItem": {
        "type": "object",
        "required": ["staff_uuid", "dishes_uuid", "version"],
        "properties": {
          "staff_uuid": { "type": "string" },
          "dishes_uuid": { "type": "string" },
          "version": { "type": "integer", "minimum": 0 }
        }
      },
      "BirthdayDishSingleSaveRequest": {
        "allOf": [
          { "$ref": "#/components/schemas/BirthdayDishSaveItem" },
          {
            "type": "object",
            "required": ["restaurant_id"],
            "properties": {
              "restaurant_id": { "type": "integer", "minimum": 1, "description": "当前级联筛选选中的具体餐厅 ID" }
            }
          }
        ]
      },
      "BirthdayDishSavedItem": {
        "type": "object",
        "properties": {
          "uuid": { "type": "string" },
          "staff_uuid": { "type": "string" },
          "dishes_uuid": { "type": "string" },
          "version": { "type": "integer" }
        }
      },
      "DishOption": {
        "type": "object",
        "properties": {
          "uuid": { "type": "string" },
          "name": { "type": "string" },
          "category": { "type": "integer" },
          "category_one": { "type": "integer" },
          "category_two": { "type": "integer" },
          "file_path": { "type": "string" }
        }
      },
      "ImportRowError": {
        "type": "object",
        "properties": {
          "row_no": { "type": "integer" },
          "personnel_umber": { "type": "string", "description": "Excel 该行人员编号，已唯一匹配时返回人员档案编号" },
          "staff_name": { "type": "string", "description": "Excel 该行姓名，已唯一匹配时返回人员档案姓名" },
          "dish_name": { "type": "string", "description": "Excel 该行填写的妈妈菜" },
          "code": { "type": "string", "enum": ["HEADER_INVALID", "EXTRA_COLUMN", "STAFF_IDENTITY_REQUIRED", "DUPLICATE_STAFF", "STAFF_NOT_FOUND", "STAFF_AMBIGUOUS", "DISH_REQUIRED", "DISH_NOT_FOUND", "DISH_AMBIGUOUS", "REQUIRED", "DISH_INVALID"] },
          "message": { "type": "string" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "example": 1 },
          "message": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "error_code": { "type": "string", "example": "VERSION_CONFLICT" },
              "errors": { "type": "array", "items": { "$ref": "#/components/schemas/ImportRowError" } }
            }
          }
        }
      },
      "BirthdayDishListResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "example": 0 },
          "message": { "type": "string", "example": "操作成功" },
          "data": {
            "type": "object",
            "properties": {
              "rows": { "type": "array", "items": { "$ref": "#/components/schemas/BirthdayDishListItem" } },
              "total": { "type": "integer" },
              "page": { "type": "integer" },
              "page_size": { "type": "integer" },
              "date_start": { "type": "string", "format": "date" },
              "date_end": { "type": "string", "format": "date" }
            }
          }
        }
      },
      "DishOptionsResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "example": 0 },
          "message": { "type": "string" },
          "data": {
            "oneOf": [
              { "type": "array", "items": { "$ref": "#/components/schemas/DishOption" }, "description": "未传 page 时的兼容响应" },
              {
                "type": "object",
                "required": ["rows", "total", "page", "page_size", "has_more"],
                "properties": {
                  "rows": { "type": "array", "items": { "$ref": "#/components/schemas/DishOption" } },
                  "total": { "type": "integer", "minimum": 0 },
                  "page": { "type": "integer", "minimum": 1 },
                  "page_size": { "type": "integer", "minimum": 1, "maximum": 50 },
                  "has_more": { "type": "boolean" }
                }
              }
            ]
          }
        }
      },
      "SaveResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "example": 0 },
          "message": { "type": "string" },
          "data": { "$ref": "#/components/schemas/BirthdayDishSavedItem" }
        }
      },
      "BatchSaveResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "example": 0 },
          "message": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "count": { "type": "integer" },
              "total_count": { "type": "integer" },
              "success_count": { "type": "integer" },
              "fail_count": { "type": "integer" },
              "duplicate_overwrite_count": { "type": "integer" },
              "rows": { "type": "array", "items": { "$ref": "#/components/schemas/BirthdayDishSavedItem" } },
              "error_code": { "type": "string", "example": "IMPORT_VALIDATION_FAILED" },
              "errors": { "type": "array", "items": { "$ref": "#/components/schemas/ImportRowError" } }
            }
          }
        }
      },
      "BirthdayPerson": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "avatar": { "type": "string" },
          "dish_uuid": { "type": "string" },
          "dish_name": { "type": "string" },
          "dish_image": { "type": "string" }
        },
        "description": "不得增加出生年份、人员编号、手机号、证件号或余额"
      },
      "AnhuiBoardResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "integer", "example": 0 },
          "message": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "restaurant_id": { "type": "integer" },
              "restaurant_name": { "type": "string" },
              "birthday_people": { "type": "array", "items": { "$ref": "#/components/schemas/BirthdayPerson" } },
              "updated_at": { "type": "string" }
            }
          }
        }
      }
    }
  }
}
