ソースを参照

add delivery rider management module

admin 1 ヶ月 前
コミット
2c21acb31a

+ 245 - 0
client-app/src/main/java/com/sapit/client/app/manage/controller/rider/DeliveryRiderManage1Controller.java

@@ -0,0 +1,245 @@
+package com.sapit.client.app.manage.controller.rider;
+
+import com.sapit.client.app.manage.service.rider.DeliveryRiderManageService;
+import com.sapit.common.constant.ReturnCode;
+import com.sapit.common.entity.rider.DeliveryRider;
+import com.sapit.common.framwork.AuthToken;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.framwork.ResultBean;
+import com.sapit.common.search.rider.DeliveryRiderSearchBean;
+import com.sapit.common.util.DateUtil;
+import com.sapit.common.util.MD5;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeParseException;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@RestController
+@RequestMapping(value = "deliveryRiderManage1", produces = "application/json")
+public class DeliveryRiderManage1Controller {
+
+    @Autowired
+    private DeliveryRiderManageService deliveryRiderManageService;
+
+    @PostMapping(value = "/list")
+    @AuthToken(type = "admin")
+    public ResultBean list(DeliveryRiderSearchBean searchBean, PageControlData page) {
+        ResultBean bean = new ResultBean();
+        List<DeliveryRider> list = deliveryRiderManageService.getList(page, searchBean);
+        for (DeliveryRider rider : list) {
+            rider.setPassword(null);
+        }
+        bean.setList(list);
+        bean.setPage(page);
+        return bean;
+    }
+
+    @PostMapping(value = "/save")
+    @AuthToken(type = "admin")
+    public ResultBean save(DeliveryRider rider) {
+        ResultBean bean = new ResultBean();
+        String msg = validate(rider);
+        if (msg != null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg(msg);
+            return bean;
+        }
+        if (!deliveryRiderManageService.checkPhone(rider.getId(), rider.getPhone())) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("手机号已被使用,请更换手机号");
+            return bean;
+        }
+
+        try {
+            normalize(rider);
+        } catch (DateTimeParseException e) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("不能接单时间格式错误");
+            return bean;
+        }
+        if (rider.getId() == null) {
+            rider.setPassword(MD5.encrypt(rider.getPassword()));
+            rider.setCreateTime(DateUtil.getSqlToday());
+            rider.setUpdateTime(DateUtil.getSqlToday());
+            deliveryRiderManageService.save(rider);
+        } else {
+            DeliveryRider old = deliveryRiderManageService.getById(rider.getId());
+            if (old == null) {
+                bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+                bean.getRtnBean().setRtnMsg("配送员不存在");
+                return bean;
+            }
+            old.setPhone(rider.getPhone());
+            old.setName(rider.getName());
+            old.setType(rider.getType());
+            old.setMerchantId(rider.getMerchantId());
+            old.setWorkStatus(rider.getWorkStatus());
+            old.setAccountStatus(rider.getAccountStatus());
+            old.setPickup(rider.getPickup());
+            old.setDelivery(rider.getDelivery());
+            old.setClimbStairs(rider.getClimbStairs());
+            old.setNoOrderStartTime(rider.getNoOrderStartTime());
+            old.setNoOrderEndTime(rider.getNoOrderEndTime());
+            if (StringUtils.isNotBlank(rider.getPassword())) {
+                old.setPassword(MD5.encrypt(rider.getPassword()));
+            }
+            old.setUpdateTime(DateUtil.getSqlToday());
+            deliveryRiderManageService.update(old);
+        }
+        return bean;
+    }
+
+    @PostMapping(value = "/updateWorkStatus")
+    @AuthToken(type = "admin")
+    public ResultBean updateWorkStatus(Long id, Long workStatus) {
+        ResultBean bean = new ResultBean();
+        if (id == null || !isZeroOrOne(workStatus)) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        deliveryRiderManageService.updateWorkStatus(id, workStatus);
+        return bean;
+    }
+
+    @PostMapping(value = "/updateAccountStatus")
+    @AuthToken(type = "admin")
+    public ResultBean updateAccountStatus(Long id, Long accountStatus) {
+        ResultBean bean = new ResultBean();
+        if (id == null || !isZeroOrOne(accountStatus)) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        deliveryRiderManageService.updateAccountStatus(id, accountStatus);
+        return bean;
+    }
+
+    @PostMapping(value = "/updatePermission")
+    @AuthToken(type = "admin")
+    public ResultBean updatePermission(Long id, String field, Long value) {
+        ResultBean bean = new ResultBean();
+        if (id == null || !isZeroOrOne(value)) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        String column = getPermissionColumn(field);
+        if (column == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("权限字段错误");
+            return bean;
+        }
+        DeliveryRider rider = deliveryRiderManageService.getById(id);
+        if (rider == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("配送员不存在");
+            return bean;
+        }
+        if (("pickup".equals(field) && value == 1 && rider.getClimbStairs() == 1)
+                || ("climbStairs".equals(field) && value == 1 && rider.getPickup() == 1)) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("取餐权限和爬楼权限不能同时存在");
+            return bean;
+        }
+        deliveryRiderManageService.updatePermission(id, column, value);
+        return bean;
+    }
+
+    @PostMapping(value = "/delete")
+    @AuthToken(type = "admin")
+    public ResultBean delete(Long id) {
+        ResultBean bean = new ResultBean();
+        if (id == null) {
+            bean.getRtnBean().setRtnCode(ReturnCode.PARAM_WRONG);
+            bean.getRtnBean().setRtnMsg("参数错误");
+            return bean;
+        }
+        int rows = deliveryRiderManageService.deleteById(id);
+        if (rows < 1) {
+            bean.getRtnBean().setRtnCode(ReturnCode.FAILED);
+            bean.getRtnBean().setRtnMsg("配送员不存在");
+        }
+        return bean;
+    }
+
+    private String validate(DeliveryRider rider) {
+        if (rider == null || StringUtils.isBlank(rider.getName()) || StringUtils.isBlank(rider.getPhone())) {
+            return "请填写姓名和手机号";
+        }
+        if (!rider.getPhone().matches("^1\\d{10}$")) {
+            return "请输入正确的手机号";
+        }
+        if (rider.getId() == null && StringUtils.isBlank(rider.getPassword())) {
+            return "新增配送员必须填写密码";
+        }
+        if (rider.getType() == null || (rider.getType() != 1L && rider.getType() != 2L)) {
+            return "请选择配送员类型";
+        }
+        if (rider.getType() == 2L && rider.getMerchantId() == null) {
+            return "商家配送员必须填写所属商家";
+        }
+        if (rider.getType() == 1L && Long.valueOf(1L).equals(rider.getPickup()) && Long.valueOf(1L).equals(rider.getClimbStairs())) {
+            return "取餐权限和爬楼权限不能同时存在";
+        }
+        return null;
+    }
+
+    private void normalize(DeliveryRider rider) {
+        rider.setWorkStatus(defaultZeroOne(rider.getWorkStatus(), 1L));
+        rider.setAccountStatus(defaultZeroOne(rider.getAccountStatus(), 1L));
+        rider.setPickup(defaultZeroOne(rider.getPickup(), 0L));
+        rider.setDelivery(defaultZeroOne(rider.getDelivery(), 0L));
+        rider.setClimbStairs(defaultZeroOne(rider.getClimbStairs(), 0L));
+        if (rider.getType() == 1L) {
+            rider.setMerchantId(null);
+            rider.setNoOrderStartTime(null);
+            rider.setNoOrderEndTime(null);
+        } else {
+            rider.setPickup(0L);
+            rider.setDelivery(0L);
+            rider.setClimbStairs(0L);
+            rider.setNoOrderStartTime(parseTime(rider.getNoOrderStartTimeStr()));
+            rider.setNoOrderEndTime(parseTime(rider.getNoOrderEndTimeStr()));
+        }
+    }
+
+    private Timestamp parseTime(String time) {
+        if (StringUtils.isBlank(time)) {
+            return null;
+        }
+        LocalTime localTime = LocalTime.parse(time, DateTimeFormatter.ofPattern("HH:mm"));
+        LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(1970, 1, 1), localTime);
+        return Timestamp.valueOf(dateTime);
+    }
+
+    private Long defaultZeroOne(Long value, Long defaultValue) {
+        return isZeroOrOne(value) ? value : defaultValue;
+    }
+
+    private boolean isZeroOrOne(Long value) {
+        return value != null && (value == 0L || value == 1L);
+    }
+
+    private String getPermissionColumn(String field) {
+        if ("pickup".equals(field)) {
+            return "pickup";
+        }
+        if ("delivery".equals(field)) {
+            return "delivery";
+        }
+        if ("climbStairs".equals(field)) {
+            return "climb_stairs";
+        }
+        return null;
+    }
+}

