Kaynağa Gözat

refactor(更新)
- SOP作业详情完成

周文健 5 ay önce
ebeveyn
işleme
ddfc43392a
27 değiştirilmiş dosya ile 2166 ekleme ve 61 silme
  1. 7 0
      app/src/main/java/com/grkj/iscs/common/DataTransferConstants.kt
  2. 442 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditJobFragment.kt
  3. 354 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopJobFragment.kt
  4. 18 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt
  5. 156 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageFragment.kt
  6. 1 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageHomeFragment.kt
  7. 1 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/SopManageFragment.kt
  8. 46 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobManageViewModel.kt
  9. 1 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobViewModel.kt
  10. 58 1
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopJobViewModel.kt
  11. 378 0
      app/src/main/res/layout/fragment_edit_sop_job.xml
  12. 125 0
      app/src/main/res/layout/fragment_job_execute.xml
  13. 125 0
      app/src/main/res/layout/fragment_job_manage.xml
  14. 44 0
      app/src/main/res/layout/item_job_manage.xml
  15. 52 0
      app/src/main/res/navigation/nav_job_manage.xml
  16. 8 0
      app/src/main/res/values-en/strings.xml
  17. 8 0
      app/src/main/res/values-zh/strings.xml
  18. 8 0
      app/src/main/res/values/strings.xml
  19. 2 2
      data/src/main/java/com/grkj/data/dao/IsSopDao.kt
  20. 108 1
      data/src/main/java/com/grkj/data/dao/JobTicketDao.kt
  21. 25 0
      data/src/main/java/com/grkj/data/enums/JobTicketStatusEnums.kt
  22. 21 0
      data/src/main/java/com/grkj/data/model/vo/JobTicketManageVo.kt
  23. 48 2
      data/src/main/java/com/grkj/data/repository/IJobTicketRepository.kt
  24. 0 13
      data/src/main/java/com/grkj/data/repository/ITicketRepository.kt
  25. 119 3
      data/src/main/java/com/grkj/data/repository/impl/JobTicketRepository.kt
  26. 0 25
      data/src/main/java/com/grkj/data/repository/impl/TicketRepository.kt
  27. 11 11
      ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt

+ 7 - 0
app/src/main/java/com/grkj/iscs/common/DataTransferConstants.kt

@@ -36,4 +36,11 @@ object DataTransferConstants {
      * 修改SOP的SopId
      */
     const val KEY_EDIT_SOP_SOP_ID = "key_edit_sop_sop_id"
+
+    /**
+     * 作业id用于编辑
+     * SOP作业需要再携带SopId
+     */
+    const val KEY_JOB_TICKET_ID = "key_job_ticket_id"
+    const val KEY_EDIT_SOP_JOB_SOP_ID = "key_edit_sop_job_sop_id"
 }

+ 442 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditJobFragment.kt

