Explorar el Código

refactor(开关和设备异常)
- 异常处理完成

周文健 hace 5 meses
padre
commit
2fe22c8e45

+ 47 - 6
app/src/main/java/com/grkj/iscs/BusinessManager.kt

@@ -26,6 +26,7 @@ import com.grkj.iscs.extentions.toHexStrings
 import com.grkj.iscs.modbus.DockBean
 import com.grkj.iscs.modbus.ModBusController
 import com.grkj.iscs.modbus.ModBusController.dockList
+import com.grkj.iscs.modbus.ModBusController.getOneKey
 import com.grkj.iscs.model.Constants.PERMISSION_REQUEST_CODE
 import com.grkj.iscs.model.Constants.USER_TYPE_LOCKER
 import com.grkj.iscs.model.DeviceConst.DEVICE_TYPE_CARD
@@ -79,7 +80,6 @@ import com.sik.sikcore.date.TimeUtils
 import com.sik.sikcore.thread.ThreadUtils
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 import pub.devrel.easypermissions.AfterPermissionGranted
@@ -196,6 +196,10 @@ object BusinessManager {
                                         1
                                     )
                                     ToastUtils.tip(R.string.take_out_key)
+                                    //连接一把
+                                    if (BleManager.getInstance().allConnectedDevice.size < BleConst.MAX_KEY_STAND_BY) {
+                                        connectExistsKey(it.data.bleBean.bleDevice.mac)
+                                    }
                                 }
                             } else {
                                 LogUtil.e("切换工作模式失败 : ${it.data.bleBean.bleDevice.mac}")
@@ -208,7 +212,9 @@ object BusinessManager {
                         2 -> {
                             if (it.data.res == 1) {
                                 // 只能在这里断开,不能全部断开
-                                BleManager.getInstance().disconnect(it.data.bleBean.bleDevice)
+                                if (BleManager.getInstance().allConnectedDevice.size > BleConst.MAX_KEY_STAND_BY) {
+                                    BleManager.getInstance().disconnect(it.data.bleBean.bleDevice)
+                                }
                                 ModBusController.updateKeyReadyStatus(
                                     it.data.bleBean.bleDevice.mac,
                                     true,
@@ -247,6 +253,41 @@ object BusinessManager {
         }
     }
 
+    /**
+     * 连接一把存在的可连接的钥匙
+     */
+    private fun connectExistsKey(exceptKeyMac: String) {
+        ThreadUtils.runOnIO {
+            // —— 串行请求1 & 2 ——
+            val slotsPage = getSlotsPage()
+
+            // —— 并行加载字典(或按需串行也行) ——
+            val slotStatus =
+                async { fetchDict<CommonDictRespVO>(DictConstants.KEY_SLOT_STATUS) }
+            val keyStatus = async { fetchDict<CommonDictRespVO>(DictConstants.KEY_KEY_STATUS) }
+            val slotType = async { fetchDict<CommonDictRespVO>(DictConstants.KEY_SLOT_TYPE) }
+
+            // 等待字典加载完成
+            val slotStatusList = slotStatus.await()
+            val keyStatusList = keyStatus.await()
+            val slotTypeList = slotType.await()
+            withContext(Dispatchers.Default) {
+                val keyPage = withContext(Dispatchers.IO) { getKeyPage() }
+                getOneKey(
+                    slotsPage?.records
+                        ?.filter {
+                            it.slotType == slotTypeList.find { d -> d.dictLabel == "钥匙" }?.dictValue &&
+                                    it.status == slotStatusList.find { d -> d.dictLabel == "异常" }?.dictValue
+                        }?.toMutableList() ?: mutableListOf(),
+                    (keyPage?.records
+                        ?.filter { it.exStatus == keyStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }
+                        ?.map { it.keyNfc ?: "" }
+                        ?.toMutableList() ?: mutableListOf()).apply { add(exceptKeyMac) }
+                )
+            }
+        }
+    }
+
     /****************************************** ModBus ******************************************/
     /**
      * 链接底座
@@ -681,7 +722,7 @@ object BusinessManager {
                             }?.toMutableList() ?: mutableListOf(),
                         locksPage?.records
                             ?.filter { it.exStatus == lockStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }
-                            ?.map { it.lockNfc }
+                            ?.map { it.lockNfc ?: "" }
                             ?.toMutableList() ?: mutableListOf()
                     )
                 }
@@ -701,7 +742,7 @@ object BusinessManager {
                 if (isNeedKey) {
                     val keyPage = withContext(Dispatchers.IO) { getKeyPage() }
                     keyPair = withContext(Dispatchers.Default) {
-                        ModBusController.getOneKey(
+                        getOneKey(
                             slotsPage?.records
                                 ?.filter {
                                     it.slotType == slotTypeList.find { d -> d.dictLabel == "钥匙" }?.dictValue &&
@@ -709,7 +750,7 @@ object BusinessManager {
                                 }?.toMutableList() ?: mutableListOf(),
                             keyPage?.records
                                 ?.filter { it.exStatus == keyStatusList.find { d -> d.dictLabel == "异常" }?.dictValue }
-                                ?.map { it.keyNfc }
+                                ?.map { it.keyNfc ?: "" }
                                 ?.toMutableList() ?: mutableListOf()
                         )
                     }
@@ -805,7 +846,7 @@ object BusinessManager {
         if (connectListeners.isEmpty()) {
             return
         }
-        if (isPreparing || BleManager.getInstance().allConnectedDevice.size >= BleConst.MAX_CONNECTED_DEVICE_SIZE) {
+        if (isPreparing || BleManager.getInstance().allConnectedDevice.size > BleConst.MAX_KEY_STAND_BY) {
             Executor.delayOnMain(1000) {
                 connectKey()
             }

+ 1 - 1
app/src/main/java/com/grkj/iscs/ble/BleConst.kt

@@ -5,7 +5,7 @@ package com.grkj.iscs.ble
  */
 object BleConst {
 
-    const val MAX_CONNECTED_DEVICE_SIZE: Int = 2
+    const val MAX_KEY_STAND_BY: Int = 1
 
     const val MTU = 500
 

+ 47 - 46
app/src/main/java/com/grkj/iscs/modbus/ModBusController.kt

@@ -902,63 +902,64 @@ object ModBusController {
      * @return 底座地址,钥匙
      */
     suspend fun getOneKey(
-        exceptionSlots: MutableList<CabinetSlotsRecord>, exceptionKeys: MutableList<String>
+        exceptionSlots: List<CabinetSlotsRecord>,
+        exceptionKeys: List<String>
     ): Pair<Byte, DockBean.KeyBean?>? {
-        LogUtil.i("异常钥匙rfid:${exceptionKeys}")
-        LogUtil.i("异常钥匙仓位:${exceptionSlots.joinToString(",") { "${it.row},${it.col}" }}")
-        val keyDockList =
-            dockList.filter { it.type == DOCK_TYPE_KEY || it.type == DOCK_TYPE_PORTABLE }
-        keyDockList.sortedBy { it.addr }
-        keyDockList.forEach { it.deviceList.sortedBy { it.idx } }
-        val keyList =
-            keyDockList.map { it.deviceList }.flatten().filterIsInstance<DockBean.KeyBean>()
-                .filterIndexed { index, _ -> (index + 1) !in exceptionSlots.map { it.col?.toInt() } }
-                .filter { it.rfid !in exceptionKeys && !it.rfid.isNullOrEmpty() && !it.mac.isNullOrEmpty() && it.isExist }
-                .toMutableList()
-        LogUtil.i("keyList : $keyList")
+        // 1. 过滤并准备钥匙列表
+        val slotCols = exceptionSlots.mapNotNull { it.col?.toInt() }
+        val keyDockList = dockList
+            .filter { it.type == DOCK_TYPE_KEY || it.type == DOCK_TYPE_PORTABLE }
+            .sortedBy { it.addr }
+            .onEach { it.deviceList.sortBy { dev -> dev.idx } }
+
+        val keyList = keyDockList
+            .flatMap { it.deviceList }
+            .filterIsInstance<DockBean.KeyBean>()
+            .filterIndexed { idx, _ -> (idx + 1) !in slotCols }
+            .filter { kb ->
+                !kb.rfid.isNullOrEmpty()
+                        && kb.rfid !in exceptionKeys
+                        && !kb.mac.isNullOrEmpty()
+                        && kb.isExist
+            }
+            .shuffled()
+
         if (keyList.isEmpty()) {
             ToastUtils.tip(R.string.no_available_key)
             return null
         }
 
-        keyList.forEach {
-            LogUtil.i(
-                "keyStatus  满足条件的钥匙: ${it.isExist} - ${it.rfid} - ${it.mac} - ${it.isReady} - " + "${
-                    BusinessManager.getBleDeviceByMac(
-                        it.mac
-                    )?.bleDevice != null
-                } - " + "${
-                    BleManager.getInstance()
-                        .isConnected(BusinessManager.getBleDeviceByMac(it.mac)?.bleDevice)
-                } - " + "${!BusinessManager.mExceptionKeyList.contains(it.rfid)}"
-            )
+        // —— 优先检查已经连接的 ——
+        val already = keyList.firstOrNull { kb ->
+            BleManager.getInstance().isConnected(kb.mac!!)  // mac 一定 non-null
         }
-        //等待检查的钥匙,打乱
-        val waitToCheckKeys = keyList.shuffled()
-        var key: DockBean.KeyBean? = null
-        for (waitToCheckKey in waitToCheckKeys) {
-            waitToCheckKey.mac?.let {
-                key = suspendCoroutine { cont ->
-                    BusinessManager.registerConnectListener(it) { isDone, bleBean ->
-                        if (isDone) {
-                            cont.resume(waitToCheckKey)
-                        }
-                    }
+        if (already != null) {
+            val addr = keyDockList
+                .firstOrNull { dock ->
+                    dock.getKeyList().any { it.rfid == already.rfid }
+                }?.addr
+            if (addr != null) return addr to already
+        }
+
+        // —— 如果没有已连的,再顺序挂起尝试连接 ——
+        for (kb in keyList) {
+            val mac = kb.mac ?: continue
+            val found = suspendCoroutine<DockBean.KeyBean?> { cont ->
+                BusinessManager.registerConnectListener(mac) { isDone, _ ->
+                    if (isDone) cont.resume(kb)
                 }
             }
+            if (found != null) {
+                val addr = keyDockList
+                    .firstOrNull { it.getKeyList().any { it.rfid == found.rfid } }
+                    ?.addr
+                return if (addr != null) addr to found else null
+            }
         }
 
-        if (key == null) {
-            LogUtil.e("getOneKey : no key match")
-            return null
-        }
-        LogUtil.i("待取钥匙:${key}")
-        val address = keyDockList.find { it.getKeyList().any { it.rfid == key?.rfid } }?.addr
-        if (address == null) {
-            LogUtil.e("getOneKey : no dock match")
-            return null
-        }
-        return Pair(address, key)
+        // 一个都没成功
+        LogUtil.e("getOneKey : no key match")
+        return null
     }
 
     /**

+ 10 - 0
app/src/main/java/com/grkj/iscs/model/UrlConsts.kt

@@ -316,4 +316,14 @@ object UrlConsts {
      * 获取锁具列表
      */
     const val GET_IS_LOCK_PAGE = "/iscs/lock/getIsLockPage"
+
+    /**
+     * 获取工卡列表
+     */
+    const val GET_IS_JOB_CARD_PAGE = "/iscs/card/getIsJobCardPage"
+
+    /**
+     * 获取RFID标识列表
+     */
+    const val GET_IS_RFID_TOKEN_PAGE = "/iscs/token/getIsRfidTokenPage"
 }

+ 17 - 0
app/src/main/java/com/grkj/iscs/model/vo/hardware/JobCardPageRespVO.kt

@@ -0,0 +1,17 @@
+package com.grkj.iscs.model.vo.hardware
+
+data class JobCardPageRespVO(
+    val total: Int,
+    val size: Int,
+    val current: Int,
+    val records: List<JobCardPageItem>
+)
+
+data class JobCardPageItem(
+    val cardId: String?,
+    val cardNfc: String?,
+    val nickName: String?,
+    val cardCode: String?,
+    val exStatus: String?,
+    val exRemark: String?
+)

+ 16 - 0
app/src/main/java/com/grkj/iscs/model/vo/hardware/RfidTokenPageRespVO.kt

@@ -0,0 +1,16 @@
+package com.grkj.iscs.model.vo.hardware
+
+data class RfidTokenPageRespVO(
+    val total: Int,
+    val size: Int,
+    val current: Int,
+    val records: List<RfidTokenPageItem>
+)
+
+data class RfidTokenPageItem(
+    val rfidId: String?,
+    val rfid: String?,
+    val rfidCode: String?,
+    val status: String?,
+    val remark: String?
+)

+ 6 - 4
app/src/main/java/com/grkj/iscs/model/vo/key/KeyPageRespVO.kt

@@ -8,8 +8,10 @@ data class KeyPageRespVO(
 )
 
 data class KeyPageItem(
-    val keyNfc: String,
-    val macAddress: String,
-    val exStatus: String,
-    val exRemark: String
+    val keyId: String?,
+    val keyNfc: String?,
+    val macAddress: String?,
+    val keyName: String?,
+    val exStatus: String?,
+    val exRemark: String?
 )

+ 5 - 3
app/src/main/java/com/grkj/iscs/model/vo/lock/LockPageRespVO.kt

@@ -8,7 +8,9 @@ data class LockPageRespVO(
 )
 
 data class LockPageItem(
-    val lockNfc: String,
-    val exStatus: String,
-    val exRemark: String
+    val lockId: String?,
+    val lockNfc: String?,
+    val lockName: String?,
+    val exStatus: String?,
+    val exRemark: String?
 )

+ 47 - 2
app/src/main/java/com/grkj/iscs/util/NetApi.kt

@@ -14,8 +14,10 @@ import com.grkj.iscs.model.vo.dict.CommonDictRespVO
 import com.grkj.iscs.model.vo.finger.LoginCharacteristicRespVO
 import com.grkj.iscs.model.vo.hardware.CabinetSlotsRespVo
 import com.grkj.iscs.model.vo.hardware.JobCardExDTO
+import com.grkj.iscs.model.vo.hardware.JobCardPageRespVO
 import com.grkj.iscs.model.vo.hardware.KeyExDTO
 import com.grkj.iscs.model.vo.hardware.LockExDTO
+import com.grkj.iscs.model.vo.hardware.RfidTokenPageRespVO
 import com.grkj.iscs.model.vo.hardware.SlotExDTO
 import com.grkj.iscs.model.vo.hardware.SwitchListReqVO
 import com.grkj.iscs.model.vo.key.KeyInfoRespVO
@@ -999,6 +1001,7 @@ object NetApi {
         exceptionType: String,
         raiser: Long,
         sourceName: String,
+        hardwareId:String,
         callBack: (Boolean) -> Unit
     ) {
         val map = mutableMapOf(
@@ -1007,7 +1010,7 @@ object NetApi {
             "exceptionType" to exceptionType,
             "raiser" to raiser,
             "sourceName" to sourceName,
-            "parameters" to sourceName,//设备id
+            "parameters" to hardwareId,//设备id
             "raiseTime" to SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(
                 Calendar.getInstance().time
             )
@@ -1356,7 +1359,7 @@ object NetApi {
     }
 
     /**
-     * 获取钥匙列表
+     * 获取挂锁列表
      */
     fun getIsLockPage(callBack: (LockPageRespVO?) -> Unit) {
         NetHttpManager.getInstance().doRequestNet(
@@ -1375,4 +1378,46 @@ object NetApi {
             }, isGet = true, isAuth = true
         )
     }
+
+    /**
+     * 获取工卡列表
+     */
+    fun getIsJobCardPage(callBack: (JobCardPageRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.GET_IS_JOB_CARD_PAGE,
+            false,
+            mapOf(
+                "current" to 1,
+                "size" to 50
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                } ?: run {
+                    callBack.invoke(null)
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
+
+    /**
+     * 获取Rfid标识列表
+     */
+    fun getIsRfidTokenPage(callBack: (RfidTokenPageRespVO?) -> Unit) {
+        NetHttpManager.getInstance().doRequestNet(
+            UrlConsts.GET_IS_RFID_TOKEN_PAGE,
+            false,
+            mapOf(
+                "current" to 1,
+                "size" to 50
+            ),
+            { res, _, _ ->
+                res?.let {
+                    callBack.invoke(getRefBean(it))
+                } ?: run {
+                    callBack.invoke(null)
+                }
+            }, isGet = true, isAuth = true
+        )
+    }
 }

+ 3 - 0
app/src/main/java/com/grkj/iscs/util/SPUtils.kt

@@ -11,6 +11,7 @@ import com.grkj.iscs.model.vo.user.UserInfoRespVO
 import com.grkj.iscs.util.log.LogUtil
 import com.sik.sikcore.extension.getMMKVData
 import com.sik.sikcore.extension.saveMMKVData
+import com.sik.sikcore.extension.toJson
 import com.tencent.mmkv.MMKV
 
 object SPUtils {
@@ -44,6 +45,8 @@ object SPUtils {
 
     private const val KEY_CABINET_ID = "key_cabinet_id"
 
+    private const val KEY_USERINFO = "key_userinfo"
+
     fun getLoginUser(context: Context): LoginUserBO? {
         val sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
         if (sp.getLong(KEY_LOGIN_USER_USER_ID, -1) == -1L) {

+ 2 - 1
app/src/main/java/com/grkj/iscs/view/activity/HomeActivity.kt

@@ -22,6 +22,7 @@ import com.grkj.iscs.model.eventmsg.DeviceExceptionMsg
 import com.grkj.iscs.model.eventmsg.MsgEvent
 import com.grkj.iscs.model.eventmsg.MsgEventConstants.MSG_EVENT_DEVICE_EXCEPTION
 import com.grkj.iscs.model.vo.user.UserInfoRespVO
+import com.grkj.iscs.util.SPUtils
 import com.grkj.iscs.util.log.LogUtil
 import com.grkj.iscs.view.adapter.MenuAdapter
 import com.grkj.iscs.view.base.BaseFragment
@@ -55,7 +56,7 @@ class HomeActivity : BaseMvpActivity<IHomeView, HomePresenter, ActivityHomeBindi
         presenter?.registerStatusListener()
         presenter?.getAndSaveCabinetId()
         val userInfo = intent.getSerializableExtra("userInfo")
-
+        
         BusinessManager.isTestMode = false
         if (userInfo != null && (userInfo as UserInfoRespVO).roles != null) {
             if (userInfo.roles?.any { it == USER_ROLE_DRAWER || it == USER_ROLE_LOCKER || it == USER_ROLE_COLOCKER || it == USER_ROLE_GUARD } == true) {

+ 69 - 25
app/src/main/java/com/grkj/iscs/view/fragment/DeviceStatusFragment.kt

@@ -6,13 +6,16 @@ import android.view.View
 import android.widget.ImageView
 import androidx.core.content.ContextCompat
 import androidx.recyclerview.widget.RecyclerView
+import com.grkj.iscs.MyApplication
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentDeviceStatusBinding
 import com.grkj.iscs.extentions.setSelected
 import com.grkj.iscs.extentions.setVisibleWithHolder
 import com.grkj.iscs.modbus.ModBusController
+import com.grkj.iscs.model.Constants.USER_ROLE_ADMHDWTESTER
 import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_KEY
 import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_LOCK
+import com.grkj.iscs.util.SPUtils
 import com.grkj.iscs.util.ToastUtils
 import com.grkj.iscs.view.base.BaseMvpFragment
 import com.grkj.iscs.view.dialog.SlotExceptionDialog
@@ -38,6 +41,7 @@ class DeviceStatusFragment :
         get() = FragmentDeviceStatusBinding.inflate(layoutInflater)
 
     override fun initView() {
+        presenter?.loginUser = SPUtils.getLoginUser(requireContext())
         presenter?.initData(mRowList)
         presenter?.getSlotData {
             presenter?.getExceptionIcon {
@@ -251,11 +255,19 @@ class DeviceStatusFragment :
                                     presenter?.exceptionHint(cabinetSlotsRecord.remark ?: "")
                                 }
                                 holder?.setOnLongClickListener(R.id.iv_key_exception_1) {
-                                    presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
-                                        presenter?.mExceptionRecovery?.invoke(
-                                            (row.row + 1),
-                                            1,
-                                            it.toInt()
+                                    if (presenter?.loginUser?.roleKeyList?.any { it == USER_ROLE_ADMHDWTESTER } == true) {
+                                        presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
+                                            presenter?.mExceptionRecovery?.invoke(
+                                                (row.row + 1),
+                                                1,
+                                                it.toInt()
+                                            )
+                                        }
+                                    } else {
+                                        ToastUtils.tip(
+                                            MyApplication.instance?.applicationContext!!.getString(
+                                                R.string.no_permission
+                                            )
                                         )
                                     }
                                     true
@@ -274,11 +286,19 @@ class DeviceStatusFragment :
                                     presenter?.exceptionHint(cabinetSlotsRecord.remark ?: "")
                                 }
                                 holder?.setOnLongClickListener(R.id.iv_key_exception_2) {
-                                    presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
-                                        presenter?.mExceptionRecovery?.invoke(
-                                            (row.row + 1),
-                                            2,
-                                            it.toInt()
+                                    if (presenter?.loginUser?.roleKeyList?.any { it == USER_ROLE_ADMHDWTESTER } == true) {
+                                        presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
+                                            presenter?.mExceptionRecovery?.invoke(
+                                                (row.row + 1),
+                                                2,
+                                                it.toInt()
+                                            )
+                                        }
+                                    } else {
+                                        ToastUtils.tip(
+                                            MyApplication.instance?.applicationContext!!.getString(
+                                                R.string.no_permission
+                                            )
                                         )
                                     }
                                     true
@@ -297,11 +317,19 @@ class DeviceStatusFragment :
                                     presenter?.exceptionHint(cabinetSlotsRecord.remark ?: "")
                                 }
                                 holder?.setOnLongClickListener(R.id.iv_key_exception_3) {
-                                    presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
-                                        presenter?.mExceptionRecovery?.invoke(
-                                            (row.row + 1),
-                                            3,
-                                            it.toInt()
+                                    if (presenter?.loginUser?.roleKeyList?.any { it == USER_ROLE_ADMHDWTESTER } == true) {
+                                        presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
+                                            presenter?.mExceptionRecovery?.invoke(
+                                                (row.row + 1),
+                                                3,
+                                                it.toInt()
+                                            )
+                                        }
+                                    } else {
+                                        ToastUtils.tip(
+                                            MyApplication.instance?.applicationContext!!.getString(
+                                                R.string.no_permission
+                                            )
                                         )
                                     }
                                     true
@@ -320,11 +348,19 @@ class DeviceStatusFragment :
                                     presenter?.exceptionHint(cabinetSlotsRecord.remark ?: "")
                                 }
                                 holder?.setOnLongClickListener(R.id.iv_key_exception_4) {
-                                    presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
-                                        presenter?.mExceptionRecovery?.invoke(
-                                            (row.row + 1),
-                                            4,
-                                            it.toInt()
+                                    if (presenter?.loginUser?.roleKeyList?.any { it == USER_ROLE_ADMHDWTESTER } == true) {
+                                        presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
+                                            presenter?.mExceptionRecovery?.invoke(
+                                                (row.row + 1),
+                                                4,
+                                                it.toInt()
+                                            )
+                                        }
+                                    } else {
+                                        ToastUtils.tip(
+                                            MyApplication.instance?.applicationContext!!.getString(
+                                                R.string.no_permission
+                                            )
                                         )
                                     }
                                     true
@@ -436,11 +472,19 @@ class DeviceStatusFragment :
                                     presenter?.exceptionHint(cabinetSlotsRecord.remark ?: "")
                                 }
                                 holder?.setOnLongClickListener(R.id.iv_lock_exception) {
-                                    presenter?.slotType?.find { it.dictLabel == "钥匙" }?.dictValue?.let {
-                                        presenter?.mExceptionRecovery?.invoke(
-                                            row.row,
-                                            (lockIdx + 1),
-                                            it.toInt()
+                                    if (presenter?.loginUser?.roleKeyList?.any { it == USER_ROLE_ADMHDWTESTER } == true) {
+                                        presenter?.slotType?.find { it.dictLabel == "锁" }?.dictValue?.let {
+                                            presenter?.mExceptionRecovery?.invoke(
+                                                row.row,
+                                                (lockIdx + 1),
+                                                it.toInt()
+                                            )
+                                        }
+                                    } else {
+                                        ToastUtils.tip(
+                                            MyApplication.instance?.applicationContext!!.getString(
+                                                R.string.no_permission
+                                            )
                                         )
                                     }
                                     true

+ 114 - 3
app/src/main/java/com/grkj/iscs/view/fragment/ExceptionReportFragment.kt

@@ -1,8 +1,13 @@
 package com.grkj.iscs.view.fragment
 
+import android.view.View
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentExceptionReportBinding
 import com.grkj.iscs.model.vo.dict.CommonDictRespVO
+import com.grkj.iscs.model.vo.hardware.JobCardPageItem
+import com.grkj.iscs.model.vo.hardware.RfidTokenPageItem
+import com.grkj.iscs.model.vo.key.KeyPageItem
+import com.grkj.iscs.model.vo.lock.LockPageItem
 import com.grkj.iscs.util.ToastUtils
 import com.grkj.iscs.view.base.BaseMvpFragment
 import com.grkj.iscs.view.iview.IExceptionReportView
@@ -19,6 +24,11 @@ class ExceptionReportFragment :
     private val mLevelList = mutableListOf<CommonDictRespVO>()
     private var mTypeIdx = -1   // 选中的类型
     private var mLevelIdx = -1  // 选中的等级
+    private var mSelectDeviceId = ""
+    private var mKeyList: MutableList<KeyPageItem> = mutableListOf()
+    private var mLockList: MutableList<LockPageItem> = mutableListOf()
+    private var mJobCardList: MutableList<JobCardPageItem> = mutableListOf()
+    private var mRfidList: MutableList<RfidTokenPageItem> = mutableListOf()
 
     override val viewBinding: FragmentExceptionReportBinding
         get() = FragmentExceptionReportBinding.inflate(layoutInflater)
@@ -40,13 +50,109 @@ class ExceptionReportFragment :
             }
         }
 
-        mBinding?.siType?.setOnSpinnerSelectListener(object : SelectableInput.OnSpinnerSelectListener {
+        mBinding?.siType?.setOnSpinnerSelectListener(object :
+            SelectableInput.OnSpinnerSelectListener {
             override fun onSelect(str: String?, index: Int) {
+                mSelectDeviceId = ""
                 mTypeIdx = index
+                when (str) {
+                    "钥匙异常" -> {
+                        mBinding?.siDevice?.visibility = View.VISIBLE
+                        mBinding?.siDevice?.setTitle(getString(R.string.exception_report_hardware_key))
+                        presenter?.getKeyPage {
+                            mKeyList.clear()
+                            mKeyList.addAll(it?.records ?: mutableListOf())
+                            if (mKeyList.isEmpty()){
+                                mBinding?.siDevice?.setOptionList(mutableListOf())
+                            }
+                            mBinding?.siDevice?.setOptionList(mKeyList.map { "${it.keyName}(${it.keyNfc})" }
+                                ?.toMutableList() ?: mutableListOf())
+                        }
+                        mBinding?.siDevice?.setOnSpinnerSelectListener(object :
+                            SelectableInput.OnSpinnerSelectListener {
+                            override fun onSelect(str: String?, index: Int) {
+                                mSelectDeviceId =
+                                    mKeyList.find { it.keyNfc?.let { it1 -> str?.contains(it1) } == true }?.keyId
+                                        ?: ""
+                            }
+                        })
+                    }
+
+                    "挂锁异常" -> {
+                        mBinding?.siDevice?.visibility = View.VISIBLE
+                        mBinding?.siDevice?.setTitle(getString(R.string.exception_report_hardware_lock))
+                        presenter?.getLockPage {
+                            mLockList.clear()
+                            mLockList.addAll(it?.records ?: mutableListOf())
+                            if (mLockList.isEmpty()){
+                                mBinding?.siDevice?.setOptionList(mutableListOf())
+                            }
+                            mBinding?.siDevice?.setOptionList(mLockList.map { "${it.lockName}(${it.lockNfc})" }
+                                ?.toMutableList() ?: mutableListOf())
+                        }
+                        mBinding?.siDevice?.setOnSpinnerSelectListener(object :
+                            SelectableInput.OnSpinnerSelectListener {
+                            override fun onSelect(str: String?, index: Int) {
+                                mSelectDeviceId =
+                                    mLockList.find { it.lockNfc?.let { it1 -> str?.contains(it1) } == true }?.lockId
+                                        ?: ""
+                            }
+                        })
+                    }
+
+                    "工卡异常" -> {
+                        mBinding?.siDevice?.visibility = View.VISIBLE
+                        mBinding?.siDevice?.setTitle(getString(R.string.exception_report_hardware_job_card))
+                        presenter?.getJobCardPage {
+                            mJobCardList.clear()
+                            mJobCardList.addAll(it?.records?.filter { it.nickName != null }
+                                ?.toMutableList() ?: mutableListOf())
+                            if (mJobCardList.isEmpty()){
+                                mBinding?.siDevice?.setOptionList(mutableListOf())
+                            }
+                            mBinding?.siDevice?.setOptionList(mJobCardList.map { "${it.cardCode}(${it.cardNfc})" }.toMutableList() ?: mutableListOf())
+                        }
+                        mBinding?.siDevice?.setOnSpinnerSelectListener(object :
+                            SelectableInput.OnSpinnerSelectListener {
+                            override fun onSelect(str: String?, index: Int) {
+                                mSelectDeviceId =
+                                    mJobCardList.find { it.cardNfc?.let { it1 -> str?.contains(it1) } == true }?.cardId
+                                        ?: ""
+                            }
+                        })
+                    }
+
+                    "RFID异常" -> {
+                        mBinding?.siDevice?.visibility = View.VISIBLE
+                        mBinding?.siDevice?.setTitle(getString(R.string.exception_report_hardware_rfid_point))
+                        presenter?.getRfidTokenPage {
+                            mRfidList.clear()
+                            mRfidList.addAll(it?.records ?: mutableListOf())
+                            if (mRfidList.isEmpty()){
+                                mBinding?.siDevice?.setOptionList(mutableListOf())
+                            }
+                            mBinding?.siDevice?.setOptionList(mRfidList.map { "${it.rfidCode}(${it.rfid})" }
+                                ?.toMutableList() ?: mutableListOf())
+                        }
+                        mBinding?.siDevice?.setOnSpinnerSelectListener(object :
+                            SelectableInput.OnSpinnerSelectListener {
+                            override fun onSelect(str: String?, index: Int) {
+                                mSelectDeviceId =
+                                    mRfidList.find { it.rfid?.let { it1 -> str?.contains(it1) } == true }?.rfidId
+                                        ?: ""
+                            }
+                        })
+                    }
+
+                    else -> {
+                        mBinding?.siDevice?.visibility = View.GONE
+                    }
+                }
             }
         })
 
-        mBinding?.siLevel?.setOnSpinnerSelectListener(object : SelectableInput.OnSpinnerSelectListener {
+        mBinding?.siLevel?.setOnSpinnerSelectListener(object :
+            SelectableInput.OnSpinnerSelectListener {
             override fun onSelect(str: String?, index: Int) {
                 mLevelIdx = index
             }
@@ -61,10 +167,15 @@ class ExceptionReportFragment :
                 ToastUtils.tip(R.string.exception_level_tip)
                 return@setOnClickListener
             }
+            if (mBinding?.siDevice?.visibility == View.VISIBLE && mSelectDeviceId.isEmpty()) {
+                ToastUtils.tip(R.string.exception_select_hardware_tip)
+                return@setOnClickListener
+            }
             presenter?.insertException(
                 mBinding?.siDescription?.getText(),
                 mLevelList[mLevelIdx].dictValue!!,
-                mExceptionList[mTypeIdx].dictValue!!
+                mExceptionList[mTypeIdx].dictValue!!,
+                mSelectDeviceId
             ) {
                 ToastUtils.tip(R.string.exception_submit_success_tip)
             }

+ 3 - 0
app/src/main/java/com/grkj/iscs/view/presenter/DeviceStatusPresenter.kt

@@ -12,9 +12,11 @@ import com.grkj.iscs.extentions.toHexStrings
 import com.grkj.iscs.modbus.ModBusController
 import com.grkj.iscs.model.DeviceConst.DOCK_TYPE_LOCK
 import com.grkj.iscs.model.DictConstants
+import com.grkj.iscs.model.bo.LoginUserBO
 import com.grkj.iscs.model.vo.dict.CommonDictRespVO
 import com.grkj.iscs.model.vo.hardware.CabinetSlotsRecord
 import com.grkj.iscs.model.vo.hardware.SlotExDTO
+import com.grkj.iscs.model.vo.user.UserInfoRespVO
 import com.grkj.iscs.util.Executor
 import com.grkj.iscs.util.NetApi
 import com.grkj.iscs.util.SPUtils
@@ -35,6 +37,7 @@ class DeviceStatusPresenter : BasePresenter<IDeviceStatusView>() {
         null
     var slotType: MutableList<CommonDictRespVO>? = null
     var slotStatus: MutableList<CommonDictRespVO>? = null
+    var loginUser: LoginUserBO? = null
 
     fun initData(rowList: MutableList<DockStatusBO>) {
         val dockConfigJson = SPUtils.getDockConfig(mContext!!)

+ 35 - 1
app/src/main/java/com/grkj/iscs/view/presenter/ExceptionReportPresenter.kt

@@ -4,6 +4,10 @@ import com.grkj.iscs.extentions.serialNo
 import com.grkj.iscs.model.Constants.DEVICE_TYPE
 import com.grkj.iscs.model.UrlConsts
 import com.grkj.iscs.model.vo.dict.CommonDictRespVO
+import com.grkj.iscs.model.vo.hardware.JobCardPageRespVO
+import com.grkj.iscs.model.vo.hardware.RfidTokenPageRespVO
+import com.grkj.iscs.model.vo.key.KeyPageRespVO
+import com.grkj.iscs.model.vo.lock.LockPageRespVO
 import com.grkj.iscs.util.Executor
 import com.grkj.iscs.util.NetApi
 import com.grkj.iscs.util.SPUtils
@@ -33,6 +37,7 @@ class ExceptionReportPresenter : BasePresenter<IExceptionReportView>() {
         exceptionDescription: String?,
         exceptionLevel: String,
         exceptionType: String,
+        hardwareId: String,
         callBack: (Boolean) -> Unit
     ) {
         NetApi.insertException(
@@ -41,11 +46,40 @@ class ExceptionReportPresenter : BasePresenter<IExceptionReportView>() {
             exceptionLevel,
             exceptionType,
             SPUtils.getLoginUser(mContext!!)?.userId!!,
-            mContext!!.serialNo()
+            mContext!!.serialNo(),
+            hardwareId
         ) {
             Executor.runOnMain {
                 callBack(it)
             }
         }
     }
+
+    /**
+     * 获取钥匙列表
+     */
+    fun getKeyPage(callBack: (KeyPageRespVO?) -> Unit) {
+        NetApi.getIsKeyPage { callBack.invoke(it) }
+    }
+
+    /**
+     * 获取挂锁列表
+     */
+    fun getLockPage(callBack: (LockPageRespVO?) -> Unit) {
+        NetApi.getIsLockPage { callBack.invoke(it) }
+    }
+
+    /**
+     * 获取工卡列表
+     */
+    fun getJobCardPage(callBack: (JobCardPageRespVO?) -> Unit) {
+        NetApi.getIsJobCardPage { callBack.invoke(it) }
+    }
+
+    /**
+     * 获取工卡列表
+     */
+    fun getRfidTokenPage(callBack: (RfidTokenPageRespVO?) -> Unit) {
+        NetApi.getIsRfidTokenPage { callBack.invoke(it) }
+    }
 }

+ 37 - 9
app/src/main/java/com/grkj/iscs/view/widget/SelectableInput.kt

@@ -87,7 +87,10 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
                 mBinding.et.background = if (mBinding.et.isEnabled) {
                     AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_spinner_bg)
                 } else {
-                    AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_spinner_disabled_bg)
+                    AppCompatResources.getDrawable(
+                        ctx,
+                        R.drawable.selectable_input_spinner_disabled_bg
+                    )
                 }
             }
             // 输入模式
@@ -95,7 +98,10 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
                 mBinding.et.background = if (mBinding.et.isEnabled) {
                     AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_text_bg)
                 } else {
-                    AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_text_disabled_bg)
+                    AppCompatResources.getDrawable(
+                        ctx,
+                        R.drawable.selectable_input_text_disabled_bg
+                    )
                 }
             }
         }
@@ -110,7 +116,10 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
                 mBinding.et.background = if (mBinding.et.isEnabled) {
                     AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_spinner_bg)
                 } else {
-                    AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_spinner_disabled_bg)
+                    AppCompatResources.getDrawable(
+                        ctx,
+                        R.drawable.selectable_input_spinner_disabled_bg
+                    )
                 }
 
                 mBinding.et.inputType = 0
@@ -133,7 +142,10 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
                 mBinding.et.background = if (mBinding.et.isEnabled) {
                     AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_text_bg)
                 } else {
-                    AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_text_disabled_bg)
+                    AppCompatResources.getDrawable(
+                        ctx,
+                        R.drawable.selectable_input_text_disabled_bg
+                    )
                 }
 
                 val textWatcher = object : TextWatcher {
@@ -153,7 +165,8 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
                         mBinding.et.addTextChangedListener(textWatcher)
                     } else {
                         mBinding.et.removeTextChangedListener(textWatcher)
-                        val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+                        val imm =
+                            ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                         imm.hideSoftInputFromWindow(view.windowToken, 0)
                     }
                 }
@@ -165,7 +178,10 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
                 mBinding.et.background = if (mBinding.et.isEnabled) {
                     AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_spinner_bg)
                 } else {
-                    AppCompatResources.getDrawable(ctx, R.drawable.selectable_input_spinner_disabled_bg)
+                    AppCompatResources.getDrawable(
+                        ctx,
+                        R.drawable.selectable_input_spinner_disabled_bg
+                    )
                 }
 
                 mBinding.et.inputType = 0
@@ -186,6 +202,12 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
         }
     }
 
+    fun setTitle(v: String?) {
+        isSkipListener = true
+        mBinding.tvName.text = v
+        isSkipListener = false
+    }
+
     fun setText(v: String?) {
         isSkipListener = true
         mBinding.et.setText(v)
@@ -202,11 +224,16 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
     fun setOptionList(list: MutableList<String?>) {
         mOptionList.clear()
         mOptionList.addAll(list)
+        mPopWindow?.contentView?.let {
+            val popBinding = LayoutSelectableinputSpinnerBinding.bind(it)
+            popBinding.rvOptions.adapter?.notifyDataSetChanged()
+        }
     }
 
     private fun showDropdown() {
-        mDropdownView?:let {
-            mDropdownView = LayoutInflater.from(ctx).inflate(R.layout.layout_selectableinput_spinner, null)
+        mDropdownView ?: let {
+            mDropdownView =
+                LayoutInflater.from(ctx).inflate(R.layout.layout_selectableinput_spinner, null)
         }
 
         mPopWindow ?: let {
@@ -218,7 +245,8 @@ class SelectableInput(private val ctx: Context, attrs: AttributeSet) : LinearLay
             mPopWindow?.isFocusable = true
 
             val popBinding = LayoutSelectableinputSpinnerBinding.bind(mDropdownView!!)
-            popBinding.rvOptions.adapter = object : CommonAdapter<String>(ctx, R.layout.item_rv_selectableinput_spinner, mOptionList) {
+            popBinding.rvOptions.adapter = object :
+                CommonAdapter<String>(ctx, R.layout.item_rv_selectableinput_spinner, mOptionList) {
                 override fun convert(holder: ViewHolder?, option: String?, position: Int) {
                     holder?.setText(R.id.tv_option, option ?: "")
                     holder?.setOnClickListener(R.id.tv_option) {

+ 14 - 2
app/src/main/res/layout/fragment_exception_report.xml

@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     tools:context=".view.fragment.ExceptionReportFragment">
 
     <com.grkj.iscs.view.widget.CommonBtn
         android:id="@+id/cb_submit"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
         android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true"
         android:layout_marginRight="@dimen/common_spacing"
         app:btn_bg="@drawable/common_btn_red_bg"
         app:btn_name="@string/submit" />
@@ -49,6 +49,18 @@
             app:required="true"
             app:text_size="@dimen/common_text_size" />
 
+        <com.grkj.iscs.view.widget.SelectableInput
+            android:id="@+id/si_device"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginVertical="@dimen/common_spacing"
+            android:visibility="gone"
+            app:edit_width="200dp"
+            app:edittext_hint="@string/exception_report_level_hint"
+            app:mode="select"
+            app:required="true"
+            app:text_size="@dimen/common_text_size" />
+
         <com.grkj.iscs.view.widget.SelectableInput
             android:id="@+id/si_description"
             android:layout_width="match_parent"

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

@@ -350,4 +350,6 @@
     <string name="is_device_recovery_check">Is device recovery check(%1$s,%2$s)?</string>
     <string name="recovery_success">recovery success</string>
     <string name="recovery_failed">recovery failed</string>
+    <string name="no_permission">no permission</string>
+    <string name="exception_select_hardware_tip">Please select hardware</string>
 </resources>

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

@@ -350,4 +350,6 @@
     <string name="is_device_recovery_check">是否确认恢复设备(%1$s,%2$s)</string>
     <string name="recovery_success">恢复成功</string>
     <string name="recovery_failed">恢复失败</string>
+    <string name="no_permission">权限不足</string>
+    <string name="exception_select_hardware_tip">请选择硬件</string>
 </resources>

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

@@ -350,4 +350,6 @@
     <string name="is_device_recovery_check">是否确认恢复设备(%1$s,%2$s)</string>
     <string name="recovery_success">恢复成功</string>
     <string name="recovery_failed">恢复失败</string>
+    <string name="no_permission">权限不足</string>
+    <string name="exception_select_hardware_tip">请选择硬件</string>
 </resources>