+ 55 - 0
client-app/src/main/java/com/sapit/client/app/manage/service/rider/DeliveryRiderManageService.java

@@ -0,0 +1,55 @@
+package com.sapit.client.app.manage.service.rider;
+
+import com.sapit.client.app.persistence.rider.DeliveryRiderDao;
+import com.sapit.common.entity.rider.DeliveryRider;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.rider.DeliveryRiderSearchBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+public class DeliveryRiderManageService {
+
+    @Autowired
+    private DeliveryRiderDao deliveryRiderDao;
+
+    public List<DeliveryRider> getList(PageControlData page, DeliveryRiderSearchBean searchBean) {
+        return deliveryRiderDao.getList(page, searchBean);
+    }
+
+    public DeliveryRider getById(Long id) {
+        return deliveryRiderDao.getById(id);
+    }
+
+    public boolean checkPhone(Long id, String phone) {
+        return deliveryRiderDao.checkPhone(id, phone);
+    }
+
+    public boolean save(DeliveryRider rider) {
+        return deliveryRiderDao.save(rider);
+    }
+
+    public boolean update(DeliveryRider rider) {
+        return deliveryRiderDao.update(rider);
+    }
+
+    public void updateWorkStatus(Long id, Long workStatus) {
+        deliveryRiderDao.updateWorkStatus(id, workStatus);
+    }
+
+    public void updateAccountStatus(Long id, Long accountStatus) {
+        deliveryRiderDao.updateAccountStatus(id, accountStatus);
+    }
+
+    public void updatePermission(Long id, String column, Long value) {
+        deliveryRiderDao.updatePermission(id, column, value);
+    }
+
+    public int deleteById(Long id) {
+        return deliveryRiderDao.deleteById(id);
+    }
+}

