========================================================================
屋里咖啡咖啡机对接一期 - 字典/菜单/权限手工添加清单 (2026-09-18)
========================================================================

说明：以下内容需要管理员在对应环境手工添加（UPMS 管理端或直接执行 SQL）。
SQL 按租户幂等，风格与 db/20260904_add_er_yin_printer.sql 一致，可直接参考执行；
菜单 parent_id 依赖各环境实际菜单主键，请先确认父菜单再执行。

------------------------------------------------------------------------
一、字典项
------------------------------------------------------------------------

1. 设备类型字典 hard_type 增加咖啡机（所有已配置 hard_type 的租户）：

INSERT INTO `pjcloudx`.`sys_dict_item`
(`id`, `dict_id`, `item_value`, `label`, `dict_type`, `description`, `sort_order`, `create_by`, `create_time`, `remarks`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict_item`) + ROW_NUMBER() OVER (ORDER BY d.`tenant_id`, d.`id`),
       d.`id`, '13', '咖啡机', 'hard_type', '屋里咖啡扫码支付咖啡机', 13,
       'system', NOW(), '屋里咖啡咖啡机', '0', d.`tenant_id`
FROM `pjcloudx`.`sys_dict` d
WHERE d.`dict_type` = 'hard_type'
  AND d.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_dict_item` i
      WHERE i.`tenant_id` = d.`tenant_id` AND i.`dict_type` = 'hard_type'
        AND i.`item_value` = '13' AND i.`del_flag` = '0'
  );

2. 新字典 hard_coffee_stage（咖啡机生命周期阶段）：

-- 先建字典类型（按租户）
INSERT INTO `pjcloudx`.`sys_dict`
(`id`, `dict_type`, `description`, `create_by`, `create_time`, `remarks`, `system_flag`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict`) + ROW_NUMBER() OVER (ORDER BY tenant.`tenant_id`),
       'hard_coffee_stage', '咖啡机生命周期阶段', 'system', NOW(), '咖啡机生命周期阶段', '1', '0', tenant.`tenant_id`
FROM (SELECT DISTINCT `tenant_id` FROM `pjcloudx`.`sys_dict` WHERE `del_flag` = '0') tenant
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx`.`sys_dict` d
    WHERE d.`tenant_id` = tenant.`tenant_id` AND d.`dict_type` = 'hard_coffee_stage' AND d.`del_flag` = '0'
);

-- 再建字典项
INSERT INTO `pjcloudx`.`sys_dict_item`
(`id`, `dict_id`, `item_value`, `label`, `dict_type`, `description`, `sort_order`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict_item`) + ROW_NUMBER() OVER (ORDER BY d.`tenant_id`, item.`sort_order`),
       d.`id`, item.`item_value`, item.`label`, d.`dict_type`, item.`label`, item.`sort_order`,
       'system', NOW(), '0', d.`tenant_id`
FROM `pjcloudx`.`sys_dict` d
JOIN (
    SELECT 'GET_ORDER'    AS `item_value`, '扫码拉取厂商订单' AS `label`, 1 AS `sort_order`
    UNION ALL SELECT 'CREATE_ORDER', '创建本地订单', 2
    UNION ALL SELECT 'WALLET_PAY',   '饭卡扣款', 3
    UNION ALL SELECT 'NOTIFY_PAY',   '通知厂商支付结果', 4
    UNION ALL SELECT 'QUERY',        '厂商查单', 5
    UNION ALL SELECT 'REFUND_NOTIFY','通知厂商退款', 6
) item ON item.`item_value` IS NOT NULL
WHERE d.`dict_type` = 'hard_coffee_stage'
  AND d.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_dict_item` i
      WHERE i.`tenant_id` = d.`tenant_id` AND i.`dict_type` = 'hard_coffee_stage'
        AND i.`item_value` = item.`item_value` AND i.`del_flag` = '0'
  );

3. 新字典 hard_coffee_direction（交互方向）：

INSERT INTO `pjcloudx`.`sys_dict`
(`id`, `dict_type`, `description`, `create_by`, `create_time`, `remarks`, `system_flag`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict`) + ROW_NUMBER() OVER (ORDER BY tenant.`tenant_id`),
       'hard_coffee_direction', '咖啡机日志交互方向', 'system', NOW(), '咖啡机日志交互方向', '1', '0', tenant.`tenant_id`
FROM (SELECT DISTINCT `tenant_id` FROM `pjcloudx`.`sys_dict` WHERE `del_flag` = '0') tenant
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx`.`sys_dict` d
    WHERE d.`tenant_id` = tenant.`tenant_id` AND d.`dict_type` = 'hard_coffee_direction' AND d.`del_flag` = '0'
);

