openapi: 3.1.0
info:
  title: 鲜到发车门店收银系统 P0 API
  version: 0.1.0
  description: >
    当前为启动阶段接口契约草案。真实开发前需要用鲜到发车正式 API 文档校准字段、
    签名、幂等键、错误码和重试策略。
servers:
  - url: http://localhost:4173
paths:
  /api/health:
    get:
      summary: 服务健康检查
      responses:
        "200":
          description: 服务可用
  /api/users:
    get:
      summary: 查询本地演示用户
      responses:
        "200":
          description: 用户列表
  /api/auth/login:
    post:
      summary: 演示登录
      description: 使用角色和 PIN 获取 Bearer 会话令牌，admin=1001、cashier=1002、manager=1003。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [role, pin]
              properties:
                role:
                  type: string
                  enum: [admin, cashier, manager]
                pin:
                  type: string
      responses:
        "200":
          description: 登录成功
        "401":
          description: 角色或 PIN 错误
  /api/auth/me:
    get:
      summary: 查询当前登录用户
      responses:
        "200":
          description: 当前用户
        "401":
          description: 未登录
  /api/auth/logout:
    post:
      summary: 退出登录
      responses:
        "200":
          description: 已退出
  /api/dev/reset:
    post:
      summary: 重置本地开发数据
      description: 仅本地开发使用，需要 x-operator 为 admin。
      responses:
        "200":
          description: 数据已重置
        "403":
          description: 当前角色无权限
  /api/dev/export:
    get:
      summary: 导出本地开发数据
      description: 返回当前 JSON 状态、导出时间和各实体计数，便于演示备份与交接。
      responses:
        "200":
          description: 导出成功
  /api/dev/import:
    post:
      summary: 导入本地开发数据
      description: 仅本地开发使用，需要 x-operator 为 admin。可提交 `/api/dev/export` 的完整响应或其中的 state 对象。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: 导入成功
        "403":
          description: 当前角色无权限
        "422":
          description: 导入数据结构不合法
  /api/products:
    get:
      summary: 查询门店商品
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 商品列表
  /api/products/{productId}/price:
    post:
      summary: 商品调价
      description: 需要 x-operator 为 admin 或 manager。
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [price]
              properties:
                price:
                  type: number
                  minimum: 0
                reason:
                  type: string
      responses:
        "200":
          description: 调价成功
        "404":
          description: 商品不存在
        "422":
          description: 价格参数错误
        "403":
          description: 当前角色无权限
  /api/products/{productId}/stock-adjust:
    post:
      summary: 商品库存调整
      description: 需要 x-operator 为 admin 或 manager。
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [delta]
              properties:
                delta:
                  type: number
                reason:
                  type: string
      responses:
        "200":
          description: 库存调整成功
        "404":
          description: 商品不存在
        "409":
          description: 库存不能为负
        "422":
          description: 库存调整参数错误
        "403":
          description: 当前角色无权限
  /api/inventory-movements:
    get:
      summary: 查询库存流水
      description: 返回最近库存变化流水，覆盖手工调库、销售扣减、整单退款回滚和部分退款回滚。
      parameters:
        - name: productId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 库存流水列表和低库存摘要
  /api/inventory-counts:
    get:
      summary: 查询库存盘点单
      responses:
        "200":
          description: 最近盘点单
    post:
      summary: 提交库存盘点单
      description: 需要 admin/manager。按 countedStock 调整账面库存，并生成库存流水、同步任务和审计日志。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                reason:
                  type: string
                items:
                  type: array
                  items:
                    type: object
                    required: [productId, countedStock]
                    properties:
                      productId:
                        type: string
                      countedStock:
                        type: number
                        minimum: 0
      responses:
        "201":
          description: 盘点已入账
        "403":
          description: 无权限
        "422":
          description: 参数错误
  /api/loss-reports:
    get:
      summary: 查询报损单
      responses:
        "200":
          description: 最近报损单
    post:
      summary: 提交报损单
      description: 需要 admin/manager。扣减损耗数量，并生成库存流水、loss/inventory 同步任务和审计日志。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                reason:
                  type: string
                items:
                  type: array
                  items:
                    type: object
                    required: [productId, qty]
                    properties:
                      productId:
                        type: string
                      qty:
                        type: number
                        minimum: 0
      responses:
        "201":
          description: 报损已入账
        "403":
          description: 无权限
        "409":
          description: 报损数量超过库存
        "422":
          description: 参数错误
  /api/members:
    get:
      summary: 查询会员
      parameters:
        - name: phone
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 会员列表
  /api/members/{memberId}/recharge:
    post:
      summary: 会员储值充值
      parameters:
        - name: memberId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                planId:
                  type: string
                amount:
                  type: number
                  minimum: 0.01
                bonus:
                  type: number
                  minimum: 0
                operator:
                  type: string
      responses:
        "201":
          description: 充值成功
        "404":
          description: 会员不存在
        "422":
          description: 充值金额错误
  /api/recharge-plans:
    get:
      summary: 查询可用充值方案
      parameters:
        - name: includeDisabled
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 充值方案列表
    post:
      summary: 新增充值方案
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, amount]
              properties:
                name:
                  type: string
                amount:
                  type: number
                  minimum: 0.01
                bonus:
                  type: number
                  minimum: 0
      responses:
        "201":
          description: 方案已创建
        "409":
          description: 方案编码已存在
        "422":
          description: 参数错误
  /api/recharge-plans/{planId}/disable:
    post:
      summary: 停用充值方案
      parameters:
        - name: planId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 方案已停用
        "404":
          description: 方案不存在
  /api/recharge-plans/{planId}/enable:
    post:
      summary: 启用充值方案
      parameters:
        - name: planId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 方案已启用
        "404":
          description: 方案不存在
  /api/recharge-plans/{planId}/update:
    post:
      summary: 编辑充值方案
      parameters:
        - name: planId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                amount:
                  type: number
                  minimum: 0.01
                bonus:
                  type: number
                  minimum: 0
      responses:
        "200":
          description: 方案已更新
        "404":
          description: 方案不存在
        "422":
          description: 参数错误
  /api/member-transactions:
    get:
      summary: 查询会员储值流水
      description: 返回储值消费、充值、退款流水，可按 memberId 筛选。
      parameters:
        - name: memberId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 储值流水列表
  /api/orders:
    get:
      summary: 查询最近订单
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum: [paid, partially_refunded, refunded]
        - name: memberId
          in: query
          required: false
          schema:
            type: string
        - name: paymentMethod
          in: query
          required: false
          schema:
            type: string
            enum: [wechat, alipay, cash, stored_value, aggregate]
        - name: q
          in: query
          required: false
          description: 支持按订单号、会员、支付/状态、商品编码或商品名搜索。
          schema:
            type: string
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: 最近订单列表、当前筛选结果摘要和分页信息
    post:
      summary: 创建 POS 订单
      description: 创建订单时会按整单折扣分摊每行实付金额，供后续部分退款使用。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [storeId, items, total, paid, paymentMethod]
              properties:
                storeId:
                  type: string
                memberId:
                  type: string
                  nullable: true
                items:
                  type: array
                  items:
                    type: object
                total:
                  type: number
                discount:
                  type: number
                paid:
                  type: number
                paymentMethod:
                  type: string
                  enum: [wechat, alipay, cash, stored_value, aggregate]
      responses:
        "201":
          description: 订单已创建
        "409":
          description: 库存不足或会员余额不足
        "422":
          description: 订单参数错误
  /api/orders/{orderId}:
    get:
      summary: 查询订单详情
      description: 返回订单、会员、会员流水、库存流水、同步任务和审计日志，供后台核账使用。
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 订单详情
        "404":
          description: 订单不存在
  /api/orders/{orderId}/cancel:
    post:
      summary: 取消订单并执行库存回滚
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
      responses:
        "200":
          description: 订单已取消，库存已回滚
        "404":
          description: 订单不存在
        "409":
          description: 当前订单状态不可取消
  /api/orders/{orderId}/partial-refund:
    post:
      summary: 部分退款/部分退货
      description: 按商品数量退款，退款金额基于订单行折扣后实付金额计算，避免折扣订单退多。
      parameters:
        - name: orderId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [items]
              properties:
                reason:
                  type: string
                  maxLength: 120
                  description: 退款原因，未传时使用默认原因。
                items:
                  type: array
                  items:
                    type: object
                    required: [productId, qty]
                    properties:
                      productId:
                        type: string
                      qty:
                        type: number
                        minimum: 0
      responses:
        "200":
          description: 部分退款成功
        "404":
          description: 订单或订单商品不存在
        "409":
          description: 订单状态不可退或退款数量超限
        "422":
          description: 参数错误
  /api/sync-logs:
    get:
      summary: 查询同步队列
      description: 返回同步任务列表，并附带当前筛选结果的 total/open/status/entity 摘要。
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
        - name: entity
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 同步任务列表
  /api/sync-logs/{syncLogId}/retry:
    post:
      summary: 重试同步任务
      parameters:
        - name: syncLogId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 同步任务已重新进入 pending
        "409":
          description: 已达到最大重试次数
        "404":
          description: 同步任务不存在
  /api/sync-logs/{syncLogId}/mark-failed:
    post:
      summary: 标记同步任务失败
      parameters:
        - name: syncLogId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                errorMessage:
                  type: string
                operator:
                  type: string
      responses:
        "200":
          description: 同步任务已标记失败
        "404":
          description: 同步任务不存在
  /api/audit-logs:
    get:
      summary: 查询操作日志
      responses:
        "200":
          description: 最近操作日志
  /api/sync/run:
    post:
      summary: 模拟执行待同步任务
      responses:
        "200":
          description: 返回本次成功同步的任务 ID
  /api/sync/clear-success:
    post:
      summary: 清理成功同步任务
      description: 需要 x-operator 为 admin 或 manager。
      responses:
        "200":
          description: 返回清理数量
        "403":
          description: 当前角色无权限
  /api/sync/auto-retry:
    post:
      summary: 自动重试失败同步任务
      description: 需要 x-operator 为 admin 或 manager。
      responses:
        "200":
          description: 返回已重试和跳过的任务 ID
        "403":
          description: 当前角色无权限
  /api/supply/products/sync:
    get:
      summary: 鲜到发车到门店的商品增量同步
      parameters:
        - name: cursor
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: 商品增量数据
  /api/supply/delivery/notify:
    post:
      summary: 配送到达通知
      responses:
        "202":
          description: 已接收
  /api/supply/price/change:
    post:
      summary: 调价通知
      responses:
        "202":
          description: 已接收
  /api/supply/sales/report:
    post:
      summary: 门店销售数据上报
      responses:
        "202":
          description: 已接收
  /api/supply/inventory/report:
    post:
      summary: 门店库存数据上报
      responses:
        "202":
          description: 已接收
  /api/supply/requisition:
    post:
      summary: 门店要货单提交
      responses:
        "202":
          description: 已接收
  /api/supply/loss/report:
    post:
      summary: 门店损耗数据上报
      responses:
        "202":
          description: 已接收