+ 73 - 0
client-app/src/main/java/com/sapit/client/app/persistence/rider/DeliveryRiderDao.java

@@ -0,0 +1,73 @@
+package com.sapit.client.app.persistence.rider;
+
+import com.sapit.common.entity.rider.DeliveryRider;
+import com.sapit.common.framwork.BaseDao;
+import com.sapit.common.framwork.PageControlData;
+import com.sapit.common.search.rider.DeliveryRiderSearchBean;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Repository
+@SuppressWarnings("unchecked")
+public class DeliveryRiderDao extends BaseDao {
+
+    public DeliveryRiderDao() {
+        super.setEntity("DeliveryRider");
+    }
+
+    public List<DeliveryRider> getList(PageControlData page, DeliveryRiderSearchBean searchBean) {
+        String hql = "from DeliveryRider where 1=1" + searchBean.getSerchCondition() + " order by id desc";
+        return this.searchByHql(page, hql);
+    }
+
+    public DeliveryRider getById(Long id) {
+        return (DeliveryRider) this.findById(DeliveryRider.class, id);
+    }
+
+    public boolean checkPhone(Long id, String phone) {
+        String sql = "select count(*) from delivery_rider where phone = :phone";
+        Map<String, Object> params = new HashMap<>();
+        params.put("phone", phone);
+        if (id != null) {
+            sql += " and id <> :id";
+            params.put("id", id);
+        }
+        List<?> list = this.searchBySql(null, sql, params);
+        return list != null && !list.isEmpty() && Long.valueOf(list.get(0).toString()) < 1;
+    }
+
+    @Transactional
+    public void updateWorkStatus(Long id, Long workStatus) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("id", id);
+        params.put("workStatus", workStatus);
+        this.executeSql("update delivery_rider set work_status = :workStatus where id = :id", params);
+    }
+
+    @Transactional
+    public void updateAccountStatus(Long id, Long accountStatus) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("id", id);
+        params.put("accountStatus", accountStatus);
+        this.executeSql("update delivery_rider set account_status = :accountStatus where id = :id", params);
+    }
+
+    @Transactional
+    public void updatePermission(Long id, String column, Long value) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("id", id);
+        params.put("value", value);
+        this.executeSql("update delivery_rider set " + column + " = :value where id = :id", params);
+    }
+
+    @Transactional
+    public int deleteById(Long id) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("id", id);
+        return this.executeSql("delete from delivery_rider where id = :id", params);
+    }
+}

