Prechádzať zdrojové kódy

feat(数据管理)
- 新增数据库备份Worker
- 新增步骤动作枚举
- 调整钥匙归还逻辑,增加强制上传数据选项

refactor(用户)
- 优化指纹登录和指纹校验逻辑,提高匹配效率
- 调整设置指纹界面的数据展示和删除逻辑

fix(流程)
- 修复流程导入时SHA256校验不一致的问题
- 修复作业票待办事项组装逻辑,确保不同类型的步骤能正确生成和关联

style(提示)
- 优化解压进度的文本展示格式
- 调整部分中英文提示文案

周文健 3 mesiacov pred
rodič
commit
c0c1edd112
27 zmenil súbory, kde vykonal 1069 pridanie a 231 odobranie
  1. 23 17
      app/src/main/java/com/grkj/iscs/ISCSApplication.kt
  2. 2 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/home/HomeFragment.kt
  3. 4 4
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt
  4. 113 105
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/MyTodoListFragment.kt
  5. 12 6
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/WorkflowManageFragment.kt
  6. 70 16
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt
  7. 160 32
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/MyTodoViewModel.kt
  8. 1 0
      app/src/main/res/layout/item_my_todo.xml
  9. 1 0
      app/src/main/res/values-en/strings.xml
  10. 1 0
      app/src/main/res/values-zh/strings.xml
  11. 1 0
      app/src/main/res/values/strings.xml
  12. 26 0
      data/src/main/java/com/grkj/data/dao/JobTicketDao.kt
  13. 16 1
      data/src/main/java/com/grkj/data/enums/OperationTypeEnum.kt
  14. 26 0
      data/src/main/java/com/grkj/data/enums/TodoStatusEnum.kt
  15. 14 8
      data/src/main/java/com/grkj/data/model/local/TodoStepJoin.kt
  16. 16 5
      data/src/main/java/com/grkj/data/model/vo/TodoItemVo.kt
  17. 216 33
      data/src/main/java/com/grkj/data/repository/impl/standard/JobTicketRepository.kt
  18. 1 1
      gradle/libs.versions.toml
  19. 1 0
      ui-base/build.gradle.kts
  20. 1 3
      ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt
  21. 155 0
      ui-base/src/main/java/com/grkj/ui_base/dialog/WheelDateRangePickerDialog.kt
  22. 5 0
      ui-base/src/main/res/color/common_text_color_selector.xml
  23. 96 0
      ui-base/src/main/res/layout-land/dialog_wheel_date_range.xml
  24. 96 0
      ui-base/src/main/res/layout/dialog_wheel_date_range.xml
  25. 4 0
      ui-base/src/main/res/values-en/strings.xml
  26. 4 0
      ui-base/src/main/res/values-zh/strings.xml
  27. 4 0
      ui-base/src/main/res/values/strings.xml

+ 23 - 17
app/src/main/java/com/grkj/iscs/ISCSApplication.kt

@@ -5,6 +5,8 @@ import android.app.Application
 import android.app.PendingIntent
 import android.content.Context
 import android.content.Intent
+import android.content.res.Configuration
+import android.util.TypedValue
 import androidx.work.Constraints
 import androidx.work.ExistingPeriodicWorkPolicy
 import androidx.work.PeriodicWorkRequestBuilder
@@ -147,25 +149,29 @@ class ISCSApplication : Application() {
     init {
 
         //设置全局的Header构建器
-        SmartRefreshLayout.setDefaultRefreshHeaderCreator(object : DefaultRefreshHeaderCreator {
-            override fun createRefreshHeader(
-                context: Context,
-                layout: RefreshLayout
-            ): RefreshHeader {
-                return ClassicsHeader(context) //.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header
-            }
-        })
+        SmartRefreshLayout.setDefaultRefreshHeaderCreator { context, layout ->
+            ClassicsHeader(context).apply {
+                if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                    setTextSizeTime(TypedValue.COMPLEX_UNIT_SP, 18 * 1.7f)
+                    setTextSizeTitle(TypedValue.COMPLEX_UNIT_SP, 18 * 1.7f)
+                } else {
+                    setTextSizeTime(TypedValue.COMPLEX_UNIT_SP, 18 * 1.2f)
+                    setTextSizeTitle(TypedValue.COMPLEX_UNIT_SP, 18 * 1.2f)
+                }
+            } //.setTimeFormat(new DynamicTimeFormat("更新于 %s"));//指定为经典Header,默认是 贝塞尔雷达Header
+        }
 
         //设置全局的Footer构建器
-        SmartRefreshLayout.setDefaultRefreshFooterCreator(object : DefaultRefreshFooterCreator {
-            override fun createRefreshFooter(
-                context: Context,
-                p1: RefreshLayout
-            ): RefreshFooter {
-                //指定为经典Footer,默认是 BallPulseFooter
-                return ClassicsFooter(context).setDrawableSize(20f)
+        SmartRefreshLayout.setDefaultRefreshFooterCreator { context, p1 -> //指定为经典Footer,默认是 BallPulseFooter
+            ClassicsFooter(context).apply {
+                if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                    setTextSizeTitle(TypedValue.COMPLEX_UNIT_SP, 18 * 1.7f)
+                    setDrawableSize(20f * 1.7f)
+                } else {
+                    setTextSizeTitle(TypedValue.COMPLEX_UNIT_SP, 18 * 1.2f)
+                    setDrawableSize(20f * 1.2f)
+                }
             }
-
-        })
+        }
     }
 }

+ 2 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/home/HomeFragment.kt

@@ -30,6 +30,7 @@ import com.sik.sikcore.date.TimeUtils
 import com.sik.sikcore.extension.getMMKVData
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
+import me.jessyan.autosize.AutoSize
 import java.util.Date
 import kotlin.getValue
 