@@ -0,0 +1,442 @@
+package com.grkj.iscs.features.main.fragment.job_manage
+
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.ViewModelProvider
+import com.drake.brv.BindingAdapter
+import com.drake.brv.utils.grid
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.enums.LockModeEnum
+import com.grkj.data.enums.LockStepEnum
+import com.grkj.data.model.vo.PointManageVo
+import com.grkj.data.model.vo.UserManageVo
+import com.grkj.iscs.R
+import com.grkj.iscs.common.DataTransferConstants
+import com.grkj.iscs.databinding.FragmentCreateJobBinding
+import com.grkj.iscs.databinding.ItemSelectMemberBinding
+import com.grkj.iscs.databinding.ItemSelectPointBinding
+import com.grkj.iscs.features.main.dialog.TextDropDownDialog
+import com.grkj.iscs.features.main.viewmodel.job_manage.JobViewModel
+import com.grkj.iscs.utils.getLockModeStr
+import com.grkj.iscs.utils.getLockModeType
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.thread.ThreadUtils
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
+import kotlinx.coroutines.withContext
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * 编辑作业
+ */
+class EditJobFragment : BaseFragment<FragmentCreateJobBinding>() {
+    private val viewModel: JobViewModel by lazy { ViewModelProvider(this)[JobViewModel::class] }
+    private var selectedLockMode: String? = null
+    private var selectedWorkstationId: Long? = null
+    private var selectedPointData: List<PointManageVo> = mutableListOf()
+    private var selectedLockerData: List<UserManageVo> = mutableListOf()
+    private var selectedColockerData: List<UserManageVo> = mutableListOf()
+    private lateinit var textDropDownDialog: TextDropDownDialog
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_create_job
+    }
+
+    override fun initView() {
+        textDropDownDialog = TextDropDownDialog(requireContext())
+        textDropDownDialog.setWidthAsAnchorView(true)
+        binding.back.setDebouncedClickListener {
+            TipDialog.show(
+                title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_hint).toString(),
+                msg = CommonUtils.getStr(R.string.not_save_tip).toString(),
+                dialogType = TipDialog.DialogType.ERROR,
+                countDownTime = 10,
+                onConfirmClick = {
+                    navController.popBackStack()
+                })
+        }
+        binding.cancel.setDebouncedClickListener {
+            TipDialog.show(
+                title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_hint).toString(),
+                msg = CommonUtils.getStr(R.string.not_save_tip).toString(),
+                dialogType = TipDialog.DialogType.ERROR,
+                countDownTime = 10,
+                onConfirmClick = {
+                    navController.popBackStack()
+                })
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_confirm).toString(),
+                    msg = CommonUtils.getStr(
+                        R.string.job_save_tip,
+                        args = listOf<String>(binding.jobNameEt.text.toString()).toTypedArray()
+                    ).toString(),
+                    dialogType = TipDialog.DialogType.INFO,
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        ThreadUtils.runOnIO {
+                            val sopId = if (binding.saveSop.isChecked) {
+                                async { saveSop() }
+                            } else {
+                                null
+                            }
+                            withContext(Dispatchers.Main) {
+                                saveJob(sopId?.await())
+                            }
+                        }
+                    })
+            }
+        }
+        binding.workstationTv.setOnClickListener {
+            setWorkstationData()
+        }
+        binding.lockModeTv.setOnClickListener {
+            setLockModeData()
+        }
+        binding.noSelectedPointLayout.setDebouncedClickListener {
+            if (selectedWorkstationId == null) {
+                PopTip.tip(R.string.please_select_job_workstation)
+                return@setDebouncedClickListener
+            }
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECT_POINT_WORKSTATION_ID,
+                    selectedWorkstationId!!
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(DataTransferConstants.KEY_SELECTED_POINT_DATA, selectedPointData)
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_TITLE_DATA,
+                    CommonUtils.getStr(R.string.create_job_title).toString()
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_ICON_DATA,
+                    R.mipmap.icon_data_manage_menu_point_manage
+                )
+            navController.navigate(R.id.action_createJobFragment_to_selectPointFragment)
+        }
+        binding.noSelectedMemberLayout.setDebouncedClickListener {
+            if (selectedWorkstationId == null) {
+                PopTip.tip(R.string.please_select_job_workstation)
+                return@setDebouncedClickListener
+            }
+            if (selectedLockMode == null) {
+                PopTip.tip(R.string.please_select_lock_mode)
+                return@setDebouncedClickListener
+            }
+            GlobalDataTempStore.getInstance().saveData(
+                DataTransferConstants.KEY_CAN_SELECT_COLOCKER, selectedLockMode?.contains(
+                    LockStepEnum.COLOCK.type.toString()
+                ) == true
+            )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECT_POINT_WORKSTATION_ID,
+                    selectedWorkstationId!!
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA, selectedLockerData)
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA,
+                    selectedColockerData
+                )
+            GlobalDataTempStore.getInstance().saveData(
+                DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA,
+                selectedColockerData
+            )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_TITLE_DATA,
+                    CommonUtils.getStr(R.string.create_job_title).toString()
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_ICON_DATA,
+                    R.mipmap.icon_data_manage_menu_point_manage
+                )
+            navController.navigate(R.id.action_createJobFragment_to_selectMemberFragment)
+        }
+        binding.selectPointTv.setDebouncedClickListener {
+            if (selectedWorkstationId == null) {
+                PopTip.tip(R.string.please_select_job_workstation)
+                return@setDebouncedClickListener
+            }
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECT_POINT_WORKSTATION_ID,
+                    selectedWorkstationId!!
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(DataTransferConstants.KEY_SELECTED_POINT_DATA, selectedPointData)
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_TITLE_DATA,
+                    CommonUtils.getStr(R.string.create_job_title).toString()
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_ICON_DATA,
+                    R.mipmap.icon_data_manage_menu_point_manage
+                )
+            navController.navigate(R.id.action_createJobFragment_to_selectPointFragment)
+        }
+        binding.selectMemberTv.setDebouncedClickListener {
+            if (selectedWorkstationId == null) {
+                PopTip.tip(R.string.please_select_job_workstation)
+                return@setDebouncedClickListener
+            }
+            if (selectedLockMode == null) {
+                PopTip.tip(R.string.please_select_lock_mode)
+                return@setDebouncedClickListener
+            }
+            GlobalDataTempStore.getInstance().saveData(
+                DataTransferConstants.KEY_CAN_SELECT_COLOCKER, selectedLockMode?.contains(
+                    LockStepEnum.COLOCK.type.toString()
+                ) == true
+            )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECT_POINT_WORKSTATION_ID,
+                    selectedWorkstationId!!
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA, selectedLockerData)
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA,
+                    selectedColockerData
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_TITLE_DATA,
+                    CommonUtils.getStr(R.string.create_job_title).toString()
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_ICON_DATA,
+                    R.mipmap.icon_data_manage_menu_point_manage
+                )
+            navController.navigate(R.id.action_createJobFragment_to_selectMemberFragment)
+        }
+        binding.pointRv.grid(6).setup {
+            addType<PointManageVo>(R.layout.item_select_point)
+            onBind {
+                onSelectedPointRVBinding(this)
+            }
+        }
+        binding.colockerRv.linear(orientation = LinearLayout.HORIZONTAL).setup {
+            addType<UserManageVo>(R.layout.item_select_member)
+            onBind {
+                onSelectedMemberRVBinding(this)
+            }
+        }
+    }
+
+    /**
+     * 保存sop
+     */
+    private suspend fun saveSop(): Long {
+        val sopId = suspendCoroutine<Long> { cont ->
+            ThreadUtils.runOnMain {
+                viewModel.saveSop(
+                    selectedPointData,
+                    selectedLockerData,
+                    selectedColockerData,
+                    selectedLockMode,
+                    selectedWorkstationId!!,
+                    binding.jobNameEt.text.toString()
+                ).observe(this@EditJobFragment) {
+                    cont.resume(it)
+                }
+            }
+        }
+        return sopId
+    }
+
+    /**
+     * 保存作业
+     */
+    private fun saveJob(sopId: Long?) {
+        viewModel.createJob(
+            selectedPointData,
+            selectedLockerData,
+            selectedColockerData,
+            selectedLockMode,
+            sopId,
+            selectedWorkstationId!!,
+            binding.jobNameEt.text.toString()
+        ).observe(this) {
+            if (it) {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed).toString(),
+                    msg = CommonUtils.getStr(R.string.job_create_succeed).toString(),
+                    dialogType = TipDialog.DialogType.SUCCESS,
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        clearData()
+                    },
+                    onCancelClick = {
+                        clearData()
+                    }
+                )
+            } else {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed).toString(),
+                    msg = CommonUtils.getStr(R.string.job_create_failed).toString(),
+                    dialogType = TipDialog.DialogType.ERROR,
+                    showConfirm = false,
+                    countDownTime = 10
+                )
+            }
+        }
+    }
+
+    private fun clearData() {
+        binding.jobNameEt.setText("")
+        selectedLockerData = listOf()
+        selectedColockerData = listOf()
+        selectedPointData = listOf()
+        selectedLockMode = null
+        selectedWorkstationId = null
+        binding.saveSop.isChecked = false
+        binding.lockModeTv.text = ""
+        binding.workstationTv.text = ""
+        binding.noSelectedMemberLayout.isVisible = true
+        binding.noSelectedPointLayout.isVisible = true
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (selectedWorkstationId == null) {
+            PopTip.tip(R.string.please_select_job_workstation)
+            return false
+        }
+        if (selectedLockMode == null) {
+            PopTip.tip(R.string.please_select_lock_mode)
+            return false
+        }
+        if (binding.jobNameEt.text.isEmpty()) {
+            PopTip.tip(R.string.please_input_job_name)
+            return false
+        }
+        if (selectedPointData.isEmpty()) {
+            PopTip.tip(R.string.please_must_select_at_least_one_point)
+            return false
+        }
+        if (selectedLockerData.isEmpty()) {
+            PopTip.tip(com.grkj.ui_base.R.string.select_locker)
+            return false
+        }
+        if (selectedLockMode?.contains(
+                LockStepEnum.COLOCK.type.toString()
+            ) == true && selectedColockerData.isEmpty()
+        ) {
+            PopTip.tip(com.grkj.ui_base.R.string.select_coloker)
+            return false
+        }
+        return true
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onSelectedMemberRVBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemSelectMemberBinding>()
+        val item = holder.getModel<UserManageVo>()
+        itemBinding.lockerName.text = item.nickName
+        itemBinding.lockerIcon.isSelected = true
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemSelectPointBinding>()
+        val item = holder.getModel<PointManageVo>()
+        itemBinding.pointName.text = item.pointName.toString()
+        itemBinding.pointIcon.setImageResource(R.mipmap.icon_data_manage_menu_point_manage)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (GlobalDataTempStore.getInstance()
+                .hasData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA)
+        ) {
+            selectedLockerData = GlobalDataTempStore.getInstance()
+                .getData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA)
+                ?.let { return@let it as List<UserManageVo> } ?: listOf()
+        }
+
+        if (GlobalDataTempStore.getInstance()
+                .hasData(DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA)
+        ) {
+            selectedColockerData = GlobalDataTempStore.getInstance()
+                .getData(DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA)
+                ?.let { return@let it as List<UserManageVo> } ?: listOf()
+        }
+
+        if (GlobalDataTempStore.getInstance()
+                .hasData(DataTransferConstants.KEY_SELECTED_POINT_DATA)
+        ) {
+            selectedPointData = GlobalDataTempStore.getInstance()
+                .getData(DataTransferConstants.KEY_SELECTED_POINT_DATA)
+                ?.let { return@let it as List<PointManageVo> } ?: listOf()
+        }
+        binding.noSelectedMemberLayout.isVisible =
+            selectedColockerData.isEmpty() && selectedLockerData.isEmpty()
+        binding.noSelectedPointLayout.isVisible = selectedPointData.isEmpty()
+        if (selectedColockerData.isNotEmpty()) {
+            binding.colockerRv.models = selectedColockerData
+        }
+        if (selectedLockerData.isNotEmpty()) {
+            val userData = selectedLockerData.first()
+            binding.lockerName.text = userData.nickName
+        }
+        if (selectedPointData.isNotEmpty()) {
+            binding.pointRv.models = selectedPointData
+        }
+    }
+
+    private fun setWorkstationData() {
+        viewModel.getWorkstationData().observe(this) {
+            textDropDownDialog.setData(
+                viewModel.workstationData.map {
+                    TextDropDownDialog.SimpleTextDropDownEntity(
+                        dataId = it.workstationId,
+                        dataText = it.workstationName
+                    )
+                })
+            textDropDownDialog.setOnItemSelectListener {
+                binding.workstationTv.text = it.getShowText()
+                selectedWorkstationId = it.getId()
+            }
+            textDropDownDialog.showPopupWindow(binding.workstationTv)
+        }
+    }
+
+    private fun setLockModeData() {
+        textDropDownDialog.setData(
+            LockModeEnum.values().map {
+                TextDropDownDialog.SimpleTextDropDownEntity(
+                    dataTag = it.getLockModeType(),
+                    dataText = it.getLockModeStr()
+                )
+            })
+        textDropDownDialog.setOnItemSelectListener {
+            binding.lockModeTv.text = it.getShowText()
+            selectedLockMode = it.getTag()
+            binding.selectColockerLayout.isVisible = selectedLockMode?.contains(
+                LockStepEnum.COLOCK.type.toString()
+            ) == true
+        }
+        textDropDownDialog.showPopupWindow(binding.lockModeTv)
+    }
+}

+ 354 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopJobFragment.kt