+ 199 - 0
common/src/main/java/com/sapit/common/entity/rider/DeliveryRider.java

@@ -0,0 +1,199 @@
+package com.sapit.common.entity.rider;
+
+import com.sapit.common.util.DateUtil;
+import org.apache.commons.lang.StringUtils;
+
+import javax.persistence.*;
+import java.io.Serializable;
+import java.sql.Timestamp;
+
+@Entity
+@Table(name = "delivery_rider")
+public class DeliveryRider implements Serializable {
+    private static final long serialVersionUID = 5491441283899935661L;
+
+    private Long id;
+    private String phone;
+    private String password;
+    private String name;
+    private Long type;
+    private Long merchantId;
+    private Long workStatus;
+    private Long accountStatus;
+    private Long pickup;
+    private Long delivery;
+    private Long climbStairs;
+    private Timestamp noOrderStartTime;
+    private Timestamp noOrderEndTime;
+    private Timestamp createTime;
+    private Timestamp updateTime;
+
+    private String noOrderStartTimeStr;
+    private String noOrderEndTimeStr;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    @Column(name = "id")
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    @Column(name = "phone")
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    @Column(name = "password")
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    @Column(name = "name")
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Column(name = "type")
+    public Long getType() {
+        return type;
+    }
+
+    public void setType(Long type) {
+        this.type = type;
+    }
+
+    @Column(name = "merchant_id")
+    public Long getMerchantId() {
+        return merchantId;
+    }
+
+    public void setMerchantId(Long merchantId) {
+        this.merchantId = merchantId;
+    }
+
+    @Column(name = "work_status")
+    public Long getWorkStatus() {
+        return workStatus;
+    }
+
+    public void setWorkStatus(Long workStatus) {
+        this.workStatus = workStatus;
+    }
+
+    @Column(name = "account_status")
+    public Long getAccountStatus() {
+        return accountStatus;
+    }
+
+    public void setAccountStatus(Long accountStatus) {
+        this.accountStatus = accountStatus;
+    }
+
+    @Column(name = "pickup")
+    public Long getPickup() {
+        return pickup;
+    }
+
+    public void setPickup(Long pickup) {
+        this.pickup = pickup;
+    }
+
+    @Column(name = "delivery")
+    public Long getDelivery() {
+        return delivery;
+    }
+
+    public void setDelivery(Long delivery) {
+        this.delivery = delivery;
+    }
+
+    @Column(name = "climb_stairs")
+    public Long getClimbStairs() {
+        return climbStairs;
+    }
+
+    public void setClimbStairs(Long climbStairs) {
+        this.climbStairs = climbStairs;
+    }
+
+    @Column(name = "no_order_start_time")
+    public Timestamp getNoOrderStartTime() {
+        return noOrderStartTime;
+    }
+
+    public void setNoOrderStartTime(Timestamp noOrderStartTime) {
+        this.noOrderStartTime = noOrderStartTime;
+    }
+
+    @Column(name = "no_order_end_time")
+    public Timestamp getNoOrderEndTime() {
+        return noOrderEndTime;
+    }
+
+    public void setNoOrderEndTime(Timestamp noOrderEndTime) {
+        this.noOrderEndTime = noOrderEndTime;
+    }
+
+    @Column(name = "create_time")
+    public Timestamp getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Timestamp createTime) {
+        this.createTime = createTime;
+    }
+
+    @Column(name = "update_time")
+    public Timestamp getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Timestamp updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    @Transient
+    public String getNoOrderStartTimeStr() {
+        if (noOrderStartTime != null) {
+            return DateUtil.getDateStr(noOrderStartTime, "HH:mm");
+        }
+        return noOrderStartTimeStr;
+    }
+
+    public void setNoOrderStartTimeStr(String noOrderStartTimeStr) {
+        this.noOrderStartTimeStr = noOrderStartTimeStr;
+    }
+
+    @Transient
+    public String getNoOrderEndTimeStr() {
+        if (noOrderEndTime != null) {
+            return DateUtil.getDateStr(noOrderEndTime, "HH:mm");
+        }
+        return noOrderEndTimeStr;
+    }
+
+    public void setNoOrderEndTimeStr(String noOrderEndTimeStr) {
+        this.noOrderEndTimeStr = noOrderEndTimeStr;
+    }
+
+    @Transient
+    public boolean hasPassword() {
+        return StringUtils.isNotBlank(password);
+    }
+}