INSERT INTO `pjcloudx`.`sys_dict_item`
(`id`, `dict_id`, `item_value`, `label`, `dict_type`, `description`, `sort_order`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict_item`) + ROW_NUMBER() OVER (ORDER BY d.`tenant_id`, item.`sort_order`),
       d.`id`, item.`item_value`, item.`label`, d.`dict_type`, item.`label`, item.`sort_order`,
       'system', NOW(), '0', d.`tenant_id`
FROM `pjcloudx`.`sys_dict` d
JOIN (
    SELECT '1' AS `item_value`, '我方调厂商' AS `label`, 1 AS `sort_order`
    UNION ALL SELECT '2', '厂商调我方', 2
) item ON item.`item_value` IS NOT NULL
WHERE d.`dict_type` = 'hard_coffee_direction'
  AND d.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_dict_item` i
      WHERE i.`tenant_id` = d.`tenant_id` AND i.`dict_type` = 'hard_coffee_direction'
        AND i.`item_value` = item.`item_value` AND i.`del_flag` = '0'
  );

4. 新字典 hard_coffee_notify_status（通知厂商状态，用于订单列表筛选/展示）：

INSERT INTO `pjcloudx`.`sys_dict`
(`id`, `dict_type`, `description`, `create_by`, `create_time`, `remarks`, `system_flag`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict`) + ROW_NUMBER() OVER (ORDER BY tenant.`tenant_id`),
       'hard_coffee_notify_status', '咖啡机通知厂商状态', 'system', NOW(), '咖啡机通知厂商状态', '1', '0', tenant.`tenant_id`
FROM (SELECT DISTINCT `tenant_id` FROM `pjcloudx`.`sys_dict` WHERE `del_flag` = '0') tenant
WHERE NOT EXISTS (
    SELECT 1 FROM `pjcloudx`.`sys_dict` d
    WHERE d.`tenant_id` = tenant.`tenant_id` AND d.`dict_type` = 'hard_coffee_notify_status' AND d.`del_flag` = '0'
);

INSERT INTO `pjcloudx`.`sys_dict_item`
(`id`, `dict_id`, `item_value`, `label`, `dict_type`, `description`, `sort_order`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict_item`) + ROW_NUMBER() OVER (ORDER BY d.`tenant_id`, item.`sort_order`),
       d.`id`, item.`item_value`, item.`label`, d.`dict_type`, item.`label`, item.`sort_order`,
       'system', NOW(), '0', d.`tenant_id`
FROM `pjcloudx`.`sys_dict` d
JOIN (
    SELECT '0' AS `item_value`, '未通知' AS `label`, 1 AS `sort_order`
    UNION ALL SELECT '1', '通知成功', 2
    UNION ALL SELECT '2', '通知失败', 3
) item ON item.`item_value` IS NOT NULL
WHERE d.`dict_type` = 'hard_coffee_notify_status'
  AND d.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_dict_item` i
      WHERE i.`tenant_id` = d.`tenant_id` AND i.`dict_type` = 'hard_coffee_notify_status'
        AND i.`item_value` = item.`item_value` AND i.`del_flag` = '0'
  );

5. 第三方密钥类型字典 third_key_type 增加屋里咖啡两项（所有已配置该字典的租户）。
   third_key_manage 表单的「类型」下拉即读此字典，不加则无法在管理端选择咖啡机类型录入密钥。
   item_value 必须与后端 ThirdKeyTypeEnum 一致（11 调用对、12 查单对）：

INSERT INTO `pjcloudx`.`sys_dict_item`
(`id`, `dict_id`, `item_value`, `label`, `dict_type`, `description`, `sort_order`, `create_by`, `create_time`, `remarks`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_dict_item`) + ROW_NUMBER() OVER (ORDER BY d.`tenant_id`, item.`sort_order`),
       d.`id`, item.`item_value`, item.`label`, d.`dict_type`, item.`label`, item.`sort_order`,
       'system', NOW(), item.`label`, '0', d.`tenant_id`
FROM `pjcloudx`.`sys_dict` d
JOIN (
    SELECT '11' AS `item_value`, '屋里咖啡' AS `label`, 11 AS `sort_order`
    UNION ALL SELECT '12', '屋里咖啡查单', 12
) item ON item.`item_value` IS NOT NULL
WHERE d.`dict_type` = 'third_key_type'
  AND d.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_dict_item` i
      WHERE i.`tenant_id` = d.`tenant_id` AND i.`dict_type` = 'third_key_type'
        AND i.`item_value` = item.`item_value` AND i.`del_flag` = '0'
  );

------------------------------------------------------------------------
二、菜单与页面路由
------------------------------------------------------------------------

说明（重要）：
- 前端页面已实现：咖啡机订单页 views/hard/hard-coffee-order/index.vue、
  生命周期日志页 views/hard/hard-coffee-log/index.vue；
- 「咖啡机订单」与现有订单管理菜单同级（参照 permission=hard_hardOrder_view 的菜单取父级）；
- 「咖啡机日志」与设备管理菜单同级（参照 permission=hard_hardManager_view 的菜单取父级）；
- menu_type='0' 为页面菜单，path/component 与前端路由约定一致；
- 页面菜单自身的 permission 即查看权限，无需再单独加按钮。

1. 咖啡机订单页面菜单：

INSERT INTO `pjcloudx`.`sys_menu`
(`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`menu_id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_menu`) + ROW_NUMBER() OVER (ORDER BY sibling.`tenant_id`, sibling.`menu_id`),
       '咖啡机订单', 'hard_hardCoffeeOrder_view', '/hard/coffee-order', 'hard/hard-coffee-order/index',
       sibling.`parent_id`, NULL, '1', 40, '0', '0', '0',
       'system', NOW(), 'system', NOW(), '0', sibling.`tenant_id`