@@ -146,6 +147,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
     }
 
     private fun pickDateTime(startTime: Boolean = true, timeView: TextView) {
+        AutoSize.autoConvertDensity(requireActivity(), 600f, false)
         CardDatePickerDialog.builder(requireContext()).setTitle(
             if (startTime) CommonUtils.getStr(com.grkj.ui_base.R.string.start_time)
                 .toString() else CommonUtils.getStr(com.grkj.ui_base.R.string.end_time)

+ 4 - 4
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt

@@ -524,7 +524,7 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
 
             EventConstants.EVENT_UPDATE_TICKET_PROGRESS -> {
                 checkLayout(1)
-                getData()
+                getData(true)
             }
 
             EventConstants.EVENT_TICKET_FINISHED -> {
@@ -626,12 +626,12 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
             viewModel.ticketId =
                 GlobalDataTempStore.getInstance().getData(DataTransferConstants.KEY_JOB_TICKET_ID)
                     ?: 0L
-            getData()
+            getData(true)
         }
     }
 
-    private fun getData() {
-        viewModel.getJobTicketData().observe(this) {
+    private fun getData(refresh: Boolean = false) {
+        viewModel.getJobTicketData(refresh).observe(this) {
             if (it) {
                 viewModel.isUnlockFirst(viewModel.ticketData?.modeId!!).observe(this) {}
                 viewModel.getWorkflowSteps(viewModel.ticketData?.modeId!!).observe(this) {

+ 113 - 105
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/MyTodoListFragment.kt

@@ -16,6 +16,7 @@ import com.grkj.data.data.MainDomainData
 import com.grkj.data.data.Type
 import com.grkj.data.enums.OperationTypeEnum
 import com.grkj.data.enums.RoleEnum
+import com.grkj.data.enums.TodoStatusEnum
 import com.grkj.data.model.vo.IsJobTicketStepDataVo
 import com.grkj.data.model.vo.TodoItemVo
 import com.grkj.iscs.R
@@ -32,6 +33,7 @@ import com.grkj.shared.model.EventBean
 import com.grkj.shared.utils.ArcSoftUtil
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.dialog.WheelDateRangePickerDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.RFIDCardReadEvent
 import com.grkj.ui_base.utils.event.UiEvent
@@ -111,38 +113,50 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
                     0 -> {
                         binding.handleTime.text = it.getShowText()
                         binding.listRv.models =
-                            viewModel.doneData.sortedByDescending { it.stepUpdateTime }
+                            viewModel.doneData.sortedWith(compareByDescending<TodoItemVo> { it.stepUpdateTime }.thenBy {
+                                OperationTypeEnum.actionPriority(
+                                    it.todoType
+                                )
+                            })
                     }
 
                     1 -> {
                         binding.handleTime.text = it.getShowText()
                         binding.listRv.models = viewModel.doneData.filter {
-                            val updateTimeDate = TimeUtils.parseDate(
-                                it.stepUpdateTime,
-                                TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT
+                            TimeUtils.isWithinRange(
+                                it.stepUpdateTime.split(" ")[0], TimeUtils.formatDate(
+                                    TimeUtils.offsetDay(7),
+                                    TimeUtils.DEFAULT_DATE_FORMAT
+                                ), TimeUtils.nowString(
+                                    TimeUtils.DEFAULT_DATE_FORMAT
+                                ), TimeUtils.DEFAULT_DATE_FORMAT
                             )
-                            updateTimeDate?.let {
-                                TimeUtils.calcDayNum(it, Date()) <= 7
-                            } ?: false
-                        }.sortedByDescending { it.stepUpdateTime }
+                        }.sortedWith(compareByDescending<TodoItemVo> { it.stepUpdateTime }.thenBy {
+                            OperationTypeEnum.actionPriority(
+                                it.todoType
+                            )
+                        })
                     }
 
                     2 -> {
                         binding.handleTime.text = it.getShowText()
                         binding.listRv.models = viewModel.doneData.filter {
-                            val updateTimeDate = TimeUtils.parseDate(
-                                it.stepUpdateTime,
-                                TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT
+                            TimeUtils.isWithinRange(
+                                it.stepUpdateTime.split(" ")[0], TimeUtils.formatDate(
+                                    TimeUtils.offsetDay(30),
+                                    TimeUtils.DEFAULT_DATE_FORMAT
+                                ), TimeUtils.nowString(
+                                    TimeUtils.DEFAULT_DATE_FORMAT
+                                ), TimeUtils.DEFAULT_DATE_FORMAT
+                            )
+                        }.sortedWith(compareByDescending<TodoItemVo> { it.stepUpdateTime }.thenBy {
+                            OperationTypeEnum.actionPriority(
+                                it.todoType
                             )
-                            updateTimeDate?.let {
-                                TimeUtils.calcDayNum(it, Date()) <= 30
-                            } ?: false
-                        }.sortedByDescending { it.stepUpdateTime }
+                        })
                     }
 
                     3 -> {
-                        viewModel.startTime = ""
-                        viewModel.endTime = ""
                         pickDateTime(timeView = binding.handleTime)
                     }
                 }
@@ -153,55 +167,28 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
     /**
      * 选择时间
      */
-    private fun pickDateTime(startTime: Boolean = true, timeView: TextView) {
-        CardDatePickerDialog.builder(requireContext()).setTitle(
-            if (startTime) CommonUtils.getStr(com.grkj.ui_base.R.string.start_time)
-                .toString() else CommonUtils.getStr(com.grkj.ui_base.R.string.end_time)
-                .toString()
-        ).setOnChoose { millisecond ->
-            val chooseTime = TimeUtils.formatDate(
-                Date(millisecond), TimeUtils.DEFAULT_DATE_HOUR_MIN_FORMAT
-            )
-            if (TimeUtils.isTimeBefore(
-                    chooseTime,
+    private fun pickDateTime(timeView: TextView) {
+        WheelDateRangePickerDialog.show(
+            viewModel.startTime,
+            viewModel.endTime
+        ) { startTime, endTime ->
+            viewModel.startTime = startTime
+            viewModel.endTime = endTime
+            timeView.text =
+                "${viewModel.startTime} - ${viewModel.endTime}"
+            binding.listRv.models = viewModel.doneData.filter {
+                TimeUtils.isWithinRange(
+                    it.stepUpdateTime.split(" ")[0],
                     viewModel.startTime,
-                    TimeUtils.DEFAULT_DATE_HOUR_MIN_FORMAT
+                    viewModel.endTime,
+                    TimeUtils.DEFAULT_DATE_FORMAT
                 )
-            ) {
-                showToast(getString(R.string.start_time_must_large_then_end_time))
-                pickDateTime(startTime, timeView)
-                return@setOnChoose
-            }
-            val timeStr = TimeUtils.formatDate(
-                Date(millisecond), TimeUtils.DEFAULT_DATE_HOUR_MIN_FORMAT
-            )
-            if (startTime) {
-                viewModel.startTime = timeStr
-            } else {
-                viewModel.endTime = timeStr
-            }
-            if (viewModel.startTime.isNotEmpty() && viewModel.endTime.isNotEmpty()) {
-                timeView.text = "${viewModel.startTime}-${viewModel.endTime}"
-                binding.listRv.models = viewModel.doneData.filter {
-                    val updateTimeDate = TimeUtils.parseDate(
-                        it.stepUpdateTime,
-                        TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT
-                    )!!
-                    val startTimeDate = TimeUtils.parseDate(
-                        viewModel.startTime,
-                        TimeUtils.DEFAULT_DATE_HOUR_MIN_FORMAT
-                    )!!
-                    val endTimeDate = TimeUtils.parseDate(
-                        viewModel.endTime,
-                        TimeUtils.DEFAULT_DATE_HOUR_MIN_FORMAT
-                    )!!
-                    TimeUtils.isTimeBefore(startTimeDate, updateTimeDate) &&
-                            TimeUtils.isTimeBefore(updateTimeDate, endTimeDate)
-                }.sortedByDescending { it.stepUpdateTime }
-            } else if (viewModel.startTime.isNotEmpty() && viewModel.endTime.isEmpty() && startTime) {
-                pickDateTime(false, timeView)
-            }
-        }.build().show()
+            }.sortedWith(compareByDescending<TodoItemVo> { it.stepUpdateTime }.thenBy {
+                OperationTypeEnum.actionPriority(
+                    it.todoType
+                )
+            })
+        }
     }
 
     private fun setupTabs() {
@@ -236,7 +223,12 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
                 val list = when (tab.position) {
                     0 -> viewModel.waitData.sortedBy { it.ticketId }
                     1 -> viewModel.todoData.sortedBy { it.ticketId }
-                    2 -> viewModel.doneData.sortedByDescending { it.stepUpdateTime }
+                    2 -> viewModel.doneData.sortedWith(compareByDescending<TodoItemVo> { it.stepUpdateTime }.thenBy {
+                        OperationTypeEnum.actionPriority(
+                            it.todoType
+                        )
+                    })
+
                     else -> mutableListOf()
                 }
                 list to tab.position
@@ -262,7 +254,7 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
 
     private fun getTodoTitlePrefix(item: TodoItemVo): String {
         return when {
-            !item.isCurrentStep && item.stepStatus == "0" -> "等待:"
+            item.todoStatus == TodoStatusEnum.WAIT -> "等待:"
             else -> ""
         }
     }
@@ -272,35 +264,54 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
         val item = holder.getModel<TodoItemVo>()
         itemBinding.todoTitle.text =
             "${getTodoTitlePrefix(item)}${item.todoType.todoTitle}${item.groupName?.let { if (it.isEmpty()) "(${item.todoContent})" else "(${it})" }}"
-        if (item.isCurrentStep) {
+        if (item.todoStatus == TodoStatusEnum.TODO) {
             itemBinding.todoTitle.setTextColor(requireContext().getColor(R.color.tag_error))
         } else {
             itemBinding.todoTitle.setTextColor(requireContext().getColor(R.color.text_primary))
         }
         itemBinding.jobName.text = item.ticketName
-        itemBinding.currentStep.isVisible = item.stepStatus == "0"
-        itemBinding.currentStepTv.isVisible = item.stepStatus == "0"
-        itemBinding.completeTime.isVisible = item.stepStatus == "1"
-        itemBinding.completeTimeTv.isVisible = item.stepStatus == "1"
+        itemBinding.currentStep.isVisible = item.todoStatus == TodoStatusEnum.TODO
+        itemBinding.currentStepTv.isVisible = item.todoStatus == TodoStatusEnum.TODO
+        itemBinding.completeTime.isVisible = item.todoStatus == TodoStatusEnum.DONE
+        itemBinding.completeTimeTv.isVisible = item.todoStatus == TodoStatusEnum.DONE
         itemBinding.completeTime.text = item.stepUpdateTime
         itemBinding.currentStep.text = "${item.stepIndex}-${item.todoTitle}"
-        itemBinding.currentOperationTv.isVisible = !item.isCurrentStep && item.stepStatus == "0"
-        itemBinding.currentOperation.isVisible = !item.isCurrentStep && item.stepStatus == "0"
+        itemBinding.currentOperationTv.isVisible = item.todoStatus == TodoStatusEnum.WAIT
+        itemBinding.currentOperation.isVisible = item.todoStatus == TodoStatusEnum.WAIT
         itemBinding.currentOperation.text =
             item.previousTodoItem?.joinToString(",") { itemData -> "${itemData.todoType.todoTitle}${itemData.groupName?.let { if (it.isEmpty()) "(${itemData.todoContent})" else "(${it})" }}" }
         itemBinding.btnPointDetail.isVisible =
             item.todoType == OperationTypeEnum.LOCK_TAKE_KEY || item.todoType == OperationTypeEnum.UNLOCK_TAKE_KEY || item.todoType == OperationTypeEnum.LOCK_RETURN_KEY || item.todoType == OperationTypeEnum.UNLOCK_RETURN_KEY
-        itemBinding.btnHandle.isVisible = if (item.todoType == OperationTypeEnum.UNLOCK_TAKE_KEY) {
-            val canUnLock =
-                item.enableUnlock && jobExecuteViewModel.ticketPoints.filter { it.groupId == item.groupId }
-                    .all { it.pointStatus == "1" } && ((jobExecuteViewModel.workflowModes.find { it.modeId == jobExecuteViewModel.ticketData?.modeId }?.isColockSupport == false) ||
-                        ((jobExecuteViewModel.workflowModes.find { it.modeId == jobExecuteViewModel.ticketData?.modeId }?.isColockSupport == true) &&
-                                jobExecuteViewModel.ticketUser.filter { it.userRole == RoleEnum.JTCOLOCKER.roleKey }
+
+        if (jobExecuteViewModel.ticketId != item.ticketId) {
+            jobExecuteViewModel.getWorkflowModes().observe(this) {
+                jobExecuteViewModel.ticketId = item.ticketId
+                getJobTicketData {
+                    itemBinding.btnHandle.isVisible =
+                        if (item.todoType == OperationTypeEnum.UNLOCK_TAKE_KEY) {
+                            val canUnLock =
+                                jobExecuteViewModel.ticketPoints.filter { it.groupId == item.groupId }
+                                    .all { it.pointStatus == "1" } && (jobExecuteViewModel.workflowModes.find { it.modeId == jobExecuteViewModel.ticketData?.modeId }?.isColockSupport == false || (jobExecuteViewModel.workflowModes.find { it.modeId == jobExecuteViewModel.ticketData?.modeId }?.isColockSupport == true && jobExecuteViewModel.ticketUser.filter { it.userRole == RoleEnum.JTCOLOCKER.roleKey }
                                     .all { it.jobStatus == "2" }))
-            item.isCurrentStep && item.todoType != OperationTypeEnum.LOCK_RETURN_KEY && item.todoType != OperationTypeEnum.UNLOCK_RETURN_KEY && canUnLock
+                            item.isCurrentStep && canUnLock
+                        } else {
+                            item.isCurrentStep && item.todoType != OperationTypeEnum.LOCK_RETURN_KEY && item.todoType != OperationTypeEnum.UNLOCK_RETURN_KEY
+                        } && item.todoStatus == TodoStatusEnum.TODO
+                }
+            }
         } else {
-            item.isCurrentStep && item.todoType != OperationTypeEnum.LOCK_RETURN_KEY && item.todoType != OperationTypeEnum.UNLOCK_RETURN_KEY
+            itemBinding.btnHandle.isVisible =
+                if (item.todoType == OperationTypeEnum.UNLOCK_TAKE_KEY) {
+                    val canUnLock =
+                        jobExecuteViewModel.ticketPoints.filter { it.groupId == item.groupId }
+                            .all { it.pointStatus == "1" } && (jobExecuteViewModel.workflowModes.find { it.modeId == jobExecuteViewModel.ticketData?.modeId }?.isColockSupport == false || (jobExecuteViewModel.workflowModes.find { it.modeId == jobExecuteViewModel.ticketData?.modeId }?.isColockSupport == true && jobExecuteViewModel.ticketUser.filter { it.userRole == RoleEnum.JTCOLOCKER.roleKey }
+                            .all { it.jobStatus == "2" }))
+                    item.isCurrentStep && canUnLock
+                } else {
+                    item.isCurrentStep && item.todoType != OperationTypeEnum.LOCK_RETURN_KEY && item.todoType != OperationTypeEnum.UNLOCK_RETURN_KEY
+                } && item.todoStatus == TodoStatusEnum.TODO
         }
+
         itemBinding.btnPointDetail.setDebouncedClickListener {
             viewModel.getTicketPointsData(item.ticketId).observe(this) {
                 TodoPointDetailDialog.show(it.filter { it.groupId == item.groupId })
@@ -314,7 +325,7 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
             TipDialog.showInfo(viewModel.getHandleTip(item), onConfirmClick = {
                 jobExecuteViewModel.getWorkflowModes().observe(this) {
                     jobExecuteViewModel.ticketId = item.ticketId
-                    getJobTicketData {
+                    getJobTicketData(true) {
                         when (item.todoType) {
                             OperationTypeEnum.LOCK_TAKE_KEY -> item.groupId?.let {
                                 jobExecuteViewModel.toLock(it).observe(this) {}
@@ -347,17 +358,16 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
                             }
 
                             OperationTypeEnum.CONFIRM -> {
-                                jobExecuteViewModel.currentStepData
-                                    ?.let { stepVo ->
-                                        stepClickConfirm(
-                                            stepVo,
-                                            jobExecuteViewModel.currentStepData
-                                        )
-                                    }
+                                jobExecuteViewModel.currentStepData?.let { stepVo ->
+                                    stepClickConfirm(
+                                        stepVo, jobExecuteViewModel.currentStepData
+                                    )
+                                }
                             }
 
-                            OperationTypeEnum.END -> jobExecuteViewModel.finishJob()
-                                .observe(this) {}
+                            OperationTypeEnum.END -> jobExecuteViewModel.finishJob().observe(this) {
+                                getData()
+                            }
 
                             else -> ""
                         }
@@ -478,11 +488,15 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
             }
 
             EventConstants.EVENT_UPDATE_TICKET_PROGRESS -> {
-                getData()
+                getJobTicketData(true) {
+                    getData()
+                }
             }
 
             EventConstants.EVENT_TICKET_FINISHED -> {
-                getData()
+                getJobTicketData(true) {
+                    getData()
+                }
             }
         }
     }
@@ -491,8 +505,7 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
      * 点击确认
      */
     private fun stepClickConfirm(
-        item: IsJobTicketStepDataVo,
-        workflowStep: IsJobTicketStepDataVo?
+        item: IsJobTicketStepDataVo, workflowStep: IsJobTicketStepDataVo?
     ) {
         if (!checkSelectMember(workflowStep)) {
             if (jobExecuteViewModel.stepConditionsComplete(workflowStep)) {
@@ -553,22 +566,18 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
     /**
      * 获取作业票相关数据
      */
-    private fun getJobTicketData(dataGetDone: () -> Unit) {
-        jobExecuteViewModel.getJobTicketData().observe(this) {
+    private fun getJobTicketData(refresh: Boolean = false, dataGetDone: () -> Unit) {
+        jobExecuteViewModel.getJobTicketData(refresh).observe(this) {
             if (it) {
                 jobExecuteViewModel.isUnlockFirst(jobExecuteViewModel.ticketData?.modeId!!)
                     .observe(this) {}
                 jobExecuteViewModel.getWorkflowSteps(jobExecuteViewModel.ticketData?.modeId!!)
                     .observe(this) {
                         jobExecuteViewModel.currentStepData =
-                            jobExecuteViewModel.ticketStep.firstOrNull { it.stepStatus == "0" }
+                            jobExecuteViewModel.ticketStep.filter { it.stepStatus == "0" }
+                                .minByOrNull { it.stepIndex }
                         dataGetDone()
                     }
-            } else {
-                TipDialog.showError(
-                    msg = CommonUtils.getStr(com.grkj.ui_base.R.string.ticket_data_error)
-                        .toString(),
-                )
             }
         }
     }
@@ -593,8 +602,7 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
             }
             if (jobExecuteViewModel.checkMemberFinish) {
                 jobExecuteViewModel.updateLockerAndColockerData().observe(this) {
-                    val workflowStep =
-                        jobExecuteViewModel.currentStepData
+                    val workflowStep = jobExecuteViewModel.currentStepData
                     if (workflowStep?.confirmType != 0 ||//自动确认
                         (jobExecuteViewModel.ticketData?.createBy == MainDomainData.userInfo?.userName || workflowStep.currentUserCanConfirm())
                     ) {

+ 12 - 6
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/WorkflowManageFragment.kt

@@ -39,7 +39,7 @@ class WorkflowManageFragment : BaseFragment<FragmentWorkflowManageBinding>() {
             deleteSelected()
         }
         binding.importWorkflow.setDebouncedClickListener {
-            viewModel.importWorkflowMode().observe(this){
+            viewModel.importWorkflowMode().observe(this) {
                 getData()
             }
         }
@@ -125,11 +125,17 @@ class WorkflowManageFragment : BaseFragment<FragmentWorkflowManageBinding>() {
                 viewModel.workflowModeData.filter { !it.isPreset }.all { it.isSelected }
             setSelectAllListener()
         }
-        itemBinding.delete.setDebouncedClickListener{
-            viewModel.deleteWorkflowMode(item).observe(this@WorkflowManageFragment){
-                TipDialog.showSuccess(CommonUtils.getStr(R.string.delete_success).toString(),onConfirmClick={
-                    getData()
-                })
+        itemBinding.delete.setDebouncedClickListener {
+            if (item.isPreset) {
+                showToast(CommonUtils.getStr(R.string.preset_workflow_can_not_delete).toString())
+                return@setDebouncedClickListener
+            }
+            viewModel.deleteWorkflowMode(item).observe(this@WorkflowManageFragment) {
+                TipDialog.showSuccess(
+                    CommonUtils.getStr(R.string.delete_success).toString(),
+                    onConfirmClick = {
+                        getData()
+                    })
             }
         }
         itemBinding.root.setDebouncedClickListener {

+ 70 - 16
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt

@@ -54,12 +54,20 @@ class JobExecuteViewModel @Inject constructor(
 ) : BaseViewModel(userRepository) {
     var ticketId: Long = 0
     var ticketData: IsJobTicketDataVo? = null
+    var ticketDataMap: MutableMap<Long, IsJobTicketDataVo?> = mutableMapOf()
     lateinit var ticketKey: List<IsJobTicketKeyDataVo>
+    var ticketKeyMap: MutableMap<Long, List<IsJobTicketKeyDataVo>?> = mutableMapOf()
     lateinit var ticketLock: List<IsJobTicketLockDataVo>
+    var ticketLockMap: MutableMap<Long, List<IsJobTicketLockDataVo>?> = mutableMapOf()
     lateinit var ticketPoints: List<IsJobTicketPointsDataVo>
+    var ticketPointsMap: MutableMap<Long, List<IsJobTicketPointsDataVo>?> = mutableMapOf()
     lateinit var ticketStep: List<IsJobTicketStepDataVo>
+    var ticketStepMap: MutableMap<Long, List<IsJobTicketStepDataVo>?> = mutableMapOf()
     lateinit var ticketUser: List<IsJobTicketUserDataVo>
+    var ticketUserMap: MutableMap<Long, List<IsJobTicketUserDataVo>?> = mutableMapOf()
     var groupInfo: MutableList<JobTicketGroupInfoVo> = mutableListOf()
+    var groupInfoMap: MutableMap<Long, MutableList<JobTicketGroupInfoVo>?> = mutableMapOf()
+
     var selectedLockerData: List<JobTicketGroupDataVo<JobUserVo>> = mutableListOf()
     var selecteColockerData: List<JobUserVo> = mutableListOf()
     var checkMemberFinish: Boolean = false
@@ -71,18 +79,66 @@ class JobExecuteViewModel @Inject constructor(
     /**
      * 获取作业数据
      */
-    fun getJobTicketData(): LiveData<Boolean> {
+    fun getJobTicketData(refresh: Boolean): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            ticketData = jobTicketRepository.getJobTicketDataByTicketId(ticketId)
+            ticketData = if (refresh) {
+                jobTicketRepository.getJobTicketDataByTicketId(ticketId).also {
+                    ticketDataMap[ticketId] = it
+                }
+            } else {
+                ticketDataMap.getOrPut(ticketId) {
+                    jobTicketRepository.getJobTicketDataByTicketId(ticketId)
+                }
+            }
             if (ticketData == null) {
                 emit(false)
                 return@liveData
             }
-            ticketKey = jobTicketRepository.getJobTicketKeyDataByTicketId(ticketId)
-            ticketLock = jobTicketRepository.getJobTicketLockDataByTicketId(ticketId)
-            ticketPoints = jobTicketRepository.getJobTicketPointsDataByTicketId(ticketId)
-            ticketStep = jobTicketRepository.getJobTicketStepDataByTicketId(ticketId)
-            ticketUser = jobTicketRepository.getJobTicketUserDataByTicketId(ticketId)
+            ticketKey = if (refresh) {
+                jobTicketRepository.getJobTicketKeyDataByTicketId(ticketId).also {
+                    ticketKeyMap[ticketId] = it
+                }
+            } else {
+                ticketKeyMap.getOrPut(ticketId) {
+                    jobTicketRepository.getJobTicketKeyDataByTicketId(ticketId)
+                }!!
+            }
+            ticketLock = if (refresh) {
+                jobTicketRepository.getJobTicketLockDataByTicketId(ticketId).also {
+                    ticketLockMap[ticketId] = it
+                }
+            } else {
+                ticketLockMap.getOrPut(ticketId) {
+                    jobTicketRepository.getJobTicketLockDataByTicketId(ticketId)
+                }!!
+            }
+            ticketPoints = if (refresh) {
+                jobTicketRepository.getJobTicketPointsDataByTicketId(ticketId).also {
+                    ticketPointsMap[ticketId] = it
+                }
+            } else {
+                ticketPointsMap.getOrPut(ticketId) {
+                    jobTicketRepository.getJobTicketPointsDataByTicketId(ticketId)
+                }!!
+            }
+            ticketStep = if (refresh) {
+                jobTicketRepository.getJobTicketStepDataByTicketId(ticketId).also {
+                    ticketStepMap[ticketId] = it
+                }
+            } else {
+                ticketStepMap.getOrPut(ticketId) {
+                    jobTicketRepository.getJobTicketStepDataByTicketId(ticketId)
+                }!!
+            }
+            ticketUser = if (refresh) {
+                jobTicketRepository.getJobTicketUserDataByTicketId(ticketId).also {
+                    ticketUserMap[ticketId] = it
+                }
+            } else {
+                ticketUserMap.getOrPut(ticketId) {
+                    jobTicketRepository.getJobTicketUserDataByTicketId(ticketId)
+                }!!
+            }
             val tempJobTicketUserId = jobTicketRepository.getTicketUsersByTicketId(ticketId)
             selectedLockerData =
                 tempJobTicketUserId.filter { it.roleKeys.contains(RoleEnum.JTLOCKER.roleKey) }
@@ -413,7 +469,9 @@ class JobExecuteViewModel @Inject constructor(
      */
     fun getWorkflowModes(): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            workflowModes = workflowRepository.getWorkflowModes()
+            if (workflowModes.isEmpty()) {
+                workflowModes = workflowRepository.getWorkflowModes()
+            }
             emit(true)
         }
     }
@@ -425,9 +483,10 @@ class JobExecuteViewModel @Inject constructor(
         return liveData(Dispatchers.IO) {
             for (stepDataVo in ticketStep) {
                 //如果是自动确认并且没有操作的功能,则更新步骤
-                if (stepDataVo.confirmType != 0 && stepDataVo.hasAnyOperationFunction() == false) {
+                if (stepDataVo.confirmType != 0 && !stepDataVo.hasAnyOperationFunction() && stepDataVo.stepStatus == "0") {
                     stepDataVo.stepStatus = "1"
-                    stepDataVo.updateTime = TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+                    stepDataVo.updateTime =
+                        TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
                     BeanUtils.copyProperties(
                         stepDataVo, IsJobTicketStep::class.java
                     )?.let { jobTicketStep ->
@@ -666,12 +725,7 @@ class JobExecuteViewModel @Inject constructor(
      * 是否需要显示提示
      */
     fun needShowTip(): Boolean {
-        return currentStepData?.enableSetLocker == true ||
-                currentStepData?.enableSetColocker == true ||
-                currentStepData?.enableLock == true ||
-                currentStepData?.enableColock == true ||
-                currentStepData?.enableReleaseColock == true ||
-                currentStepData?.enableUnlock == true
+        return currentStepData?.enableSetLocker == true || currentStepData?.enableSetColocker == true || currentStepData?.enableLock == true || currentStepData?.enableColock == true || currentStepData?.enableReleaseColock == true || currentStepData?.enableUnlock == true
     }
 
     /**

+ 160 - 32
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/MyTodoViewModel.kt

@@ -4,7 +4,9 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
 import com.grkj.data.data.DictConstants
 import com.grkj.data.data.MainDomainData
+import com.grkj.data.enums.JobTicketStatusEnum
 import com.grkj.data.enums.OperationTypeEnum
+import com.grkj.data.enums.TodoStatusEnum
 import com.grkj.data.model.local.TodoStepJoin
 import com.grkj.data.model.local.isMyTodo
 import com.grkj.data.model.res.CommonDictRes
@@ -18,6 +20,7 @@ import com.grkj.ui_base.utils.CommonUtils
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
 import javax.inject.Inject
+import kotlin.collections.contains
 
 /**
  * 作业管理
@@ -85,22 +88,43 @@ class MyTodoViewModel @Inject constructor(
         val grouped = all.groupBy { it.ticketId }
 
         for ((_, steps) in grouped) {
-            val doneSteps = steps.filter { it.stepStatus == "1" }
-            val pendingSteps = steps.filter { it.stepStatus == "0" }
+            var doneSteps =
+                steps.filter {
+                    it.stepStatus == "1" && (!it.enableEndJob && it.ticketStatus in listOf(
+                        JobTicketStatusEnum.SELECT_MEMBER.status,
+                        JobTicketStatusEnum.LOCKING.status,
+                        JobTicketStatusEnum.COLOCKING.status,
+                        JobTicketStatusEnum.UNLOCKING.status,
+                        JobTicketStatusEnum.PROGRESSING.status,
+                    ) || it.ticketStatus == JobTicketStatusEnum.FINISHED.status)
+                }
+            val pendingSteps =
+                steps.filter {
+                    it.stepStatus == "0" || (it.stepStatus == "1" && it.enableEndJob && it.ticketStatus in listOf(
+                        JobTicketStatusEnum.SELECT_MEMBER.status,
+                        JobTicketStatusEnum.LOCKING.status,
+                        JobTicketStatusEnum.COLOCKING.status,
+                        JobTicketStatusEnum.UNLOCKING.status,
+                        JobTicketStatusEnum.PROGRESSING.status,
+                    ))
+                }
 
             /* —— 当前步骤 → todoData —— */
             val todoStep =
-                pendingSteps.filter { it.isCurrentStep }
-            tempTodo += todoStep
+                pendingSteps.filter { it.isCurrentStep }.findPredecessors()
+            tempTodo += todoStep.second
+            doneSteps = doneSteps.toMutableList().apply {
+                addAll(todoStep.third)
+            }
 
             /* —— 其它待办 → waitData —— */
-            val otherPending = pendingSteps.filter { it !in todoStep }
+            val otherPending = pendingSteps.filter { it !in (todoStep.second + todoStep.third) }
 
             /* ① 按 (stepIndex, actionKey) 分批 —— */
             val byBatch = otherPending
                 .groupBy { it.stepIndex to actionKey(it) }             // <─ 新增动作维度
                 .toSortedMap(
-                    compareBy<Pair<Int, String>> { it.first }          // stepIndex 升序
+                    compareBy<Pair<Int, Int>> { it.first }          // stepIndex 升序
                         .thenBy { it.second }                          // actionKey 字典序
                 )
 
@@ -111,7 +135,7 @@ class MyTodoViewModel @Inject constructor(
 
                 val action = actionKey(batchList[0])  // 同批次动作一致,取任意一条判断即可
 
-                if (action == "LOCK" || action == "UNLOCK") {
+                if (action == 1 || action == 4) {
                     // ✅ 上锁/解锁 → 同一 stepIndex 下多个 group 要全部保留
                     tempWait += batchList
                 } else {
@@ -124,7 +148,16 @@ class MyTodoViewModel @Inject constructor(
 
             tempDone += doneSteps
         }
-
+        tempTodo.forEach {
+            it.todoStatus = TodoStatusEnum.TODO
+        }
+        tempWait.forEach {
+            it.todoStatus = TodoStatusEnum.WAIT
+            it.previousTodoItem = tempTodo.filter { todoData -> it.ticketId == todoData.ticketId }
+        }
+        tempDone.forEach {
+            it.todoStatus = TodoStatusEnum.DONE
+        }
         /* —— 结果赋值 —— */
         todoData = tempTodo
             .sortedBy { it.ticketId }
@@ -139,16 +172,120 @@ class MyTodoViewModel @Inject constructor(
             .toMutableList()
     }
 
+    /**
+     * 处理前置
+     */
+    private fun List<TodoItemVo>.findPredecessors(): Triple<List<TodoItemVo>, List<TodoItemVo>, List<TodoItemVo>> {
+        return when {
+            any { it.enableLock } && any { it.enableColock } -> {
+                if (this.any { it.pointStatus.any { it == "0" } }) {
+                    Triple(filter { it.enableColock }, filter { it.enableLock }, mutableListOf())
+                } else if (this.any { it.pointStatus.all { it == "1" } } && this.any { it.colockerStatus.any { it == "0" } }) {
+                    Triple(
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableColock },
+                        filter { it.enableLock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableLock || it.enableColock })
+                }
+            }
+
+            any { it.enableColock } && any { it.enableReleaseColock } -> {
+                if (this.any { it.colockerStatus.any { it == "0" } }) {
+                    Triple(
+                        filter { it.enableReleaseColock },
+                        filter { it.enableColock },
+                        mutableListOf()
+                    )
+                } else if (this.all { it.colockerStatus.all { it == "1" } }) {
+                    Triple(
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableReleaseColock },
+                        filter { it.enableColock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableReleaseColock || it.enableColock })
+                }
+            }
+
+            any { it.enableReleaseColock } && any { it.enableUnlock } && any { it.enableEndJob } -> {
+                if (this.any { it.colockerStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock },
+                        mutableListOf()
+                    )
+                } else if (this.all { it.colockerStatus.all { it == "2" } } && this.any { it.pointStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock })
+                } else if (this.all { it.stepStatus == "0" } && this.all { it.pointStatus.all { it == "2" } }) {
+                    Triple(
+                        filter { it.enableEndJob },
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock || it.enableReleaseColock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.enableEndJob },
+                        filter { it.enableUnlock || it.enableReleaseColock || it.todoType == OperationTypeEnum.CONFIRM }
+                    )
+                }
+            }
+
+            any { it.enableReleaseColock } && any { it.enableUnlock } -> {
+                if (this.any { it.colockerStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock },
+                        mutableListOf()
+                    )
+                } else if (this.all { it.colockerStatus.all { it == "2" } } && this.any { it.pointStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.todoType == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock || it.enableReleaseColock })
+                }
+            }
+
+            any { it.enableUnlock } && any { it.enableEndJob } -> {
+                if (this.any { it.pointStatus.any { it != "2" } }) {
+                    Triple(filter { it.enableEndJob }, filter { it.enableUnlock }, mutableListOf())
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.enableEndJob },
+                        filter { it.enableUnlock })
+                }
+            }
+
+            else -> Triple(
+                filter { it.todoType == OperationTypeEnum.CONFIRM }, this, mutableListOf()
+            )
+        }
+    }
+
     /* -------------------------------------------------------------
      * 根据唯一的 enableXXX 返回动作标识
      * ----------------------------------------------------------- */
-    private fun actionKey(item: TodoItemVo): String = when {
-        item.enableLock -> "LOCK"
-        item.enableUnlock -> "UNLOCK"
-        item.enableColock -> "COLOCK"
-        item.enableReleaseColock -> "RELEASE_COLOCK"
-        item.enableEndJob -> "END_JOB"
-        else -> "CONFIRM"
+    private fun actionKey(item: TodoItemVo): Int = when {
+        item.enableLock -> 1
+        item.enableUnlock -> 4
+        item.enableColock -> 2
+        item.enableReleaseColock -> 3
+        item.enableEndJob -> 6
+        else -> 5
     }
 
 
@@ -160,23 +297,15 @@ class MyTodoViewModel @Inject constructor(
             this.todoId = temp.stepId
             this.ticketId = temp.ticketId
             this.ticketName = temp.ticketName
+            this.ticketStatus = temp.ticketStatus
             this.stepId = temp.stepId
             this.workflowStepId = temp.workflowStepId
             this.stepIndex = temp.stepIndex
-            this.isCurrentStep = sameTicketStepJoinData.filter { it.stepStatus == "0" }
-                .minByOrNull { it.stepIndex }?.stepIndex == temp.stepIndex
-            this.todoStatus = when {
-                (temp.stepStatus == "0" || (temp.stepStatus == "1" && type == OperationTypeEnum.END)) && this.isCurrentStep -> CommonUtils.getStr(
-                    R.string.todo_header
-                )
-
-                (temp.stepStatus == "0" || (temp.stepStatus == "1" && type == OperationTypeEnum.END)) && !this.isCurrentStep -> CommonUtils.getStr(
-                    R.string.wait_header
-                )
-
-                temp.stepStatus == "1" -> CommonUtils.getStr(R.string.done_header)
-                else -> ""
-            }.toString()
+            this.isCurrentStep =
+                sameTicketStepJoinData.filter { it.stepStatus == "0" }
+                    .minByOrNull { it.stepIndex }?.stepIndex == temp.stepIndex || (sameTicketStepJoinData.none {
+                    it.stepStatus == "0"
+                } && temp.enableEndJob)
             this.todoTitle = temp.stepTitleShort ?: temp.stepTitle
             this.todoContent = temp.stepDescription
             this.todoType = type
@@ -185,6 +314,7 @@ class MyTodoViewModel @Inject constructor(
             this.stepStatus = temp.stepStatus
             this.stepUpdateTime = temp.stepUpdateTime
             this.colockerStatus = temp.colockerStatus
+            this.pointStatus = temp.pointStatus
             this.createTime = temp.ticketStartTime
             this.enableSetLocker = temp.enableSetLocker
             this.enableSetColocker = temp.enableSetColocker
@@ -200,8 +330,6 @@ class MyTodoViewModel @Inject constructor(
             this.stepTitle = temp.stepTitle
             this.stepTitleShort = temp.stepTitleShort
             this.stepDescription = temp.stepDescription
-            this.previousTodoItem =
-                temp.previousTodoStepJoin?.map { it.toTodoVo(sameTicketStepJoinData) }
         }
     }
 
@@ -240,4 +368,4 @@ class MyTodoViewModel @Inject constructor(
             emit(ticketPoints)
         }
     }
-}
+}

+ 1 - 0
app/src/main/res/layout/item_my_todo.xml

@@ -168,6 +168,7 @@
                 android:text="@string/handle"
                 android:textColor="@color/white"
                 android:textSize="@dimen/common_text_size"
+                android:visibility="gone"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/current_step_tv"

+ 1 - 0
app/src/main/res/values-en/strings.xml

@@ -532,5 +532,6 @@
     <string name="handle_release_colock">Please confirm whether to proceed with the Release Co-Lock</string>
     <string name="please_input_correct_phone">Please input correct phone</string>
     <string name="import_success">Import success</string>
+    <string name="preset_workflow_can_not_delete">Preset workflow mode can not delete</string>
 
 </resources>

+ 1 - 0
app/src/main/res/values-zh/strings.xml

@@ -532,5 +532,6 @@
     <string name="handle_release_colock">请确认是否要进行解除共锁</string>
     <string name="please_input_correct_phone">请输入正确的手机号</string>
     <string name="import_success">导入成功</string>
+    <string name="preset_workflow_can_not_delete">预设流程模式无法删除</string>
 
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -535,5 +535,6 @@
     <string name="handle_release_colock">请确认是否要进行解除共锁</string>
     <string name="please_input_correct_phone">请输入正确的手机号</string>
     <string name="import_success">导入成功</string>
+    <string name="preset_workflow_can_not_delete">预设流程模式无法删除</string>
 
 </resources>

+ 26 - 0
data/src/main/java/com/grkj/data/dao/JobTicketDao.kt

@@ -713,4 +713,30 @@ interface JobTicketDao {
      */
     @Query("select count(1) from is_job_ticket where sop_id = :sopId and ticket_status in ('1','2','3','4','7')")
     fun checkSopHasJobInProgress(sopId: Long): Int
+
+    /**
+     * 根据作业票id获取点位
+     */
+    @Query(
+        """
+        select iip.point_id as pointId,
+        iip.point_name as pointName,
+        iip.remark as pointFunction,
+        iw.workstation_name as workstationName,
+        iw.workstation_id as workstationId,
+        irt.rfid as rfidToken,
+        irt.rfid_id as rfidId,
+        ijtp.point_status as pointStatus,
+        iip.power_type as powerType,
+        ijtp.group_id as groupId,
+        ijtg.group_name as groupName
+        from is_job_ticket_points ijtp
+        left join  is_isolation_point iip on ijtp.point_id = iip.point_id
+        left join is_rfid_token irt on iip.rfid_id = irt.rfid_id
+        left join is_workstation iw on iip.workstation_id=iw.workstation_id
+        left join is_job_ticket_group ijtg on ijtg.id = ijtp.group_id
+        WHERE ijtp.ticket_id in (:ticketIds)
+    """
+    )
+    fun getTicketPointsByTicketIds(ticketIds: List<Long>): List<JobPointVo>
 }

+ 16 - 1
data/src/main/java/com/grkj/data/enums/OperationTypeEnum.kt

@@ -30,12 +30,27 @@ enum class OperationTypeEnum(val code: String, val desc: String, val todoTitle:
                 j.enableLock && j.userRole == RoleEnum.JTLOCKER.roleKey && (j.keyStatus == "1" || j.keyStatus == "2") -> LOCK_RETURN_KEY
                 j.enableUnlock && j.userRole == RoleEnum.JTLOCKER.roleKey && j.keyStatus == "0" -> UNLOCK_TAKE_KEY
                 j.enableUnlock && j.userRole == RoleEnum.JTLOCKER.roleKey && (j.keyStatus == "1" || j.keyStatus == "2") -> UNLOCK_RETURN_KEY
+                //todo 共锁人的共锁暂时不算代办
                 j.enableColock && isCreator -> COLOCK
-                j.enableReleaseColock && isCreator -> RELEASE_COLOCK
+                j.enableReleaseColock && isCreator-> RELEASE_COLOCK
                 j.enableEndJob && isCreator -> END
                 !j.hasAnyHardwareOperationFunction() -> CONFIRM
                 else -> UNKNOWN
             }
         }
+
+        /**
+         * 根据 OperationTypeEnum 构建优先级
+         */
+        fun actionPriority(item: OperationTypeEnum): Int = when (item) {
+            END -> 1
+            UNLOCK_RETURN_KEY -> 2
+            UNLOCK_TAKE_KEY -> 3
+            RELEASE_COLOCK -> 4
+            COLOCK -> 5
+            LOCK_RETURN_KEY -> 6
+            LOCK_TAKE_KEY -> 7
+            else -> 8 // CONFIRM
+        }
     }
 }

+ 26 - 0
data/src/main/java/com/grkj/data/enums/TodoStatusEnum.kt

@@ -0,0 +1,26 @@
+package com.grkj.data.enums
+
+import com.grkj.data.model.local.TodoStepJoin
+import com.grkj.data.model.local.hasAnyHardwareOperationFunction
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * 待办状态
+ */
+enum class TodoStatusEnum() {
+    /**
+     * 等待中
+     */
+    WAIT,
+
+    /**
+     * 处理中
+     */
+    TODO,
+
+    /**
+     * 已处理
+     */
+    DONE;
+}

+ 14 - 8
data/src/main/java/com/grkj/data/model/local/TodoStepJoin.kt

@@ -2,6 +2,7 @@ package com.grkj.data.model.local
 
 import com.grkj.data.enums.RoleEnum
 import com.grkj.data.enums.StepAction
+import com.grkj.data.enums.TodoStatusEnum
 
 data class TodoStepJoin(
     // ----- 基础定位字段 -----
@@ -14,6 +15,7 @@ data class TodoStepJoin(
 
     // ----- Ticket 信息 -----
     val ticketName: String?,
+    val ticketStatus: String?,
     val createBy: String?,
     val ticketStartTime: String?,
 
@@ -38,12 +40,15 @@ data class TodoStepJoin(
     val lockerUserId: Long?, //上锁人id
     val colockerUserId: List<Long> = mutableListOf(), //共锁人id
     val colockerStatus: List<String> = mutableListOf(), //共锁人状态
+    val pointStatus: List<String> = mutableListOf(), //点位状态
     val keyStatus: String?,   // 0/1/2 未取 已取 已还
 
     var groupId: Long? = null,
     var groupName: String? = null,
 
     var previousTodoStepJoin: List<TodoStepJoin>? = null,
+    //待办状态
+    var todoStatus: TodoStatusEnum
 )
 
 // ============== 4️⃣ 权责判断 ==============
@@ -54,9 +59,10 @@ fun TodoStepJoin.isMyTodo(currentUserId: Long, currentUserName: String): Boolean
     if (enableLock && userRole == RoleEnum.JTLOCKER.roleKey && lockerUserId == currentUserId) return true
     if (enableUnlock && userRole == RoleEnum.JTLOCKER.roleKey && lockerUserId == currentUserId) return true
 
-    // ---- 判定共锁/解除共锁(由创建者) ----
-    if (enableColock && currentUserId in colockerUserId) return true
-    if (enableReleaseColock && currentUserId in colockerUserId) return true
+    // ---- 判定共锁/解除共锁(由创建者) ----=
+    //todo 共锁人的共锁暂时不算代办 currentUserId in colockerUserId
+    if (enableColock && isCreator) return true
+    if (enableReleaseColock && isCreator) return true
 
     // ---- 判定结束作业(由创建者) ----
     if (enableEndJob && isCreator) return true
@@ -69,11 +75,11 @@ fun TodoStepJoin.isMyTodo(currentUserId: Long, currentUserName: String): Boolean
 }
 
 fun TodoStepJoin.only(action: StepAction) = copy(
-    enableLock           = action == StepAction.LOCK,
-    enableUnlock         = action == StepAction.UNLOCK,
-    enableColock         = action == StepAction.COLOCK,
-    enableReleaseColock  = action == StepAction.RELEASE_COLOCK,
-    enableEndJob         = action == StepAction.END_JOB
+    enableLock = action == StepAction.LOCK,
+    enableUnlock = action == StepAction.UNLOCK,
+    enableColock = action == StepAction.COLOCK,
+    enableReleaseColock = action == StepAction.RELEASE_COLOCK,
+    enableEndJob = action == StepAction.END_JOB
 )
 
 // WorkflowStep 扩展:判定是否包含任何硬件相关动作

+ 16 - 5
data/src/main/java/com/grkj/data/model/vo/TodoItemVo.kt

@@ -1,6 +1,7 @@
 package com.grkj.data.model.vo
 
 import com.grkj.data.enums.OperationTypeEnum
+import com.grkj.data.enums.TodoStatusEnum
 
 /**
  * 作业票待办项实体类
@@ -22,6 +23,11 @@ class TodoItemVo {
      */
     var ticketName: String? = ""
 
+    /**
+     * 作业票装填
+     */
+    var ticketStatus: String? = ""
+
     /**
      * 当前步骤 ID,对应 is_job_ticket_step.step_id
      */
@@ -57,6 +63,11 @@ class TodoItemVo {
      */
     var todoType: OperationTypeEnum = OperationTypeEnum.UNKNOWN
 
+    /**
+     * 待办状态
+     */
+    var todoStatus: TodoStatusEnum = TodoStatusEnum.WAIT
+
     /**
      * 当前步骤状态(0未执行 1已完成)
      */
@@ -75,17 +86,17 @@ class TodoItemVo {
     /**
      * 共锁人状态
      */
-    var colockerStatus:List<String> = mutableListOf()
+    var colockerStatus: List<String> = mutableListOf()
 
     /**
-     * 分组名称
+     * 点位状态
      */
-    var groupName: String? = null
+    var pointStatus: List<String> = mutableListOf()
 
     /**
-     * 待办状态(待处理 待执行 已处理)
+     * 分组名称
      */
-    var todoStatus: String = ""
+    var groupName: String? = null
 
     /**
      * 创建时间(用于排序)

+ 216 - 33
data/src/main/java/com/grkj/data/repository/impl/standard/JobTicketRepository.kt

@@ -11,8 +11,10 @@ import com.grkj.data.enums.CommonDictDataEnum
 import com.grkj.data.enums.IsolationPointPowerTypeEnum
 import com.grkj.data.enums.JobTicketStatusEnum
 import com.grkj.data.enums.NextJobPrompt
+import com.grkj.data.enums.OperationTypeEnum
 import com.grkj.data.enums.RoleEnum
 import com.grkj.data.enums.StepAction
+import com.grkj.data.enums.TodoStatusEnum
 import com.grkj.data.model.dos.IsJobTicket
 import com.grkj.data.model.dos.IsJobTicketGroup
 import com.grkj.data.model.dos.IsJobTicketKey
@@ -20,7 +22,6 @@ import com.grkj.data.model.dos.IsJobTicketLock
 import com.grkj.data.model.dos.IsJobTicketPoints
 import com.grkj.data.model.dos.IsJobTicketStep
 import com.grkj.data.model.dos.IsJobTicketUser
-import com.grkj.data.model.dos.WorkflowStep
 import com.grkj.data.model.local.TodoStepJoin
 import com.grkj.data.model.local.isMyTodo
 import com.grkj.data.model.local.only
@@ -38,7 +39,6 @@ import com.grkj.data.model.vo.JobTicketGroupDataVo
 import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.JobUserVo
 import com.grkj.data.model.vo.LockedPointVo
-import com.grkj.data.model.vo.TodoItemVo
 import com.grkj.data.repository.BaseRepository
 import com.grkj.data.repository.IJobTicketRepository
 import com.sik.sikcore.data.BeanUtils
@@ -136,6 +136,10 @@ class JobTicketRepository @Inject constructor(
                 isJobTicketStep.stepIndex = workflowStep.stepIndex
                 isJobTicketStep.stepContent = workflowStep.stepTitle
                 isJobTicketStep.androidStepContent = workflowStep.stepTitleShort
+                isJobTicketStep.createTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+                isJobTicketStep.updateTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
                 add(isJobTicketStep)
             }
         }
@@ -235,6 +239,10 @@ class JobTicketRepository @Inject constructor(
                 isJobTicketStep.stepIndex = workflowStep.stepIndex
                 isJobTicketStep.stepContent = workflowStep.stepTitle
                 isJobTicketStep.androidStepContent = workflowStep.stepTitleShort
+                isJobTicketStep.createTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+                isJobTicketStep.updateTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
                 add(isJobTicketStep)
             }
         }
@@ -261,9 +269,11 @@ class JobTicketRepository @Inject constructor(
         val lockers = jobTicketDao.getTicketUsersByTicketIds(tickets.map { it.ticketId })
             .filter { it.userRole == RoleEnum.JTLOCKER.roleKey }
         // 7. 获取作业票的共锁人数据
-        var colockers = jobTicketDao.getTicketUsersByTicketIds(tickets.map { it.ticketId })
+        val colockers = jobTicketDao.getTicketUsersByTicketIds(tickets.map { it.ticketId })
             .filter { it.userRole == RoleEnum.JTCOLOCKER.roleKey }
-        // 8. 构建待办项 TodoStepJoin
+        // 8. 获取点位数据
+        val pointDatas = jobTicketDao.getTicketPointsByTicketIds(tickets.map { it.ticketId })
+        // 9. 构建待办项 TodoStepJoin
         val todoList = assembleTodoStepJoins(
             tickets,
             stepInstances,
@@ -271,6 +281,7 @@ class JobTicketRepository @Inject constructor(
             groups,
             lockers,
             colockers,
+            pointDatas,
             userId,
             userName
         )
@@ -288,6 +299,7 @@ class JobTicketRepository @Inject constructor(
         groups: List<IsJobTicketGroup>,
         lockers: List<IsJobTicketUserDataVo>,
         colockers: List<IsJobTicketUserDataVo>,
+        pointDatas: List<JobPointVo>,
         userId: Long,
         userName: String
     ): List<TodoStepJoin> {
@@ -310,14 +322,15 @@ class JobTicketRepository @Inject constructor(
 
                 /** 工具:给待办挂上前置链并收集 **/
                 fun collect(join: TodoStepJoin) {
-                    if (previousStepJoin.isNotEmpty()) join.previousTodoStepJoin = previousStepJoin.toList()
+                    if (previousStepJoin.isNotEmpty()) join.previousTodoStepJoin =
+                        previousStepJoin.toList()
                     stepJoins += join
                 }
 
                 /* 1️⃣ Lock / Unlock(按 group 拆) */
                 if (stepDef.enableLock || stepDef.enableUnlock) {
-                    val action  = if (stepDef.enableLock) StepAction.LOCK else StepAction.UNLOCK
-                    val type    = if (stepDef.enableLock) 0 else 1          // key.ticketType
+                    val action = if (stepDef.enableLock) StepAction.LOCK else StepAction.UNLOCK
+                    val type = if (stepDef.enableLock) 0 else 1          // key.ticketType
                     val keysOfStep = keyRecords.filter {                     // 全部钥匙记录
                         it.ticketId == ticketId && it.ticketType == type && it.delFlag == "0"
                     }
@@ -330,7 +343,7 @@ class JobTicketRepository @Inject constructor(
                             // 该 group 还没取钥匙 → 占位 Todo
                             listOf(
                                 buildTodoStepJoin(
-                                    stepDef, ticket, stepDef, lockerList, colockerList,
+                                    stepDef, ticket, stepDef, lockerList, colockerList, pointDatas,
                                     g.id, g.groupName, null
                                 ).only(action)
                             )
@@ -338,7 +351,7 @@ class JobTicketRepository @Inject constructor(
                             // 该 group 已有钥匙记录 → 每把钥匙一条 Todo
                             keysOfGroup.map { k ->
                                 buildTodoStepJoin(
-                                    stepDef, ticket, stepDef, lockerList, colockerList,
+                                    stepDef, ticket, stepDef, lockerList, colockerList, pointDatas,
                                     k.groupId, g.groupName, k
                                 ).only(action)
                             }
@@ -352,7 +365,7 @@ class JobTicketRepository @Inject constructor(
                 if (stepDef.enableColock) {
                     collect(
                         buildTodoStepJoin(
-                            stepDef, ticket, stepDef, lockerList, colockerList,
+                            stepDef, ticket, stepDef, lockerList, colockerList, pointDatas,
                             null, "", null
                         ).only(StepAction.COLOCK)
                     )
@@ -361,19 +374,30 @@ class JobTicketRepository @Inject constructor(
                 if (stepDef.enableReleaseColock) {
                     collect(
                         buildTodoStepJoin(
-                            stepDef, ticket, stepDef, lockerList, colockerList,
+                            stepDef, ticket, stepDef, lockerList, colockerList, pointDatas,
                             null, "", null
                         ).only(StepAction.RELEASE_COLOCK)
                     )
                 }
 
+                if (stepDef.enableEndJob) {
+                    collect(
+                        buildTodoStepJoin(
+                            stepDef, ticket, stepDef, lockerList, colockerList, pointDatas,
+                            null, "", null
+                        ).only(StepAction.END_JOB)
+                    )
+                }
+
                 /* 3️⃣ 普通确认 / End‑Job */
-                val needConfirm = !stepDef.hasAnyHardwareOperationFunction() || stepDef.enableEndJob
+                val needConfirm =
+                    !stepDef.hasAnyHardwareOperationFunction() || stepDef.confirmType == 0
                 if (needConfirm) {
-                    val action = if (stepDef.enableEndJob) StepAction.END_JOB else StepAction.CONFIRM
                     collect(
-                        buildTodoStepJoin(stepDef, ticket, stepDef, lockerList, colockerList,
-                            null, "", null).only(action) // ⚠️
+                        buildTodoStepJoin(
+                            stepDef, ticket, stepDef, lockerList, colockerList, pointDatas,
+                            null, "", null
+                        ).only(StepAction.CONFIRM) // ⚠️
                     )
                 }
 
@@ -398,6 +422,7 @@ class JobTicketRepository @Inject constructor(
         workflowStep: IsJobTicketStep,
         lockers: List<IsJobTicketUserDataVo>,
         colockers: List<IsJobTicketUserDataVo>,
+        points: List<JobPointVo>,
         groupId: Long?,
         groupName: String?,
         key: IsJobTicketKey?,
@@ -412,6 +437,7 @@ class JobTicketRepository @Inject constructor(
             workflowStepId = workflowStep.stepId,
 
             ticketName = ticket.ticketName,
+            ticketStatus = ticket.ticketStatus,
             createBy = ticket.createBy,
             ticketStartTime = ticket.ticketStartTime,
 
@@ -434,8 +460,10 @@ class JobTicketRepository @Inject constructor(
             lockerUserId = lockers.find { it.groupId == groupId }?.userId,
             colockerUserId = colockers.map { it.userId },
             colockerStatus = colockers.mapNotNull { it.jobStatus },
+            pointStatus = points.mapNotNull { it.pointStatus },
             keyStatus = getKeyStatusFromKey(key),
-            previousTodoStepJoin = previousTodoStepJoin
+            previousTodoStepJoin = previousTodoStepJoin,
+            todoStatus = TodoStatusEnum.WAIT
         ).also {
             // 将 groupId 写入(你如果实体字段没写,需要自己加上)
             it.groupId = groupId
@@ -485,23 +513,48 @@ class JobTicketRepository @Inject constructor(
         val grouped = todoList.filter { it.isMyTodo(userId, userName) }.groupBy { it.ticketId }
 
         for ((_, steps) in grouped) {
-            val doneSteps = steps.filter { it.stepStatus == "1" }
-            val pendingSteps = steps.filter { it.stepStatus == "0" }
+            var doneSteps =
+                steps.filter {
+                    it.stepStatus == "1" && (!it.enableEndJob && it.ticketStatus in listOf(
+                        JobTicketStatusEnum.SELECT_MEMBER.status,
+                        JobTicketStatusEnum.LOCKING.status,
+                        JobTicketStatusEnum.COLOCKING.status,
+                        JobTicketStatusEnum.UNLOCKING.status,
+                        JobTicketStatusEnum.PROGRESSING.status,
+                    ) || (it.ticketStatus == JobTicketStatusEnum.FINISHED.status) && it.enableEndJob)
+                }
+            val pendingSteps =
+                steps.filter {
+                    it.stepStatus == "0" || (it.stepStatus == "1" && it.enableEndJob && it.ticketStatus in listOf(
+                        JobTicketStatusEnum.SELECT_MEMBER.status,
+                        JobTicketStatusEnum.LOCKING.status,
+                        JobTicketStatusEnum.COLOCKING.status,
+                        JobTicketStatusEnum.UNLOCKING.status,
+                        JobTicketStatusEnum.PROGRESSING.status,
+                    ))
+                }
 
             /* —— 当前步骤 → todoData —— */
-            val todoStep = pendingSteps.filter { pendingStep ->
-                todoList.filter { it.stepStatus == "0" }
-                    .minByOrNull { it.stepIndex }?.stepIndex == pendingStep.stepIndex }
-            todoStep.let { tempTodo.addAll(it) }
+            val todoStep = pendingSteps
+                .filter { pendingStep ->
+                    steps.filter { it.stepStatus == "0" }
+                        .minByOrNull { it.stepIndex }?.stepIndex == pendingStep.stepIndex || (steps.none {
+                        pendingStep.stepStatus == "0"
+                    } && pendingStep.enableEndJob)
+                }.findPredecessors()
+            tempTodo += todoStep.second
+            doneSteps = doneSteps.toMutableList().apply {
+                addAll(todoStep.third)
+            }
 
             /* —— 其它待办 → waitData —— */
-            val otherPending = pendingSteps.filter { it !in todoStep }
+            val otherPending = pendingSteps.filter { it !in (todoStep.second + todoStep.third) }
 
             /* ① 按 (stepIndex, actionKey) 分批 —— */
             val byBatch = otherPending
                 .groupBy { it.stepIndex to actionKey(it) }             // <─ 新增动作维度
                 .toSortedMap(
-                    compareBy<Pair<Int, String>> { it.first }          // stepIndex 升序
+                    compareBy<Pair<Int, Int>> { it.first }          // stepIndex 升序
                         .thenBy { it.second }                          // actionKey 字典序
                 )
 
@@ -512,7 +565,7 @@ class JobTicketRepository @Inject constructor(
 
                 val action = actionKey(batchList[0])  // 同批次动作一致,取任意一条判断即可
 
-                if (action == "LOCK" || action == "UNLOCK") {
+                if (action == 1 || action == 4) {
                     // ✅ 上锁/解锁 → 同一 stepIndex 下多个 group 要全部保留
                     tempWait += batchList
                 } else {
@@ -525,19 +578,140 @@ class JobTicketRepository @Inject constructor(
 
             tempDone += doneSteps
         }
+        tempTodo.forEach {
+            it.todoStatus = TodoStatusEnum.TODO
+        }
+        tempWait.forEach {
+            it.todoStatus = TodoStatusEnum.WAIT
+        }
+        tempDone.forEach {
+            it.todoStatus = TodoStatusEnum.DONE
+        }
         return tempTodo.size + tempWait.size
     }
 
+    /**
+     * 获取待办类型
+     */
+    private fun TodoStepJoin.getTodoType(): OperationTypeEnum {
+        val isCreator = createBy == MainDomainData.userInfo?.userName
+        return OperationTypeEnum.fromJoin(this, isCreator)
+    }
+
+    /**
+     * 处理前置
+     */
+    private fun List<TodoStepJoin>.findPredecessors(): Triple<List<TodoStepJoin>, List<TodoStepJoin>, List<TodoStepJoin>> {
+        return when {
+            any { it.enableLock } && any { it.enableColock } -> {
+                if (this.any { it.pointStatus.any { it == "0" } }) {
+                    Triple(filter { it.enableColock }, filter { it.enableLock }, mutableListOf())
+                } else if (this.any { it.pointStatus.all { it == "1" } } && this.any { it.colockerStatus.any { it == "0" } }) {
+                    Triple(
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableColock },
+                        filter { it.enableLock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableLock || it.enableColock })
+                }
+            }
+
+            any { it.enableColock } && any { it.enableReleaseColock } -> {
+                if (this.any { it.colockerStatus.any { it == "0" } }) {
+                    Triple(
+                        filter { it.enableReleaseColock },
+                        filter { it.enableColock },
+                        mutableListOf()
+                    )
+                } else if (this.all { it.colockerStatus.all { it == "1" } }) {
+                    Triple(
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableReleaseColock },
+                        filter { it.enableColock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableReleaseColock || it.enableColock })
+                }
+            }
+
+            any { it.enableReleaseColock } && any { it.enableUnlock } && any { it.enableEndJob } -> {
+                if (this.any { it.colockerStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock },
+                        mutableListOf()
+                    )
+                } else if (this.all { it.colockerStatus.all { it == "2" } } && this.any { it.pointStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock })
+                } else if (this.all { it.stepStatus == "0" } && this.all { it.pointStatus.all { it == "2" } }) {
+                    Triple(
+                        filter { it.enableEndJob },
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock || it.enableReleaseColock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.enableEndJob },
+                        filter { it.enableUnlock || it.enableReleaseColock || it.getTodoType() == OperationTypeEnum.CONFIRM }
+                    )
+                }
+            }
+
+            any { it.enableReleaseColock } && any { it.enableUnlock } -> {
+                if (this.any { it.colockerStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock },
+                        mutableListOf()
+                    )
+                } else if (this.all { it.colockerStatus.all { it == "2" } } && this.any { it.pointStatus.any { it != "2" } }) {
+                    Triple(
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock },
+                        filter { it.enableReleaseColock })
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.getTodoType() == OperationTypeEnum.CONFIRM },
+                        filter { it.enableUnlock || it.enableReleaseColock })
+                }
+            }
+
+            any { it.enableUnlock } && any { it.enableEndJob } -> {
+                if (this.any { it.pointStatus.any { it != "2" } }) {
+                    Triple(filter { it.enableEndJob }, filter { it.enableUnlock }, mutableListOf())
+                } else {
+                    Triple(
+                        mutableListOf(),
+                        filter { it.enableEndJob },
+                        filter { it.enableUnlock })
+                }
+            }
+
+            else -> Triple(
+                filter { it.getTodoType() == OperationTypeEnum.CONFIRM }, this, mutableListOf()
+            )
+        }
+    }
+
     /* -------------------------------------------------------------
      * 根据唯一的 enableXXX 返回动作标识
      * ----------------------------------------------------------- */
-    private fun actionKey(item: TodoStepJoin): String = when {
-        item.enableLock           -> "LOCK"
-        item.enableUnlock         -> "UNLOCK"
-        item.enableColock         -> "COLOCK"
-        item.enableReleaseColock  -> "RELEASE_COLOCK"
-        item.enableEndJob         -> "END_JOB"
-        else                      -> "CONFIRM"
+    private fun actionKey(item: TodoStepJoin): Int = when {
+        item.enableLock -> 1
+        item.enableUnlock -> 4
+        item.enableColock -> 2
+        item.enableReleaseColock -> 3
+        item.enableEndJob -> 6
+        else -> 5
     }
 
     override fun updateCoincideToUnLock(ticketDetail: TicketDetailRes, groupId: Long) {
@@ -671,7 +845,8 @@ class JobTicketRepository @Inject constructor(
             //如果是自动确认并且没有操作的功能,则更新步骤
             if (stepDataVo?.confirmType != 0 && stepDataVo?.hasAnyOperationFunction() == false) {
                 stepDataVo.stepStatus = "1"
-                stepDataVo.updateTime = TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+                stepDataVo.updateTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
                 BeanUtils.copyProperties(
                     stepDataVo, IsJobTicketStep::class.java
                 )?.let { jobTicketStep ->
@@ -1126,6 +1301,10 @@ class JobTicketRepository @Inject constructor(
                 isJobTicketStep.stepIndex = workflowStep.stepIndex
                 isJobTicketStep.stepContent = workflowStep.stepTitle
                 isJobTicketStep.androidStepContent = workflowStep.stepTitleShort
+                isJobTicketStep.createTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+                isJobTicketStep.updateTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
                 add(isJobTicketStep)
             }
         }
@@ -1209,6 +1388,10 @@ class JobTicketRepository @Inject constructor(
                 isJobTicketStep.stepIndex = workflowStep.stepIndex
                 isJobTicketStep.stepContent = workflowStep.stepTitle
                 isJobTicketStep.androidStepContent = workflowStep.stepTitleShort
+                isJobTicketStep.createTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
+                isJobTicketStep.updateTime =
+                    TimeUtils.nowString(TimeUtils.DEFAULT_DATE_HOUR_MIN_SEC_FORMAT)
                 add(isJobTicketStep)
             }
         }

+ 1 - 1
gradle/libs.versions.toml

@@ -11,7 +11,7 @@ material = "1.10.0"
 activity = "1.8.0"
 constraintlayout = "2.1.4"
 jetbrainsKotlinJvm = "2.0.21"
-sikextension = "1.1.63"
+sikextension = "1.1.64"
 sikcamera = "1.0.11"
 sikcronjob = "1.0.3"
 sikfontmanager = "1.0.2"

+ 1 - 0
ui-base/build.gradle.kts

@@ -67,6 +67,7 @@ dependencies {
     api("io.github.scwang90:refresh-footer-classics:3.0.0-alpha")
     api("com.google.android.flexbox:flexbox:3.0.0")
     api("com.github.liangjingkanji:StateLayout:1.4.2")
+    api("com.github.ycuwq:DatePicker:1.3.4")
     api("com.github.bingoogolapple.BGABadgeView-Android:api:1.2.0")
     kapt("com.github.bingoogolapple.BGABadgeView-Android:compiler:1.2.0")
 //    api("com.licheedev:android-serialport:2.1.5")

+ 1 - 3
ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt

@@ -721,9 +721,7 @@ object BleBusinessManager {
                                 val ticketPoints =
                                     RepositoryManager.jobTicketRepo.getTicketDetail(data.taskCode?.toLong()!!)?.ticketPointsVOList
                                 var currentWorkflowStep = ticketStepDataVo
-                                if ((currentWorkflowStep?.enableLock == true && currentWorkflowStep.enableColock == true) ||
-                                    (currentWorkflowStep?.enableReleaseColock == true && currentWorkflowStep.enableUnlock == true ||
-                                            ticketPoints?.all { it.pointStatus == ticketPoints.firstOrNull()?.pointStatus } == false)
+                                if ((currentWorkflowStep?.enableLock == true && currentWorkflowStep.enableColock) || (currentWorkflowStep?.enableReleaseColock == true && currentWorkflowStep.enableUnlock || ticketPoints?.all { it.pointStatus == ticketPoints.firstOrNull()?.pointStatus } == false)
                                 ) {
                                     logger.info("当前上锁和共锁或者解锁和解除共锁在同一步骤,或者所有点位的状态不统一,不更新步骤状态")
                                 } else {

+ 155 - 0
ui-base/src/main/java/com/grkj/ui_base/dialog/WheelDateRangePickerDialog.kt

@@ -0,0 +1,155 @@
+package com.grkj.ui_base.dialog
+
+import android.icu.text.SimpleDateFormat
+import android.icu.util.Calendar
+import android.view.View
+import com.grkj.ui_base.R
+import com.grkj.ui_base.databinding.DialogWheelDateRangeBinding
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.extension.tip
+import com.kongzue.dialogx.dialogs.BottomDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import com.kongzue.dialogx.interfaces.OnBindView
+import com.sik.sikcore.extension.setDebouncedClickListener
+import java.util.Locale
+
+class WheelDateRangePickerDialog(
+    val startDate: String,
+    val endDate: String,
+    val onConfirm: (startDate: String, endDate: String) -> Unit
+) : OnBindView<BottomDialog>(R.layout.dialog_wheel_date_range) {
+
+    /** 当前是否选择开始日期 */
+    private var isStartDateSelected = true
+
+    /** 当前展示的日历对象 */
+    private var currentCalendar: Calendar = Calendar.getInstance()
+
+    private lateinit var binding: DialogWheelDateRangeBinding
+
+    override fun onBind(dialog: BottomDialog, v: View) {
+        binding = DialogWheelDateRangeBinding.bind(v)
+        dialog.setAllowInterceptTouch(false)
+        dialog.setMaskColor(CommonUtils.getColor(R.color.scrim))
+
+        var selectedStart = startDate
+        var selectedEnd = endDate
+
+        val today = Calendar.getInstance()
+        val tempStartDate: List<String>
+        val tempEndDate: List<String>
+
+        // 初始化开始/结束日期:若为空则使用今天日期
+        if (selectedStart.isNotEmpty() && selectedEnd.isNotEmpty()) {
+            tempStartDate = selectedStart.split("-")
+            tempEndDate = selectedEnd.split("-")
+        } else {
+            val y = today.get(Calendar.YEAR)
+            val m = today.get(Calendar.MONTH) + 1
+            val d = today.get(Calendar.DAY_OF_MONTH)
+            tempStartDate = listOf(y.toString(), m.toString(), (d - 1).toString())
+            tempEndDate = listOf(y.toString(), m.toString(), d.toString())
+            selectedStart = "%04d-%02d-%02d".format(y, m, d)
+            selectedEnd = selectedStart
+        }
+
+        // 设置初始选中日期到 datePicker
+        currentCalendar.set(
+            tempStartDate[0].toInt(),
+            tempStartDate[1].toInt() - 1,
+            tempStartDate[2].toInt()
+        )
+
+        binding.datePicker.setDate(
+            tempStartDate[0].toInt(),
+            tempStartDate[1].toInt(),
+            tempStartDate[2].toInt()
+        )
+
+        // 显示开始/结束时间文本
+        binding.startDate.text = CommonUtils.getStr(
+            R.string.date,
+            tempStartDate[0].toInt(),
+            tempStartDate[1].toInt(),
+            tempStartDate[2].toInt()
+        )
+
+        binding.endDate.text = CommonUtils.getStr(
+            R.string.date,
+            tempEndDate[0].toInt(),
+            tempEndDate[1].toInt(),
+            tempEndDate[2].toInt()
+        )
+
+        // 切换选中态样式
+        checkDateSelected()
+
+        // 日期选择监听,更新当前选中项对应的日期文本和变量
+        binding.datePicker.setOnDateSelectedListener { year, month, day ->
+            val text = CommonUtils.getStr(R.string.date, year, month, day)
+            val dateStr = "%04d-%02d-%02d".format(year, month, day)
+            if (isStartDateSelected) {
+                binding.startDate.text = text
+                selectedStart = dateStr
+            } else {
+                binding.endDate.text = text
+                selectedEnd = dateStr
+            }
+        }
+
+        // 切换至开始时间选择,切换样式并回显日期
+        binding.startDateLayout.setDebouncedClickListener {
+            isStartDateSelected = true
+            checkDateSelected()
+            val parts = selectedStart.split("-").map { it.toInt() }
+            binding.datePicker.setDate(parts[0], parts[1], parts[2])
+        }
+
+        // 切换至结束时间选择,切换样式并回显日期
+        binding.endDateLayout.setDebouncedClickListener {
+            isStartDateSelected = false
+            checkDateSelected()
+            val parts = selectedEnd.split("-").map { it.toInt() }
+            binding.datePicker.setDate(parts[0], parts[1], parts[2])
+        }
+
+        // 确认按钮:将当前选中的开始和结束时间回传
+        binding.confirm.setDebouncedClickListener {
+            val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+            val startCal = Calendar.getInstance().apply { time = sdf.parse(selectedStart) }
+            val endCal = Calendar.getInstance().apply { time = sdf.parse(selectedEnd) }
+
+            if (startCal.after(endCal)) {
+                PopTip.build().tip(R.string.error_date_range_invalid)
+                return@setDebouncedClickListener
+            }
+
+            onConfirm(selectedStart, selectedEnd)
+            dialog.dismiss()
+        }
+    }
+
+    /**
+     * 根据当前选中的时间类型,更新 UI 状态(start 或 end 高亮)
+     */
+    private fun checkDateSelected() {
+        binding.startDateTv.isSelected = isStartDateSelected
+        binding.startDate.isSelected = isStartDateSelected
+        binding.endDateTv.isSelected = !isStartDateSelected
+        binding.endDate.isSelected = !isStartDateSelected
+    }
+
+    companion object {
+        /**
+         * 显示弹窗入口
+         */
+        @JvmStatic
+        fun show(
+            startDate: String = "",
+            endDate: String = "",
+            onConfirm: (String, String) -> Unit
+        ) {
+            BottomDialog.show(WheelDateRangePickerDialog(startDate, endDate, onConfirm))
+        }
+    }
+}

+ 5 - 0
ui-base/src/main/res/color/common_text_color_selector.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@color/main_color" android:state_selected="true" />
+    <item android:color="@color/black" />
+</selector>

+ 96 - 0
ui-base/src/main/res/layout-land/dialog_wheel_date_range.xml

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:id="@+id/root"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@android:color/white"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <LinearLayout
+                android:id="@+id/start_date_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/start_date_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/start"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/start_date"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size_small"
+                    tools:text="2025年7月27日" />
+            </LinearLayout>
+
+            <View
+                android:layout_width="2dp"
+                android:layout_height="match_parent"
+                android:layout_marginHorizontal="@dimen/common_spacing_2x"
+                android:background="@color/black20" />
+
+            <LinearLayout
+                android:id="@+id/end_date_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/end_date_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/end"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/end_date"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size_small"
+                    tools:text="2025年7月28日" />
+            </LinearLayout>
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="1dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/main_color"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <com.ycuwq.datepicker.date.DatePicker
+            android:id="@+id/datePicker"
+            android:layout_width="match_parent"
+            android:layout_height="340dp"
+            app:itemTextSize="@dimen/common_text_size_small"
+            app:selectedTextSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 96 - 0
ui-base/src/main/res/layout/dialog_wheel_date_range.xml

@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <LinearLayout
+        android:id="@+id/root"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@android:color/white"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <LinearLayout
+                android:id="@+id/start_date_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/start_date_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/start"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/start_date"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size_small"
+                    tools:text="2025年7月27日" />
+            </LinearLayout>
+
+            <View
+                android:layout_width="2dp"
+                android:layout_height="match_parent"
+                android:layout_marginHorizontal="@dimen/common_spacing_2x"
+                android:background="@color/black20" />
+
+            <LinearLayout
+                android:id="@+id/end_date_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/end_date_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/end"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size"
+                    android:textStyle="bold" />
+
+                <TextView
+                    android:id="@+id/end_date"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/common_text_color_selector"
+                    android:textSize="@dimen/common_text_size_small"
+                    tools:text="2025年7月28日" />
+            </LinearLayout>
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="1dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/main_color"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <com.ycuwq.datepicker.date.DatePicker
+            android:id="@+id/datePicker"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            app:itemTextSize="@dimen/common_text_size_small"
+            app:selectedTextSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 4 - 0
ui-base/src/main/res/values-en/strings.xml

@@ -375,4 +375,8 @@
     <string name="please_select_colocker">Please select Co-Locker</string>
     <string name="no_data">No Data</string>
     <string name="workstation_already_exists">Workstation already exists</string>
+    <string name="start">Start</string>
+    <string name="end">结束</string>
+    <string name="date">%2$d/%3$d/%1$d</string>
+    <string name="error_date_range_invalid">The start time cannot be later than the end time</string>
 </resources>

+ 4 - 0
ui-base/src/main/res/values-zh/strings.xml

@@ -375,4 +375,8 @@
     <string name="please_select_colocker">请选择共锁人</string>
     <string name="no_data">暂无数据</string>
     <string name="workstation_already_exists">区域已存在</string>
+    <string name="start">开始</string>
+    <string name="end">结束</string>
+    <string name="date">%1$d年%2$d月%3$d日</string>
+    <string name="error_date_range_invalid">开始时间不能晚于结束时间</string>
 </resources>

+ 4 - 0
ui-base/src/main/res/values/strings.xml

@@ -375,4 +375,8 @@
     <string name="please_select_colocker">请选择共锁人</string>
     <string name="no_data">暂无数据</string>
     <string name="workstation_already_exists">区域已存在</string>
+    <string name="start">开始</string>
+    <string name="end">结束</string>
+    <string name="date">%1$d年%2$d月%3$d日</string>
+    <string name="error_date_range_invalid">开始时间不能晚于结束时间</string>
 </resources>