+ 51 - 0
common/src/main/java/com/sapit/common/search/rider/DeliveryRiderSearchBean.java

@@ -0,0 +1,51 @@
+package com.sapit.common.search.rider;
+
+import com.sapit.common.framwork.BaseSearchBean;
+import com.sapit.common.framwork.CompositeSearch;
+
+public class DeliveryRiderSearchBean extends BaseSearchBean {
+
+    @CompositeSearch(fieldName = "name", operCode = "LR%")
+    private String name;
+
+    @CompositeSearch(fieldName = "phone", operCode = "LR%")
+    private String phone;
+
+    @CompositeSearch(fieldName = "type", operCode = "=")
+    private Long type;
+
+    @CompositeSearch(fieldName = "accountStatus", operCode = "=")
+    private Long accountStatus;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getPhone() {
+        return phone;
+    }
+
+    public void setPhone(String phone) {
+        this.phone = phone;
+    }
+
+    public Long getType() {
+        return type;
+    }
+
+    public void setType(Long type) {
+        this.type = type;
+    }
+
+    public Long getAccountStatus() {
+        return accountStatus;
+    }
+
+    public void setAccountStatus(Long accountStatus) {
+        this.accountStatus = accountStatus;
+    }
+}

+ 135 - 0
docs/deliveryerManage1-permission.sql