FROM `pjcloudx`.`sys_menu` sibling
WHERE sibling.`permission` = 'hard_hardOrder_view' AND sibling.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_menu` existing
      WHERE existing.`tenant_id` = sibling.`tenant_id`
        AND existing.`permission` = 'hard_hardCoffeeOrder_view' AND existing.`del_flag` = '0'
  );

2. 咖啡机日志页面菜单：

INSERT INTO `pjcloudx`.`sys_menu`
(`menu_id`, `name`, `permission`, `path`, `component`, `parent_id`, `icon`, `visible`, `sort_order`, `keep_alive`, `embedded`, `menu_type`, `create_by`, `create_time`, `update_by`, `update_time`, `del_flag`, `tenant_id`)
SELECT (SELECT GREATEST(COALESCE(MAX(`menu_id`), 0), 2026091800000000000) FROM `pjcloudx`.`sys_menu`) + ROW_NUMBER() OVER (ORDER BY sibling.`tenant_id`, sibling.`menu_id`),
       '咖啡机日志', 'hard_hardCoffeeLog_view', '/hard/coffee-log', 'hard/hard-coffee-log/index',
       sibling.`parent_id`, NULL, '1', 41, '0', '0', '0',
       'system', NOW(), 'system', NOW(), '0', sibling.`tenant_id`
FROM `pjcloudx`.`sys_menu` sibling
WHERE sibling.`permission` = 'hard_hardManager_view' AND sibling.`del_flag` = '0'
  AND NOT EXISTS (
      SELECT 1 FROM `pjcloudx`.`sys_menu` existing
      WHERE existing.`tenant_id` = sibling.`tenant_id`
        AND existing.`permission` = 'hard_hardCoffeeLog_view' AND existing.`del_flag` = '0'
  );

注意：菜单配置更推荐在管理端「菜单管理」页面按上述参数手工添加（组件路径
hard/hard-coffee-order/index 与 hard/hard-coffee-log/index），SQL 仅为模板参考。

------------------------------------------------------------------------
三、第三方密钥 third_key_manage（第三方库 pjcloudx_thirdparty）
------------------------------------------------------------------------

说明：屋里咖啡两对密钥通过 thirdparty 管理端「第三方密钥管理」录入，或按下面模板执行 SQL。
**通过表单录入前必须先完成上面第 5 项 third_key_type 字典项的添加，否则「类型」下拉选不到屋里咖啡。**
字段映射：app_key=屋里概念里的 appid，app_secret=屋里概念里的 appkey（SHA1签名密钥）。
密钥为平台级全局唯一（跨租户读取），建议维护在默认租户(tenant_id=1)下。
修改密钥后 hardware 侧最多延迟 5 分钟生效（本地缓存）。

1. 我方调厂商 getOrder/callback/refund 的鉴权对（bizType=11）：

INSERT INTO `pjcloudx_thirdparty`.`third_key_manage`
(`id`, `name`, `biz_type`, `app_key`, `app_secret`, `status`, `remark`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
VALUES
(2026091800000000001, '屋里咖啡', '11', '<向屋里申请的16位appid>', '<向屋里申请的16位appkey>', '1',
 '我方作为第三方支付服务商调厂商接口', 'system', NOW(), '0', 1);

2. 厂商调我方 queryOrder 查单的验签对（bizType=12，我方自行生成后告知屋里）：

INSERT INTO `pjcloudx_thirdparty`.`third_key_manage`
(`id`, `name`, `biz_type`, `app_key`, `app_secret`, `status`, `remark`, `create_by`, `create_time`, `del_flag`, `tenant_id`)
VALUES
(2026091800000000002, '屋里咖啡查单', '12', '<我方生成并告知屋里的16位appid>', '<我方生成的16位appkey>', '1',
 '厂商调我方queryOrder验签', 'system', NOW(), '0', 1);

------------------------------------------------------------------------
四、后端权限字符串与接口对应关系（前端页面取用）
------------------------------------------------------------------------

hard_hardCoffeeOrder_view:
  GET  /hard-coffee-manager/order/page    咖啡机订单分页
  GET  /hard-coffee-manager/order/{id}    咖啡机订单详情(含生命周期时间线)

hard_hardCoffeeLog_view:
  GET  /hard-coffee-manager/log/page      生命周期日志分页
