feat(inbound): 新增入库秤毛菜/净材入库功能

- 新增入库专用网络层(InboundApiService、InboundRepository、InboundNetworkModule、InboundInterceptor)
- 新增入库相关数据模型(IngredientByTrace、SupplierOption、ZoneOption、GoodsSearchModel、CleanInboundParam、RawInboundParam、InboundApiResponse)
- 新增 FormField 动态表单模型(FieldType 枚举、apiKey 字段映射、submitValueId 控制提交值)
- 新增 FormFieldAdapter,基于 FlexboxLayoutManager 实现两列自动换行表单,支持五种字段类型
- 新增 InboundGoodsSearchDialog(溯源码检索食材)和 InboundGoodsStoreDialog(入库录入,表单转 Map 提交)
- 新增 StoreViewModel,提交接口入参改为 Map<String, Any>
- 重构 StoreActivity:注入 StoreViewModel,onResume 预加载供应商/区域列表到 GlobalData
- 修改 GoodsListActivity:抽取 onGoodsSearchRequested() 供子类重写
- 修改 GoodsRecognizeDialog:StoreActivity 场景下打开 InboundGoodsStoreDialog
- GlobalData 新增 inboundSupplierList / inboundZoneList 全局缓存
- build.gradle.kts 新增 flexbox:3.0.0 依赖

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 21:18:09 +08:00
co-authored by Claude Sonnet 4.6
parent ac9494a0f3
commit fabc4d780a
28 changed files with 2352 additions and 11 deletions
@@ -0,0 +1,96 @@
package com.sw.inbound.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sw.inbound.model.response.IngredientByTrace
import com.sw.inbound.model.response.SupplierOption
import com.sw.inbound.model.response.ZoneOption
import com.sw.inbound.repository.InboundRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class StoreViewModel @Inject constructor(
private val repository: InboundRepository
) : ViewModel() {
private val _zoneOptions = MutableStateFlow<List<ZoneOption>>(emptyList())
val zoneOptions: StateFlow<List<ZoneOption>> = _zoneOptions
private val _supplierOptions = MutableStateFlow<List<SupplierOption>>(emptyList())
val supplierOptions: StateFlow<List<SupplierOption>> = _supplierOptions
/** 按溯源码查食材,结果通过 callback 返回 */
fun getIngredientByTrace(
traceCode: String,
callback: (List<IngredientByTrace>) -> Unit
) {
viewModelScope.launch {
val response = repository.getIngredientByTrace(traceCode)
if (response.isSuccess()) {
callback(response.data ?: emptyList())
} else {
Timber.e("getIngredientByTrace failed: ${response.msg}")
callback(emptyList())
}
}
}
/** 加载仓库区域列表,结果同时更新 StateFlow 并通过 callback 返回 */
fun loadZoneOptions(
warehouseId: Long? = null,
keyword: String? = null,
callback: ((List<ZoneOption>) -> Unit)? = null
) {
viewModelScope.launch {
val response = repository.getZoneOptions(warehouseId, keyword)
if (response.isSuccess()) {
val list = response.data ?: emptyList()
_zoneOptions.value = list
callback?.invoke(list)
} else {
Timber.e("loadZoneOptions failed: ${response.msg}")
callback?.invoke(emptyList())
}
}
}
/** 加载供应商列表,结果同时更新 StateFlow 并通过 callback 返回 */
fun loadSupplierOptions(
type: Int? = null,
keyword: String? = null,
callback: ((List<SupplierOption>) -> Unit)? = null
) {
viewModelScope.launch {
val response = repository.getSupplierOptions(type, keyword)
if (response.isSuccess()) {
val list = response.data ?: emptyList()
_supplierOptions.value = list
callback?.invoke(list)
} else {
Timber.e("loadSupplierOptions failed: ${response.msg}")
callback?.invoke(emptyList())
}
}
}
/** 毛菜入库提交 */
fun submitRawInbound(param: Map<String, Any>, callback: (Boolean, String?) -> Unit) {
viewModelScope.launch {
val response = repository.submitRawInbound(param)
callback(response.isSuccess(), response.msg)
}
}
/** 净菜入库提交 */
fun submitCleanInbound(param: Map<String, Any>, callback: (Boolean, String?) -> Unit) {
viewModelScope.launch {
val response = repository.submitCleanInbound(param)
callback(response.isSuccess(), response.msg)
}
}
}