@@ -0,0 +1,135 @@
+-- Run this script in database `eduepos-plat`.
+-- It adds `/delivery/deliveryerManage1` under the same module as the existing
+-- `/delivery/deliveryerManage` menu, then copies menu/button permissions from
+-- the existing rider management menu.
+
+-- 1. Create menu by copying the existing rider management menu.
+INSERT INTO sys_menu (
+    MODEL_CODE,
+    MENU_NAME,
+    URL,
+    LEVEL,
+    ORDER_KEY,
+    FATHER_ID,
+    STATUS,
+    CREATE_DATE
+)
+SELECT
+    old_menu.MODEL_CODE,
+    '新配送员管理',
+    '/delivery/deliveryerManage1',
+    old_menu.LEVEL,
+    old_menu.ORDER_KEY + 1,
+    old_menu.FATHER_ID,
+    old_menu.STATUS,
+    NOW()
+FROM sys_menu old_menu
+WHERE old_menu.URL = '/delivery/deliveryerManage'
+  AND NOT EXISTS (
+      SELECT 1
+      FROM (
+          SELECT MENU_ID
+          FROM sys_menu
+          WHERE URL = '/delivery/deliveryerManage1'
+      ) exists_menu
+  )
+LIMIT 1;
+
+-- 2. Copy selectable button permissions from the existing rider management menu.
+INSERT INTO sys_menu_button (
+    MENU_ID,
+    BUTTON_CODE
+)
+SELECT
+    new_menu.MENU_ID,
+    old_button.BUTTON_CODE
+FROM sys_menu old_menu
+JOIN sys_menu new_menu ON new_menu.URL = '/delivery/deliveryerManage1'
+JOIN sys_menu_button old_button ON old_button.MENU_ID = old_menu.MENU_ID
+WHERE old_menu.URL = '/delivery/deliveryerManage'
+  AND NOT EXISTS (
+      SELECT 1
+      FROM sys_menu_button exists_button
+      WHERE exists_button.MENU_ID = new_menu.MENU_ID
+        AND exists_button.BUTTON_CODE = old_button.BUTTON_CODE
+  );
+
+-- 3. Fallback: if the existing rider menu has no button rows, create common buttons.
+INSERT INTO sys_menu_button (
+    MENU_ID,
+    BUTTON_CODE
+)
+SELECT
+    new_menu.MENU_ID,
+    buttons.BUTTON_CODE
+FROM sys_menu new_menu
+JOIN (
+    SELECT 'add' AS BUTTON_CODE
+    UNION ALL SELECT 'edit'
+    UNION ALL SELECT 'delete'
+) buttons
+WHERE new_menu.URL = '/delivery/deliveryerManage1'
+  AND NOT EXISTS (
+      SELECT 1
+      FROM sys_menu_button exists_button
+      WHERE exists_button.MENU_ID = new_menu.MENU_ID
+        AND exists_button.BUTTON_CODE = buttons.BUTTON_CODE
+  );
+
+-- 4. Grant the new menu to every role that already has the old rider management menu.
+INSERT INTO sys_role (
+    CHARACTOR_ID,
+    MENU_ID
+)
+SELECT
+    old_role.CHARACTOR_ID,
+    new_menu.MENU_ID
+FROM sys_role old_role
+JOIN sys_menu old_menu ON old_menu.MENU_ID = old_role.MENU_ID
+JOIN sys_menu new_menu ON new_menu.URL = '/delivery/deliveryerManage1'
+WHERE old_menu.URL = '/delivery/deliveryerManage'
+  AND NOT EXISTS (
+      SELECT 1
+      FROM sys_role exists_role
+      WHERE exists_role.CHARACTOR_ID = old_role.CHARACTOR_ID
+        AND exists_role.MENU_ID = new_menu.MENU_ID
+  );
+
+-- 5. Grant matching button permissions to roles copied in step 4.
+INSERT INTO sys_role_menu_button (
+    MENU_ID,
+    ROLE_ID,
+    BUTTON_CODE
+)
+SELECT
+    new_menu.MENU_ID,
+    old_button_role.ROLE_ID,
+    old_button_role.BUTTON_CODE
+FROM sys_role_menu_button old_button_role
+JOIN sys_menu old_menu ON old_menu.MENU_ID = old_button_role.MENU_ID
+JOIN sys_menu new_menu ON new_menu.URL = '/delivery/deliveryerManage1'
+JOIN sys_menu_button new_button ON new_button.MENU_ID = new_menu.MENU_ID
+    AND new_button.BUTTON_CODE = old_button_role.BUTTON_CODE
+WHERE old_menu.URL = '/delivery/deliveryerManage'
+  AND NOT EXISTS (
+      SELECT 1
+      FROM sys_role_menu_button exists_button_role
+      WHERE exists_button_role.MENU_ID = new_menu.MENU_ID
+        AND exists_button_role.ROLE_ID = old_button_role.ROLE_ID
+        AND exists_button_role.BUTTON_CODE = old_button_role.BUTTON_CODE
+  );
+
+-- 6. Verify.
+SELECT
+    m.MENU_ID,
+    m.MENU_NAME,
+    m.URL,
+    m.MODEL_CODE,
+    m.ORDER_KEY,
+    b.BUTTON_CODE,
+    r.CHARACTOR_ID
+FROM sys_menu m
+LEFT JOIN sys_menu_button b ON b.MENU_ID = m.MENU_ID
+LEFT JOIN sys_role r ON r.MENU_ID = m.MENU_ID
+WHERE m.URL IN ('/delivery/deliveryerManage', '/delivery/deliveryerManage1')
+ORDER BY m.URL, b.BUTTON_CODE, r.CHARACTOR_ID;