@@ -0,0 +1,354 @@
+package com.grkj.iscs.features.main.fragment.job_manage
+
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.NavOptions
+import com.drake.brv.BindingAdapter
+import com.drake.brv.utils.grid
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.enums.LockStepEnum
+import com.grkj.data.model.vo.PointManageVo
+import com.grkj.data.model.vo.SopManageVo
+import com.grkj.data.model.vo.UserManageVo
+import com.grkj.iscs.R
+import com.grkj.iscs.common.DataTransferConstants
+import com.grkj.iscs.databinding.FragmentCreateSopJobBinding
+import com.grkj.iscs.databinding.FragmentEditSopBinding
+import com.grkj.iscs.databinding.FragmentEditSopJobBinding
+import com.grkj.iscs.databinding.ItemSelectMemberBinding
+import com.grkj.iscs.databinding.ItemSelectPointBinding
+import com.grkj.iscs.features.main.dialog.TextDropDownDialog
+import com.grkj.iscs.features.main.viewmodel.job_manage.SopJobViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 编辑SOP作业
+ */
+class EditSopJobFragment : BaseFragment<FragmentEditSopJobBinding>() {
+    private val viewModel: SopJobViewModel by lazy { ViewModelProvider(this)[SopJobViewModel::class] }
+    private var selectedWorkstationId: Long? = null
+    private var selectedSopId: Long? = null
+    private var selectedSop: SopManageVo? = null
+    private var selectedPointData: List<PointManageVo> = mutableListOf()
+    private var selectedLockerData: List<UserManageVo> = mutableListOf()
+    private var selectedColockerData: List<UserManageVo> = mutableListOf()
+    private lateinit var textDropDownDialog: TextDropDownDialog
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_edit_sop_job
+    }
+
+    override fun initView() {
+        textDropDownDialog = TextDropDownDialog(requireContext())
+        textDropDownDialog.setWidthAsAnchorView(true)
+        binding.back.setDebouncedClickListener {
+            TipDialog.show(
+                title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_hint).toString(),
+                msg = CommonUtils.getStr(R.string.not_save_tip).toString(),
+                dialogType = TipDialog.DialogType.ERROR,
+                countDownTime = 10,
+                onConfirmClick = {
+                    navController.popBackStack()
+                })
+        }
+        binding.startJob.setDebouncedClickListener {
+            if (checkData()) {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_hint).toString(),
+                    msg = CommonUtils.getStr(R.string.start_job_check).toString(),
+                    dialogType = TipDialog.DialogType.INFO,
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        viewModel.startJob().observe(this) {
+                            navController.navigate(
+                                R.id.action_editSopJobFragment_to_jobExecuteFragment,
+                                NavOptions.Builder()
+                                    .setPopUpTo(R.id.editSopJobFragment, true).build()
+                            )
+                        }
+                    })
+            }
+        }
+        binding.cancel.setDebouncedClickListener {
+            TipDialog.show(
+                title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_hint).toString(),
+                msg = CommonUtils.getStr(R.string.not_save_tip).toString(),
+                dialogType = TipDialog.DialogType.ERROR,
+                countDownTime = 10,
+                onConfirmClick = {
+                    navController.popBackStack()
+                })
+        }
+        binding.confirm.setDebouncedClickListener {
+            if (checkData()) {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_confirm).toString(),
+                    msg = CommonUtils.getStr(
+                        R.string.create_sop_job_tip,
+                        args = listOf<String>(binding.jobNameEt.text.toString()).toTypedArray()
+                    ).toString(),
+                    dialogType = TipDialog.DialogType.INFO,
+                    countDownTime = 10,
+                    onConfirmClick = {
+                        saveSopJob()
+                    })
+            }
+        }
+        binding.workstationTv.setOnClickListener {
+            setWorkstationData()
+        }
+        binding.sopTv.setDebouncedClickListener {
+            if (selectedWorkstationId == null) {
+                PopTip.tip(R.string.please_select_sop_workstation)
+                return@setDebouncedClickListener
+            } else {
+                viewModel.getSopByWorkstation(selectedWorkstationId!!).observe(this) {
+                    setSopData()
+                }
+            }
+        }
+        binding.selectMemberTv.setDebouncedClickListener {
+            if (selectedWorkstationId == null) {
+                PopTip.tip(R.string.please_select_sop_workstation)
+                return@setDebouncedClickListener
+            }
+            GlobalDataTempStore.getInstance().saveData(
+                DataTransferConstants.KEY_CAN_SELECT_COLOCKER, selectedSop?.lockMode?.contains(
+                    LockStepEnum.COLOCK.type.toString()
+                ) == true
+            )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECT_POINT_WORKSTATION_ID,
+                    selectedWorkstationId!!
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA, selectedLockerData)
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA,
+                    selectedColockerData
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_TITLE_DATA,
+                    CommonUtils.getStr(R.string.create_sop_title).toString()
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_ICON_DATA,
+                    R.mipmap.icon_data_manage_menu_point_manage
+                )
+            navController.navigate(R.id.action_editSopJobFragment_to_selectMemberFragment)
+        }
+        binding.pointRv.grid(6).setup {
+            addType<PointManageVo>(R.layout.item_select_point)
+            onBind {
+                onSelectedPointRVBinding(this)
+            }
+        }
+        binding.colockerRv.linear(orientation = LinearLayout.HORIZONTAL).setup {
+            addType<UserManageVo>(R.layout.item_select_member)
+            onBind {
+                onSelectedMemberRVBinding(this)
+            }
+        }
+    }
+
+    /**
+     * 保存sop作业
+     */
+    private fun saveSopJob() {
+        viewModel.saveSopJob(
+            selectedPointData,
+            selectedLockerData,
+            selectedColockerData,
+            selectedSop?.lockMode,
+            selectedSopId,
+            selectedWorkstationId!!,
+            binding.jobNameEt.text.toString(),
+            viewModel.jobTicketData?.ticketId ?: 0
+        ).observe(this) {
+            if (it) {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed).toString(),
+                    msg = CommonUtils.getStr(R.string.sop_job_save_succeed).toString(),
+                    dialogType = TipDialog.DialogType.SUCCESS,
+                    countDownTime = 10,
+                )
+            } else {
+                TipDialog.show(
+                    title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed).toString(),
+                    msg = CommonUtils.getStr(R.string.sop_job_save_failed).toString(),
+                    dialogType = TipDialog.DialogType.ERROR,
+                    showConfirm = false,
+                    countDownTime = 10
+                )
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (selectedWorkstationId == null) {
+            PopTip.tip(R.string.please_select_sop_workstation)
+            return false
+        }
+        if (selectedSopId == null) {
+            PopTip.tip(R.string.please_select_sop)
+            return false
+        }
+        if (binding.jobNameEt.text.isEmpty()) {
+            PopTip.tip(R.string.please_input_job_name)
+            return false
+        }
+        if (selectedLockerData.isEmpty()) {
+            PopTip.tip(com.grkj.ui_base.R.string.select_locker)
+            return false
+        }
+        if (selectedSop?.lockMode?.contains(
+                LockStepEnum.COLOCK.type.toString()
+            ) == true && selectedColockerData.isEmpty()
+        ) {
+            PopTip.tip(com.grkj.ui_base.R.string.select_coloker)
+            return false
+        }
+        return true
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onSelectedMemberRVBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemSelectMemberBinding>()
+        val item = holder.getModel<UserManageVo>()
+        itemBinding.lockerName.text = item.nickName
+        itemBinding.lockerIcon.isSelected = true
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onSelectedPointRVBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemSelectPointBinding>()
+        val item = holder.getModel<PointManageVo>()
+        itemBinding.pointName.text = item.pointName.toString()
+        itemBinding.pointIcon.setImageResource(R.mipmap.icon_data_manage_menu_point_manage)
+    }
+
+    override fun initData() {
+        super.initData()
+        val ticketId = GlobalDataTempStore.getInstance()
+            .getData(DataTransferConstants.KEY_JOB_TICKET_ID) as Long
+        val sopId = GlobalDataTempStore.getInstance()
+            .getData(DataTransferConstants.KEY_EDIT_SOP_JOB_SOP_ID) as Long
+        viewModel.getSopJobData(ticketId, sopId).observe(this) {
+            selectedWorkstationId = viewModel.jobTicketData?.workstationId
+            selectedSopId = viewModel.jobTicketData?.sopId
+            selectedSop = viewModel.sopData.find { it.sopId == viewModel.jobTicketData?.sopId }
+            binding.workstationTv.text =
+                viewModel.workstationData.find { it.workstationId == viewModel.jobTicketData?.workstationId }?.workstationName
+            binding.sopTv.text =
+                viewModel.sopData.find { it.sopId == viewModel.jobTicketData?.sopId }?.sopName
+            binding.jobNameEt.setText(viewModel.jobTicketData?.ticketName)
+            selectedPointData = viewModel.sopPoints
+            selectedLockerData = viewModel.sopLockerData
+            selectedColockerData = viewModel.sopColockerData
+            binding.pointRv.models = selectedPointData
+            binding.lockerName.text = selectedLockerData.first().nickName
+            binding.colockerRv.models = selectedColockerData
+            binding.selectColockerLayout.isVisible =
+                selectedSop?.lockMode?.contains(LockStepEnum.COLOCK.type.toString()) == true
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        if (GlobalDataTempStore.getInstance()
+                .hasData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA)
+        ) {
+            selectedLockerData = GlobalDataTempStore.getInstance()
+                .getData(DataTransferConstants.KEY_SELECTED_MEMBER_LOCKER_DATA)
+                ?.let { return@let it as List<UserManageVo> } ?: listOf()
+        }
+
+        if (GlobalDataTempStore.getInstance()
+                .hasData(DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA)
+        ) {
+            selectedColockerData = GlobalDataTempStore.getInstance()
+                .getData(DataTransferConstants.KEY_SELECTED_MEMBER_COLOCKER_DATA)
+                ?.let { return@let it as List<UserManageVo> } ?: listOf()
+        }
+        if (selectedColockerData.isNotEmpty()) {
+            binding.colockerRv.models = selectedColockerData
+        }
+        if (selectedLockerData.isNotEmpty()) {
+            val userData = selectedLockerData.first()
+            binding.lockerName.text = userData.nickName
+        }
+        if (selectedPointData.isNotEmpty()) {
+            binding.pointRv.models = selectedPointData
+        }
+    }
+
+    /**
+     * 设置sop区域
+     */
+    private fun setWorkstationData() {
+        viewModel.getWorkstationData().observe(this) {
+            textDropDownDialog.setData(
+                viewModel.workstationData.map {
+                    TextDropDownDialog.SimpleTextDropDownEntity(
+                        dataId = it.workstationId,
+                        dataText = it.workstationName
+                    )
+                })
+            textDropDownDialog.setOnItemSelectListener {
+                binding.workstationTv.text = it.getShowText()
+                selectedWorkstationId = it.getId()
+            }
+            textDropDownDialog.showPopupWindow(binding.workstationTv)
+        }
+    }
+
+    /**
+     * 设置sop数据
+     */
+    private fun setSopData() {
+        textDropDownDialog.setData(
+            viewModel.sopData.map {
+                TextDropDownDialog.SimpleTextDropDownEntity(
+                    dataId = it.sopId,
+                    dataObject = it,
+                    dataText = it.sopName ?: ""
+                )
+            })
+        textDropDownDialog.setOnItemSelectListener {
+            binding.sopTv.text = it.getShowText()
+            selectedSopId = it.getId()
+            selectedSop = it.getData() as SopManageVo
+            binding.selectColockerLayout.isVisible = selectedSop?.lockMode?.contains(
+                LockStepEnum.COLOCK.type.toString()
+            ) == true
+            loadSopData(it.getId())
+        }
+        textDropDownDialog.showPopupWindow(binding.sopTv)
+    }
+
+    /**
+     * 加载sop数据
+     */
+    private fun loadSopData(sopId: Long) {
+        viewModel.loadSopData(sopId).observe(this) {
+            selectedColockerData = viewModel.sopColockerData
+            selectedLockerData = viewModel.sopLockerData
+            selectedPointData = viewModel.sopPoints
+            binding.pointRv.models = selectedPointData
+            binding.lockerName.text = selectedLockerData.first().nickName
+            binding.colockerRv.models = selectedColockerData
+        }
+    }
+}

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

@@ -0,0 +1,18 @@
+package com.grkj.iscs.features.main.fragment.job_manage
+
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentJobExecuteBinding
+import com.grkj.ui_base.base.BaseFragment
+
+/**
+ * 作业执行界面
+ */
+class JobExecuteFragment: BaseFragment<FragmentJobExecuteBinding>() {
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_job_execute
+    }
+
+    override fun initView() {
+
+    }
+}

+ 156 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageFragment.kt

@@ -0,0 +1,156 @@
+package com.grkj.iscs.features.main.fragment.job_manage
+
+import android.graphics.Color
+import androidx.lifecycle.ViewModelProvider
+import com.drake.brv.BindingAdapter
+import com.drake.brv.annotaion.DividerOrientation
+import com.drake.brv.utils.divider
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.grkj.data.enums.JobTicketStatusEnums
+import com.grkj.data.model.vo.JobTicketManageVo
+import com.grkj.iscs.R
+import com.grkj.iscs.common.DataTransferConstants
+import com.grkj.iscs.databinding.FragmentJobManageBinding
+import com.grkj.iscs.databinding.ItemJobManageBinding
+import com.grkj.iscs.features.main.viewmodel.job_manage.JobManageViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.data.GlobalDataTempStore
+import com.sik.sikcore.extension.setDebouncedClickListener
+
+/**
+ * 作业管理
+ */
+class JobManageFragment : BaseFragment<FragmentJobManageBinding>() {
+    private val viewModel: JobManageViewModel by lazy { ViewModelProvider(this)[JobManageViewModel::class] }
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_job_manage
+    }
+
+    override fun initView() {
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.deleteSop.setDebouncedClickListener {
+            deleteSelectedSop()
+        }
+        binding.refreshLayout.setOnRefreshListener {
+            getData(nextPage = false)
+        }
+        binding.refreshLayout.setOnLoadMoreListener {
+            getData()
+        }
+        binding.listRv.linear().divider {
+            this.setColor(Color.BLACK)
+            this.startVisible = false
+            this.endVisible = true
+            this.orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<JobTicketManageVo>(R.layout.item_job_manage)
+            onBind {
+                onListDataBinding(this)
+            }
+        }
+        setSelectAllListener()
+    }
+
+    private fun deleteSelectedSop() {
+        if (viewModel.jobManageDataList.none { it.isSelected }) {
+            PopTip.tip(R.string.please_select_sop)
+            return
+        }
+        TipDialog.show(
+            msg = CommonUtils.getStr(R.string.check_delete_sop).toString(),
+            countDownTime = 10,
+            onConfirmClick = {
+                viewModel.deleteSelectedJob().observe(this) {
+                    if (it) {
+                        TipDialog.show(
+                            dialogType = TipDialog.DialogType.SUCCESS,
+                            msg = CommonUtils.getStr(R.string.sop_manage_delete_succeed)
+                                .toString(),
+                            showConfirm = false,
+                            countDownTime = 10,
+                            onConfirmClick = {
+                                getData(false)
+                            },
+                            onCancelClick = {
+                                getData(false)
+                            }
+                        )
+                    } else {
+                        TipDialog.show(
+                            dialogType = TipDialog.DialogType.ERROR,
+                            msg = CommonUtils.getStr(R.string.sop_manage_delete_failed)
+                                .toString(),
+                            showConfirm = false,
+                            countDownTime = 10
+                        )
+                    }
+                }
+            })
+    }
+
+    private fun setSelectAllListener() {
+        binding.selectAll.setOnCheckedChangeListener { v, checked ->
+            viewModel.jobManageDataList.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onListDataBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemJobManageBinding>()
+        val item = holder.getModel<JobTicketManageVo>()
+        itemBinding.jobName.text = item.ticketName
+        itemBinding.status.text = JobTicketStatusEnums.getTicketStatusStr(item.ticketStatus)
+        itemBinding.select.setOnCheckedChangeListener(null)
+        itemBinding.select.isChecked = item.isSelected
+        itemBinding.select.setOnCheckedChangeListener { _, checked ->
+            item.isSelected = checked
+            binding.selectAll.setOnCheckedChangeListener(null)
+            binding.selectAll.isChecked = viewModel.jobManageDataList.all { it.isSelected }
+            setSelectAllListener()
+        }
+        itemBinding.view.setDebouncedClickListener {
+            GlobalDataTempStore.getInstance()
+                .saveData(DataTransferConstants.KEY_JOB_TICKET_ID, item.ticketId)
+            if (item.ticketStatus == JobTicketStatusEnums.NOT_START.status) {
+                if (item.sopId != null) {
+                    GlobalDataTempStore.getInstance()
+                        .saveData(DataTransferConstants.KEY_EDIT_SOP_JOB_SOP_ID, item.sopId ?: 0)
+                    navController.navigate(R.id.action_jobManageFragment_to_editSopJobFragment)
+                } else {
+                    navController.navigate(R.id.action_jobManageFragment_to_editJobFragment)
+                }
+            } else if (item.ticketStatus == JobTicketStatusEnums.CANCELED.status) {
+                PopTip.tip(R.string.job_canceled)
+            } else if (item.ticketStatus == JobTicketStatusEnums.FINISHED.status) {
+                PopTip.tip(R.string.job_finished)
+            } else {
+                navController.navigate(R.id.action_jobManageFragment_to_jobExecuteFragment)
+            }
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        getData(nextPage = false)
+    }
+
+    private fun getData(nextPage: Boolean = true) {
+        viewModel.getSopData(nextPage).observe(this) {
+            if (!nextPage) {
+                binding.selectAll.setOnCheckedChangeListener(null)
+                binding.selectAll.isChecked = false
+                setSelectAllListener()
+            }
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            binding.listRv.models = viewModel.jobManageDataList
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageHomeFragment.kt

@@ -133,7 +133,7 @@ class JobManageHomeFragment : BaseFragment<FragmentJobManageHomeBinding>() {
             }
 
             5 -> {
-
+                navController.navigate(R.id.action_jobManageHomeFragment_to_jobManageFragment)
             }
 
             6 -> {

+ 1 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/SopManageFragment.kt

@@ -24,7 +24,7 @@ import com.sik.sikcore.data.GlobalDataTempStore
 import com.sik.sikcore.extension.setDebouncedClickListener
 
 /**
- * 用户管理
+ * SOP管理
  */
 class SopManageFragment : BaseFragment<FragmentSopManageBinding>() {
     private val viewModel: SopManageViewModel by lazy { ViewModelProvider(this)[SopManageViewModel::class] }

+ 46 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobManageViewModel.kt

@@ -0,0 +1,46 @@
+package com.grkj.iscs.features.main.viewmodel.job_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.model.vo.JobTicketManageVo
+import com.grkj.data.repository.IJobTicketRepository
+import com.grkj.data.repository.impl.JobTicketRepository
+import com.grkj.ui_base.base.BaseViewModel
+import kotlinx.coroutines.Dispatchers
+
+/**
+ * SOP管理
+ */
+class JobManageViewModel : BaseViewModel() {
+    private val jobTicketRepository: IJobTicketRepository by lazy { JobTicketRepository.instance }
+    var jobManageDataList: MutableList<JobTicketManageVo> = mutableListOf()
+    private var current: Int = 0
+    private var size: Int = 50
+
+    /**
+     * 删除选中的sop
+     */
+    fun deleteSelectedJob(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            jobTicketRepository.deleteTicketByTicketIds(jobManageDataList.filter { it.isSelected == true }
+                .map { it.ticketId })
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取sop数据
+     */
+    fun getSopData(nextPage: Boolean): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            jobManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            jobManageDataList.addAll(jobTicketRepository.getJobDataPage(current, size))
+            emit(true)
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobViewModel.kt

@@ -66,7 +66,7 @@ class JobViewModel : BaseViewModel() {
         jobName: String
     ): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            jobTicketRepository.createSopJob(
+            jobTicketRepository.createJob(
                 selectedPoints,
                 selectedLockerData,
                 selectedColockerData,

+ 58 - 1
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/SopJobViewModel.kt

@@ -11,6 +11,7 @@ import com.grkj.data.repository.IWorkstationRepository
 import com.grkj.data.repository.impl.SopRepository
 import com.grkj.data.repository.impl.WorkstationRepository
 import com.grkj.data.enums.RoleEnums
+import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.impl.JobTicketRepository
 import com.grkj.ui_base.base.BaseViewModel
@@ -28,6 +29,7 @@ class SopJobViewModel : BaseViewModel() {
     var sopPoints: List<PointManageVo> = listOf()
     var sopLockerData: List<UserManageVo> = listOf()
     var sopColockerData: List<UserManageVo> = listOf()
+    var jobTicketData: JobTicketManageVo? = null
 
     /**
      * 初始化岗位数据
@@ -76,7 +78,7 @@ class SopJobViewModel : BaseViewModel() {
         jobName: String
     ): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            jobTicketRepository.createSopJob(
+            jobTicketRepository.createJob(
                 selectedSopPoints,
                 selectedLockerData,
                 selectedColockerData,
@@ -88,4 +90,59 @@ class SopJobViewModel : BaseViewModel() {
             emit(true)
         }
     }
+
+    /**
+     * 创建sop作业
+     */
+    fun saveSopJob(
+        selectedSopPoints: List<PointManageVo>,
+        selectedLockerData: List<UserManageVo>,
+        selectedColockerData: List<UserManageVo>,
+        lockMode: String?,
+        sopId: Long?,
+        workstationId: Long,
+        jobName: String,
+        ticketId: Long
+    ): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            jobTicketRepository.saveJob(
+                selectedSopPoints,
+                selectedLockerData,
+                selectedColockerData,
+                lockMode,
+                sopId,
+                workstationId,
+                jobName,
+                ticketId
+            )
+            emit(true)
+        }
+    }
+
+    /**
+     * 开始作业
+     */
+    fun startJob(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            jobTicketRepository.startJob(jobTicketData?.ticketId)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取SOP作业详情
+     */
+    fun getSopJobData(ticketId: Long, sopId: Long): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            jobTicketData = jobTicketRepository.getTicketDataByTicketId(ticketId)
+            workstationData = workstationRepository.getWorkStationData()
+            sopData = sopRepository.getSopDataByWorkstationId(jobTicketData?.workstationId ?: 0)
+            val sopJobUsers = jobTicketRepository.getTicketUsersByTicketId(ticketId)
+            sopLockerData = sopJobUsers.filter { it.roleKeys.contains(RoleEnums.JTLOCKER.roleKey) }
+            sopColockerData =
+                sopJobUsers.filter { it.roleKeys.contains(RoleEnums.JTCOLOCKER.roleKey) }
+            sopPoints = sopRepository.getSopPointsBySopId(sopId)
+            emit(true)
+        }
+    }
 }

+ 378 - 0
app/src/main/res/layout/fragment_edit_sop_job.xml

@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="20dp"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="10dp"
+            android:paddingVertical="5dp">
+
+            <ImageView
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:layout_weight="1"
+                android:text="@string/edit_sop_job_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/start_job"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:backgroundTint="@color/color_1daeff"
+                android:paddingHorizontal="20dp"
+                android:text="@string/start_the_job"
+                android:textColor="@color/white"
+                android:textSize="20sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:id="@+id/base_info_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginTop="20dp"
+            android:layout_marginBottom="10dp"
+            android:background="@drawable/home_card_bg"
+            android:gravity="center_vertical"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:layout_weight="1"
+                android:text="@string/base_info_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="@color/black" />
+
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/sop_workstation"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <TextView
+                    android:id="@+id/workstation_tv"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:drawableRight="@drawable/icon_drop_down"
+                    android:hint="@string/please_select_sop_workstation"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="36dp"
+                android:layout_marginTop="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/create_sop_job_sop"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <TextView
+                    android:id="@+id/sop_tv"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:drawableRight="@drawable/icon_drop_down"
+                    android:hint="@string/please_select_sop"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="20dp"
+                android:layout_marginBottom="20dp"
+                android:gravity="center_vertical"
+                android:orientation="horizontal"
+                android:paddingHorizontal="16dp">
+
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/job_name"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+
+                <EditText
+                    android:id="@+id/job_name_et"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:background="@drawable/bg_common_input"
+                    android:hint="@string/please_input_job_name"
+                    android:maxLines="1"
+                    android:paddingHorizontal="10dp"
+                    android:paddingVertical="2dp"
+                    android:singleLine="true"
+                    android:textColor="@color/black"
+                    android:textSize="18sp" />
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/point_info_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginVertical="10dp"
+            android:background="@drawable/home_card_bg"
+            android:gravity="center_vertical"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:layout_weight="1"
+                    android:text="@string/point_info_title"
+                    android:textColor="@color/black"
+                    android:textSize="24sp" />
+
+            </LinearLayout>
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="@color/black" />
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/point_rv"
+                android:layout_width="match_parent"
+                android:layout_height="240dp"
+                android:paddingBottom="10dp" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/member_info_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginVertical="10dp"
+            android:background="@drawable/home_card_bg"
+            android:gravity="center_vertical"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
+
+                <TextView
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:layout_weight="1"
+                    android:text="@string/member_info_title"
+                    android:textColor="@color/black"
+                    android:textSize="24sp" />
+
+                <TextView
+                    android:id="@+id/select_member_tv"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dp"
+                    android:layout_marginRight="10dp"
+                    android:background="@drawable/common_dialog_btn"
+                    android:paddingHorizontal="20dp"
+                    android:text="@string/select"
+                    android:textColor="@color/black"
+                    android:textSize="20sp" />
+            </LinearLayout>
+
+            <View
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="@color/black" />
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:orientation="horizontal">
+
+                <LinearLayout
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:background="@drawable/home_card_bg"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"
+                        android:paddingHorizontal="20dp"
+                        android:paddingVertical="10dp"
+                        android:text="@string/locker"
+                        android:textColor="@color/black"
+                        android:textSize="24sp" />
+
+                    <View
+                        android:layout_width="match_parent"
+                        android:layout_height="1dp"
+                        android:background="@color/black" />
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:gravity="center"
+                        android:orientation="vertical"
+                        android:paddingBottom="10dp">
+
+                        <ImageView
+                            android:layout_width="50dp"
+                            android:layout_height="50dp"
+                            android:layout_marginTop="10dp"
+                            android:src="@mipmap/icon_data_manage_menu_user_manage"
+                            android:tint="@color/black" />
+
+                        <TextView
+                            android:id="@+id/locker_name"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:gravity="center"
+                            android:textColor="@color/black"
+                            android:textSize="20sp" />
+                    </LinearLayout>
+                </LinearLayout>
+
+                <LinearLayout
+                    android:id="@+id/select_colocker_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/home_card_bg"
+                    android:orientation="vertical">
+
+                    <TextView
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:gravity="center"
+                        android:paddingHorizontal="20dp"
+                        android:paddingVertical="10dp"
+                        android:text="@string/colocker"
+                        android:textColor="@color/black"
+                        android:textSize="24sp" />
+
+                    <View
+                        android:layout_width="match_parent"
+                        android:layout_height="1dp"
+                        android:background="@color/black" />
+
+                    <androidx.recyclerview.widget.RecyclerView
+                        android:id="@+id/colocker_rv"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent" />
+                </LinearLayout>
+
+            </LinearLayout>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="10dp">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 125 - 0
app/src/main/res/layout/fragment_job_execute.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="20dp"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="10dp"
+            android:paddingVertical="5dp">
+
+            <ImageView
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:layout_weight="1"
+                android:text="@string/job_manage_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="10dp"
+            android:paddingVertical="10dp">
+
+            <TextView
+                android:id="@+id/delete_sop"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginTop="10dp"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="10dp" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/job_name"
+                android:textSize="18sp" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/status"
+                android:textSize="18sp" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/detail"
+                android:textSize="18sp" />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginBottom="10dp">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 125 - 0
app/src/main/res/layout/fragment_job_manage.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="20dp"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="10dp"
+            android:paddingVertical="5dp">
+
+            <ImageView
+                android:layout_width="20dp"
+                android:layout_height="20dp"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:layout_weight="1"
+                android:text="@string/job_manage_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="10dp"
+            android:paddingVertical="10dp">
+
+            <TextView
+                android:id="@+id/delete_sop"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="10dp"
+                android:background="@drawable/common_dialog_btn"
+                android:paddingHorizontal="20dp"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="20sp" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginTop="10dp"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="10dp" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/job_name"
+                android:textSize="18sp" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/status"
+                android:textSize="18sp" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/detail"
+                android:textSize="18sp" />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="20dp"
+            android:layout_marginBottom="10dp">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 44 - 0
app/src/main/res/layout/item_job_manage.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:divider="@drawable/divider_table"
+        android:showDividers="middle">
+
+        <CheckBox
+            android:id="@+id/select"
+            android:layout_width="30dp"
+            android:layout_height="30dp"
+            android:layout_gravity="center"
+            android:layout_margin="10dp" />
+
+        <TextView
+            android:id="@+id/job_name"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:ellipsize="end"
+            android:gravity="center"
+            android:singleLine="true"
+            android:textSize="18sp" />
+
+        <TextView
+            android:id="@+id/status"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="18sp" />
+
+        <TextView
+            android:id="@+id/view"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:text="@string/user_manage_view"
+            android:textSize="18sp" />
+    </LinearLayout>
+</layout>

+ 52 - 0
app/src/main/res/navigation/nav_job_manage.xml

@@ -20,6 +20,9 @@
         <action
             android:id="@+id/action_jobManageHomeFragment_to_sopManageFragment"
             app:destination="@id/sopManageFragment" />
+        <action
+            android:id="@+id/action_jobManageHomeFragment_to_jobManageFragment"
+            app:destination="@id/jobManageFragment" />
     </fragment>
     <fragment
         android:id="@+id/createJobFragment"
@@ -82,4 +85,53 @@
             android:id="@+id/action_editSopFragment_to_selectPointFragment"
             app:destination="@id/selectPointFragment" />
     </fragment>
+    <fragment
+        android:id="@+id/jobManageFragment"
+        android:name="com.grkj.iscs.features.main.fragment.job_manage.JobManageFragment"
+        android:label="JobManageFragment" >
+        <action
+            android:id="@+id/action_jobManageFragment_to_selectMemberFragment"
+            app:destination="@id/selectMemberFragment" />
+        <action
+            android:id="@+id/action_jobManageFragment_to_selectPointFragment"
+            app:destination="@id/selectPointFragment" />
+        <action
+            android:id="@+id/action_jobManageFragment_to_jobExecuteFragment"
+            app:destination="@id/jobExecuteFragment" />
+        <action
+            android:id="@+id/action_jobManageFragment_to_editJobFragment"
+            app:destination="@id/editJobFragment" />
+        <action
+            android:id="@+id/action_jobManageFragment_to_editSopJobFragment"
+            app:destination="@id/editSopJobFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/editJobFragment"
+        android:name="com.grkj.iscs.features.main.fragment.job_manage.EditJobFragment"
+        android:label="EditJobFragment" >
+        <action
+            android:id="@+id/action_editJobFragment_to_selectMemberFragment"
+            app:destination="@id/selectMemberFragment" />
+        <action
+            android:id="@+id/action_editJobFragment_to_selectPointFragment"
+            app:destination="@id/selectPointFragment" />
+        <action
+            android:id="@+id/action_editJobFragment_to_jobExecuteFragment"
+            app:destination="@id/jobExecuteFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/editSopJobFragment"
+        android:name="com.grkj.iscs.features.main.fragment.job_manage.EditSopJobFragment"
+        android:label="EditSopJobFragment" >
+        <action
+            android:id="@+id/action_editSopJobFragment_to_selectMemberFragment"
+            app:destination="@id/selectMemberFragment" />
+        <action
+            android:id="@+id/action_editSopJobFragment_to_jobExecuteFragment"
+            app:destination="@id/jobExecuteFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/jobExecuteFragment"
+        android:name="com.grkj.iscs.features.main.fragment.job_manage.JobExecuteFragment"
+        android:label="JobExecuteFragment" />
 </navigation>

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

@@ -138,4 +138,12 @@
     <string name="edit_sop_title">SOP detail</string>
     <string name="sop_save_succeed">SOP save succeed</string>
     <string name="sop_save_failed">SOP save failed</string>
+    <string name="job_manage_title">Job manage</string>
+    <string name="status">status</string>
+    <string name="job_canceled">Job already canceled</string>
+    <string name="job_finished">Job already finished</string>
+    <string name="edit_sop_job_title">SOP job detail</string>
+    <string name="start_job_check">Are you sure to start job?</string>
+    <string name="sop_job_save_succeed">SOP job save succeed</string>
+    <string name="sop_job_save_failed">SOP job save failed</string>
 </resources>

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

@@ -138,4 +138,12 @@
     <string name="edit_sop_title">SOP详情</string>
     <string name="sop_save_succeed">SOP保存成功</string>
     <string name="sop_save_failed">SOP保存失败</string>
+    <string name="job_manage_title">作业管理</string>
+    <string name="status">状态</string>
+    <string name="job_canceled">作业已取消</string>
+    <string name="job_finished">作业已结束</string>
+    <string name="edit_sop_job_title">SOP作业详情</string>
+    <string name="start_job_check">确认开始执行作业吗?</string>
+    <string name="sop_job_save_succeed">SOP作业保存成功</string>
+    <string name="sop_job_save_failed">SOP作业保存失败</string>
 </resources>

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

@@ -137,4 +137,12 @@
     <string name="edit_sop_title">SOP详情</string>
     <string name="sop_save_succeed">SOP保存成功</string>
     <string name="sop_save_failed">SOP保存失败</string>
+    <string name="job_manage_title">作业管理</string>
+    <string name="status">状态</string>
+    <string name="job_canceled">作业已取消</string>
+    <string name="job_finished">作业已结束</string>
+    <string name="edit_sop_job_title">SOP作业详情</string>
+    <string name="start_job_check">确认开始执行作业吗?</string>
+    <string name="sop_job_save_succeed">SOP作业保存成功</string>
+    <string name="sop_job_save_failed">SOP作业保存失败</string>
 </resources>

+ 2 - 2
data/src/main/java/com/grkj/data/dao/IsSopDao.kt

@@ -45,7 +45,7 @@ interface IsSopDao {
         select sop_id as sopId,
             `is`.sop_name as sopName,
             `is`.sop_type as sopType,
-            `is`.lock_mode as LockMode,
+            `is`.lock_mode as lockMode,
             `is`.workstation_id as workstationId,
             iw.workstation_name as workstationName,
             `is`.del_flag as delFlag 
@@ -121,7 +121,7 @@ interface IsSopDao {
         select sop_id as sopId,
             `is`.sop_name as sopName,
             `is`.sop_type as sopType,
-            `is`.lock_mode as LockMode,
+            `is`.lock_mode as lockMode,
             `is`.workstation_id as workstationId,
             iw.workstation_name as workstationName,
             `is`.del_flag as delFlag 

+ 108 - 1
data/src/main/java/com/grkj/data/dao/JobTicketDao.kt

@@ -3,7 +3,7 @@ package com.grkj.data.dao
 import androidx.room.Dao
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy
-import androidx.room.TypeConverter
+import androidx.room.Query
 import androidx.room.TypeConverters
 import com.grkj.data.converters.Converters
 import com.grkj.data.model.dos.IsJobTicket
@@ -12,6 +12,8 @@ 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.vo.JobTicketManageVo
+import com.grkj.data.model.vo.UserManageVo
 
 /**
  * 作业票数据库操作
@@ -55,4 +57,109 @@ interface JobTicketDao {
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun saveIsJobTicketUser(ticket: List<IsJobTicketUser>)
 
+    /**
+     * 根据作业票id删除作业票数据
+     */
+    @Query("delete from is_job_ticket where ticket_id in (:ticketIds)")
+    fun deleteJobTicketByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 根据作业票id删除作业票钥匙数据
+     */
+    @Query("delete from is_job_ticket_key where ticket_id in (:ticketIds)")
+    fun deleteJobTicketKeyByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 根据作业票id删除作业票挂锁数据
+     */
+    @Query("delete from is_job_ticket_lock where ticket_id in (:ticketIds)")
+    fun deleteJobTicketLockByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 根据作业票id删除作业票点位数据
+     */
+    @Query("delete from is_job_ticket_points where ticket_id in (:ticketIds)")
+    fun deleteJobTicketPointsByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 根据作业票id删除作业票步骤数据
+     */
+    @Query("delete from is_job_ticket_step where ticket_id in (:ticketIds)")
+    fun deleteJobTicketStepByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 根据作业票id删除作业票用户数据
+     */
+    @Query("delete from is_job_ticket_user where ticket_id in (:ticketIds)")
+    fun deleteJobTicketUserByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 获取作业票分页
+     */
+    @Query("""
+        select ticket_id as ticketId,
+        ticket_name as ticketName,
+        ticket_status as ticketStatus,
+        workstation_id as workstationId,
+        sop_id as sopId
+        from is_job_ticket
+        limit :size offset :offset
+    """)
+    fun getTicketDataPage(size: Int,offset: Int): List<JobTicketManageVo>
+
+    /**
+     * 开始作业
+     */
+    @Query("update is_job_ticket set ticket_status = :status where ticket_id = :jobTicketId")
+    fun startJob(jobTicketId: Long?, status: String)
+
+    /**
+     * 根据作业id获取作业人员数据
+     */
+    @Query(
+        """
+            SELECT
+              su.user_id                         AS userId,
+              su.nick_name                       AS nickName,
+              su.user_name                       AS userName,
+              -- 聚合所有卡号(如果没卡则结果里 cardCodes 会是 NULL 或空)
+              GROUP_CONCAT(DISTINCT ijc.card_code)       AS cardCodes,
+        
+              GROUP_CONCAT(DISTINCT sr.role_id)           AS roleIds,
+              GROUP_CONCAT(DISTINCT sr.role_name)         AS roleNames,
+              ijtu.user_role          AS roleKeys,
+              GROUP_CONCAT(DISTINCT iw.workstation_id)    AS workstationIds,
+              GROUP_CONCAT(DISTINCT iw.workstation_name)  AS workstationNames,
+        
+              su.status                            AS status
+            FROM is_job_ticket_user ijtu 
+            LEFT JOIN sys_user su on su.user_id = ijtu.user_id
+            LEFT JOIN sys_user_role sur ON su.user_id = sur.user_id
+            LEFT JOIN sys_role sr       ON sur.role_id = sr.role_id
+            LEFT JOIN is_job_card ijc   ON ijc.user_id = su.user_id
+            LEFT JOIN is_user_workstation iuw ON iuw.user_id = su.user_id
+            LEFT JOIN is_workstation iw ON iw.workstation_id = iuw.workstation_id
+            WHERE su.del_flag = 0 and ijtu.ticket_id = :ticketId
+            GROUP BY
+              su.user_id,
+              su.nick_name,
+              su.status
+    """
+    )
+    fun getTicketUsersByTicketId(ticketId: Long): List<UserManageVo>
+
+    /**
+     * 根据作业id获取作业数据
+     */
+    @Query("""
+        select ticket_id as ticketId,
+        ticket_name as ticketName,
+        ticket_status as ticketStatus,
+        workstation_id as workstationId,
+        sop_id as sopId
+        from is_job_ticket
+        where ticket_id = :ticketId
+    """)
+    fun getTicketDataByTicketId(ticketId: Long): JobTicketManageVo?
+
 }

+ 25 - 0
data/src/main/java/com/grkj/data/enums/JobTicketStatusEnums.kt

@@ -0,0 +1,25 @@
+package com.grkj.data.enums
+
+/**
+ * 作业状态
+ * (0未开始 1待上锁 2进行中 3待解锁 4已解锁 5已结束6已取消)
+ */
+enum class JobTicketStatusEnums(val status: String, val description: String) {
+    NOT_START("0", "未开始"),
+    WAIT_TO_LOCK("1", "选择人员"),
+    PROCESSING("2", "上锁中"),
+    WAIT_TO_UNLOCK("3", "共锁中"),
+    UNLOCKED("4", "解锁中"),
+    FINISHED("5", "已结束"),
+    CANCELED("6", "已取消");
+
+    companion object {
+        /**
+         * 根据状态获取状态文本
+         */
+        @JvmStatic
+        fun getTicketStatusStr(status: String): String {
+            return values().find { it.status == status }?.description ?: "未知"
+        }
+    }
+}

+ 21 - 0
data/src/main/java/com/grkj/data/model/vo/JobTicketManageVo.kt

@@ -0,0 +1,21 @@
+package com.grkj.data.model.vo
+
+import androidx.room.Ignore
+
+/**
+ * 作业数据
+ */
+class JobTicketManageVo {
+    var ticketId: Long = 0
+
+    var ticketName: String = ""
+
+    var ticketStatus: String = "0"
+
+    var workstationId: Long = 0
+
+    var sopId: Long? = null
+
+    @Ignore
+    var isSelected: Boolean = false
+}

+ 48 - 2
data/src/main/java/com/grkj/data/repository/IJobTicketRepository.kt

@@ -1,5 +1,7 @@
 package com.grkj.data.repository
 
+import com.grkj.data.model.res.TicketDetailRes
+import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.PointManageVo
 import com.grkj.data.model.vo.UserManageVo
 
@@ -8,9 +10,9 @@ import com.grkj.data.model.vo.UserManageVo
  */
 interface IJobTicketRepository {
     /**
-     * 创建sop作业
+     * 创建作业
      */
-    fun createSopJob(
+    fun createJob(
         selectedSopPoints: List<PointManageVo>,
         selectedLockerData: List<UserManageVo>,
         selectedColockerData: List<UserManageVo>,
@@ -19,4 +21,48 @@ interface IJobTicketRepository {
         workstationId: Long,
         jobName: String
     )
+
+    /**
+     * 保存作业
+     */
+    fun saveJob(
+        selectedSopPoints: List<PointManageVo>,
+        selectedLockerData: List<UserManageVo>,
+        selectedColockerData: List<UserManageVo>,
+        lockMode: String?,
+        sopId: Long?,
+        workstationId: Long,
+        jobName: String,
+        ticketId: Long
+    )
+
+    /**
+     * 工作票详情
+     */
+    fun getTicketDetail(ticketId: Long, callback: (TicketDetailRes?) -> Unit)
+
+    /**
+     * 根据工作票id列表删除工作票相关数据
+     */
+    fun deleteTicketByTicketIds(ticketIds: List<Long>)
+
+    /**
+     * 获取作业分页
+     */
+    fun getJobDataPage(current: Int, size: Int): List<JobTicketManageVo>
+
+    /**
+     * 开始作业
+     */
+    fun startJob(jobTicketId: Long?)
+
+    /**
+     * 根据作业id获取作业的用户id
+     */
+    fun getTicketUsersByTicketId(ticketId: Long): List<UserManageVo>
+
+    /**
+     * 根据作业票id获取作业信息
+     */
+    fun getTicketDataByTicketId(ticketId: Long): JobTicketManageVo?
 }

+ 0 - 13
data/src/main/java/com/grkj/data/repository/ITicketRepository.kt

@@ -1,13 +0,0 @@
-package com.grkj.data.repository
-
-import com.grkj.data.model.res.TicketDetailRes
-
-/**
- * 工作票仓储
- */
-interface ITicketRepository {
-    /**
-     * 工作票详情
-     */
-    fun getTicketDetail(ticketId: Long, callback: (TicketDetailRes?) -> Unit)
-}

+ 119 - 3
data/src/main/java/com/grkj/data/repository/impl/JobTicketRepository.kt

@@ -1,15 +1,18 @@
 package com.grkj.data.repository.impl
 
-import com.grkj.data.dao.IsSopDao
 import com.grkj.data.dao.JobTicketDao
 import com.grkj.data.database.ISCSDatabase
+import com.grkj.data.enums.JobTicketStatusEnums
 import com.grkj.data.enums.LockStepEnum
+import com.grkj.data.enums.RoleEnums
 import com.grkj.data.model.dos.IsJobTicket
 import com.grkj.data.model.dos.IsJobTicketKey
 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.res.TicketDetailRes
+import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.PointManageVo
 import com.grkj.data.model.vo.UserManageVo
 import com.grkj.data.repository.IJobTicketRepository
@@ -19,8 +22,7 @@ import com.grkj.data.repository.IJobTicketRepository
  */
 class JobTicketRepository : IJobTicketRepository {
     private val jobTicketDao: JobTicketDao by lazy { ISCSDatabase.instance.jobTicketDao() }
-    private val sopDao: IsSopDao by lazy { ISCSDatabase.instance.isSopDao() }
-    override fun createSopJob(
+    override fun createJob(
         selectedSopPoints: List<PointManageVo>,
         selectedLockerData: List<UserManageVo>,
         selectedColockerData: List<UserManageVo>,
@@ -84,7 +86,121 @@ class JobTicketRepository : IJobTicketRepository {
         jobTicketDao.saveIsJobTicketStep(ticketStep)
     }
 
+    override fun saveJob(
+        selectedSopPoints: List<PointManageVo>,
+        selectedLockerData: List<UserManageVo>,
+        selectedColockerData: List<UserManageVo>,
+        lockMode: String?,
+        sopId: Long?,
+        workstationId: Long,
+        jobName: String,
+        ticketId: Long
+    ) {
+        jobTicketDao.deleteJobTicketKeyByTicketIds(listOf(ticketId))
+        jobTicketDao.deleteJobTicketLockByTicketIds(listOf(ticketId))
+        jobTicketDao.deleteJobTicketPointsByTicketIds(listOf(ticketId))
+        jobTicketDao.deleteJobTicketStepByTicketIds(listOf(ticketId))
+        jobTicketDao.deleteJobTicketUserByTicketIds(listOf(ticketId))
+        val isJobTicket = IsJobTicket()
+        isJobTicket.ticketId = ticketId
+        isJobTicket.ticketName = jobName
+        isJobTicket.workstationId = workstationId
+        isJobTicket.sopId = sopId
+        val ticketId = jobTicketDao.saveIsJobTicket(isJobTicket)
+        val ticketPoints = selectedSopPoints.map {
+            val isJobTicketPoint = IsJobTicketPoints()
+            isJobTicketPoint.ticketId = ticketId
+            isJobTicketPoint.pointId = it.pointId
+            isJobTicketPoint.workstationId = workstationId
+            isJobTicketPoint
+        }
+        val ticketPointIds = jobTicketDao.saveIsJobTicketPoints(ticketPoints)
+        val ticketLockerUsers = selectedLockerData.map {
+            val isJobticketUser = IsJobTicketUser()
+            isJobticketUser.userId = it.userId
+            isJobticketUser.ticketId = ticketId
+            isJobticketUser.userName = it.userName
+            isJobticketUser.userRole = RoleEnums.JTLOCKER.roleKey
+            isJobticketUser
+        }
+        val ticketColockerUsers = selectedColockerData.map {
+            val isJobticketUser = IsJobTicketUser()
+            isJobticketUser.userId = it.userId
+            isJobticketUser.ticketId = ticketId
+            isJobticketUser.userName = it.userName
+            isJobticketUser.userRole = RoleEnums.JTCOLOCKER.roleKey
+            isJobticketUser
+        }
+
+        jobTicketDao.saveIsJobTicketUser(ticketLockerUsers)
+        jobTicketDao.saveIsJobTicketUser(ticketColockerUsers)
+        val ticketKeys = mutableListOf<IsJobTicketKey>().apply {
+            ticketPointIds.forEach {
+                val isJobTicketKey = IsJobTicketKey()
+                isJobTicketKey.ticketId = ticketId
+                add(isJobTicketKey)
+            }
+        }
+        jobTicketDao.saveIsJobTicketKey(ticketKeys)
+        val ticketLocks = mutableListOf<IsJobTicketLock>().apply {
+            ticketPointIds.forEach { point ->
+                val isJobTicketLock = IsJobTicketLock()
+                isJobTicketLock.ticketId = ticketId
+                isJobTicketLock.isolationPointId = point
+                add(isJobTicketLock)
+            }
+        }
+        jobTicketDao.saveIsJobTicketLock(ticketLocks)
+        val ticketStep = mutableListOf<IsJobTicketStep>().apply {
+            lockMode?.split(",")?.forEachIndexed { index, stepType ->
+                val isJobTicketStep = IsJobTicketStep()
+                isJobTicketStep.ticketId = ticketId
+                isJobTicketStep.stepType = stepType
+                isJobTicketStep.stepIndex = index
+                isJobTicketStep.stepContent =
+                    LockStepEnum.values().find { it.type == stepType.toInt() }?.description
+                isJobTicketStep.androidStepContent =
+                    LockStepEnum.values().find { it.type == stepType.toInt() }?.description
+                add(isJobTicketStep)
+            }
+        }
+        jobTicketDao.saveIsJobTicketStep(ticketStep)
+    }
+
+    override fun getTicketDetail(
+        ticketId: Long,
+        callback: (TicketDetailRes?) -> Unit
+    ) {
+
+    }
 
+    override fun deleteTicketByTicketIds(ticketIds: List<Long>) {
+        jobTicketDao.deleteJobTicketByTicketIds(ticketIds)
+        jobTicketDao.deleteJobTicketKeyByTicketIds(ticketIds)
+        jobTicketDao.deleteJobTicketLockByTicketIds(ticketIds)
+        jobTicketDao.deleteJobTicketPointsByTicketIds(ticketIds)
+        jobTicketDao.deleteJobTicketStepByTicketIds(ticketIds)
+        jobTicketDao.deleteJobTicketUserByTicketIds(ticketIds)
+    }
+
+    override fun getJobDataPage(
+        current: Int,
+        size: Int
+    ): List<JobTicketManageVo> {
+        return jobTicketDao.getTicketDataPage(size, current * size)
+    }
+
+    override fun startJob(jobTicketId: Long?) {
+        jobTicketDao.startJob(jobTicketId, JobTicketStatusEnums.WAIT_TO_LOCK.status)
+    }
+
+    override fun getTicketUsersByTicketId(ticketId: Long): List<UserManageVo> {
+        return jobTicketDao.getTicketUsersByTicketId(ticketId)
+    }
+
+    override fun getTicketDataByTicketId(ticketId: Long): JobTicketManageVo? {
+        return jobTicketDao.getTicketDataByTicketId(ticketId)
+    }
     companion object {
         /**
          * 单例

+ 0 - 25
data/src/main/java/com/grkj/data/repository/impl/TicketRepository.kt

@@ -1,25 +0,0 @@
-package com.grkj.data.repository.impl
-
-import com.grkj.data.repository.ITicketRepository
-import com.grkj.data.model.res.TicketDetailRes
-
-/**
- * 工作票仓储实现
- */
-class TicketRepository : ITicketRepository {
-    companion object {
-        /**
-         * 单例
-         */
-        @JvmStatic
-        val instance: TicketRepository by lazy {
-            TicketRepository()
-        }
-    }
-    override fun getTicketDetail(
-        ticketId: Long,
-        callback: (TicketDetailRes?) -> Unit
-    ) {
-        TODO("Not yet implemented")
-    }
-}

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

@@ -4,9 +4,6 @@ import com.clj.fastble.BleManager
 import com.clj.fastble.data.BleDevice
 import com.clj.fastble.exception.BleException
 import com.google.gson.Gson
-import com.grkj.data.repository.impl.HardwareRepository
-import com.grkj.data.repository.impl.StepRepository
-import com.grkj.data.repository.impl.TicketRepository
 import com.grkj.data.model.local.DeviceTakeUpdate
 import com.grkj.data.model.local.UpdateKeyReturn
 import com.grkj.data.model.local.WorkTicketGet
@@ -16,15 +13,22 @@ import com.grkj.data.model.req.LockPointUpdateReq
 import com.grkj.data.model.res.CommonDictRes
 import com.grkj.data.model.res.TicketDetailRes
 import com.grkj.data.repository.IHardwareRepository
+import com.grkj.data.repository.IJobTicketRepository
 import com.grkj.data.repository.IStepRepository
-import com.grkj.data.repository.ITicketRepository
-import com.grkj.ui_base.R
+import com.grkj.data.repository.impl.HardwareRepository
+import com.grkj.data.repository.impl.JobTicketRepository
+import com.grkj.data.repository.impl.StepRepository
 import com.grkj.shared.config.Constants
+import com.grkj.ui_base.R
 import com.grkj.ui_base.data.DictConstants
 import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.Executor
 import com.grkj.ui_base.utils.SPUtils
+import com.grkj.ui_base.utils.ble.BleCmdManager
+import com.grkj.ui_base.utils.ble.BleConnectionManager
+import com.grkj.ui_base.utils.ble.BleConst
+import com.grkj.ui_base.utils.ble.CustomBleWriteCallback
 import com.grkj.ui_base.utils.event.CurrentModeEvent
 import com.grkj.ui_base.utils.event.DeviceExceptionEvent
 import com.grkj.ui_base.utils.event.LoadingEvent
@@ -32,10 +36,6 @@ import com.grkj.ui_base.utils.event.UpdateTicketProgressEvent
 import com.grkj.ui_base.utils.extension.serialNo
 import com.grkj.ui_base.utils.modbus.DeviceConst
 import com.grkj.ui_base.utils.modbus.ModBusController
-import com.grkj.ui_base.utils.ble.BleCmdManager
-import com.grkj.ui_base.utils.ble.BleConnectionManager
-import com.grkj.ui_base.utils.ble.BleConst
-import com.grkj.ui_base.utils.ble.CustomBleWriteCallback
 import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.SIKCore
 import com.sik.sikcore.thread.ThreadUtils
@@ -63,7 +63,7 @@ object BleBusinessManager {
     /**
      * 工作票仓储
      */
-    private val ticketRepository: ITicketRepository by lazy { TicketRepository() }
+    private val jobTicketRepository: IJobTicketRepository by lazy { JobTicketRepository.instance }
 
     /**
      * 处理工作票完成情况
@@ -276,7 +276,7 @@ object BleBusinessManager {
                                 ?.maxByOrNull { it.stepIndex!! }?.stepIndex?.let {
                                     step = it
                                 }
-                            ticketRepository.getTicketDetail(itBO.ticketId) { ticketDetail ->
+                            jobTicketRepository.getTicketDetail(itBO.ticketId) { ticketDetail ->
                                 val role = ticketDetail?.ticketUserVOList?.find {
                                     it.userId == SPUtils.getLoginUser(SIKCore.getApplication())?.userId && it.userType == Constants.USER_TYPE_LOCKER
                                 }