This commit is contained in:
zxj
2025-07-08 15:40:39 +08:00
parent d2698ed60f
commit 4501b6d5fc
132 changed files with 8998 additions and 0 deletions
@@ -0,0 +1,151 @@
package com.sw.inbound.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sw.inbound.GlobalData
import com.sw.inbound.model.response.ApiResponse
import com.sw.inbound.model.response.DictType
import com.sw.inbound.network.LoadingState
import com.sw.inbound.repository.RemoteRepository
import com.sw.inbound.utils.ToastUtils
import kotlinx.coroutines.launch
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException
abstract class BaseViewModel(
private val repository: RemoteRepository
) : ViewModel() {
protected fun launchWithLoading(block: suspend () -> Unit) {
viewModelScope.launch {
try {
LoadingState.show()
block()
} catch (e: Exception) {
// 错误处理可被子类重写
handleError(e)
} finally {
LoadingState.hide()
}
}
}
protected fun launch(block: suspend () -> Unit) {
viewModelScope.launch() {
try {
block()
} catch (e: Exception) {
// 错误处理可被子类重写
handleError(e)
}
}
}
protected open fun parseResponse(response: ApiResponse<*>): Boolean {
if (response.isSuccess()) {
return true
}
Timber.d("msg = ${response.msg}, code = ${response.code}")
ToastUtils.showToast("${response.msg}(${response.code})")
return false
}
protected open fun handleError(e: Exception) = {
Timber.e(e)
val message = when (e) {
is IOException -> "网络连接异常"
is HttpException -> "服务器错误: ${e.code()}"
else -> "操作失败: ${e.message}"
}
ToastUtils.showToast(message)
}
fun getDictType() {
Timber.d("获取所有字典列表")
launchWithLoading {
val response = repository.getGoodsStorageType()
if (response.isSuccess()) {
Timber.d("getGoodsStorageType data = ${response.data}")
response.data?.let {
// val dictType = DictType(response.data)
val list = mutableListOf<DictType>()
for (type in response.data) {
list.add(DictType(type.itemValue!!.toInt(), type.itemText!!))
}
GlobalData.storageTypeList = list
}
} else {
Timber.e("getGoodsStorageType msg = ${response.msg}, code = ${response.code}")
}
val response1 = repository.getGoodsType()
if (response1.isSuccess()) {
Timber.d("getGoodsType data = ${response1.data?.allType}")
response1.data?.let {
if (response1.data.allType != null) {
val list = mutableListOf<DictType>()
for (type in response1.data.allType) {
list.add(DictType(type!!.id!!, type.typeName!!))
}
GlobalData.goodsTypeList = list
}
}
} else {
Timber.e("getGoodsType msg = ${response1.msg}, code = ${response1.code}")
}
val response2 = repository.getDictType(RemoteRepository.TypeEnum.WAREHOUSE)
if (response2.isSuccess()) {
Timber.d("getDictType WAREHOUSE data = ${response2.data}")
response2.data?.let {
GlobalData.warehouseTypeList = response2.data
}
} else {
Timber.e("getDictType WAREHOUSE msg = ${response2.msg}, code = ${response2.code}")
}
val response3 = repository.getDictType(RemoteRepository.TypeEnum.SUPPLIER)
if (response3.isSuccess()) {
Timber.d("getDictType SUPPLIER data = ${response3.data}")
response3.data?.let {
GlobalData.supplierTypeList = response3.data
}
} else {
Timber.e("getDictType SUPPLIER msg = ${response3.msg}, code = ${response3.code}")
}
val response4 = repository.getDictType(RemoteRepository.TypeEnum.UNIT)
if (response4.isSuccess()) {
Timber.d("getDictType UNIT data = ${response4.data}")
response4.data?.let {
GlobalData.unitTypeList = response4.data
}
} else {
Timber.e("getDictType UNIT msg = ${response4.msg}, code = ${response4.code}")
}
}
}
fun getStoreList(): ArrayList<String> {
return arrayListOf(
"默认仓库",
"仓库1",
"仓库2",
"仓库3",
)
}
fun getPurchasingUnit(): ArrayList<String> {
return arrayListOf(
"",
"",
""
)
}
fun getProductList(): ArrayList<String> {
return arrayListOf<String>(
"胶东大白菜",
"玉田尖白菜1",
"玉田尖白菜2",
"玉田尖白菜3",
"玉田尖白菜4"
)
}
}
@@ -0,0 +1,31 @@
package com.sw.inbound.viewmodel
import com.sw.inbound.model.response.SupplierInfo
import com.sw.inbound.repository.RemoteRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@HiltViewModel
class ProductViewModel @Inject constructor(
private val repository: RemoteRepository
) : BaseViewModel(repository) {
private val _supplierList = MutableStateFlow<List<SupplierInfo?>>(emptyList())
val supplierList: StateFlow<List<SupplierInfo?>> = _supplierList
fun getOrderList(pageNum: Int = 0, pageSize: Int = 20) {
launchWithLoading {
val response = repository.getReceiveList(pageNum, pageSize)
if (response.isSuccess()) {
response.data?.records?.let {
_supplierList.value = it
}
}
}
}
}
@@ -0,0 +1,237 @@
package com.sw.inbound.viewmodel
import androidx.lifecycle.viewModelScope
import com.sw.inbound.ext.toSafeBigDecimal
import com.sw.inbound.ext.toSafeFloat
import com.sw.inbound.model.request.UploadInfo
import com.sw.inbound.model.response.DictType
import com.sw.inbound.model.response.GoodsInfo
import com.sw.inbound.model.response.PurchaseInfo
import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.repository.RemoteRepository
import com.sw.inbound.sdk.SensorScaleUtils
import com.sw.inbound.utils.ToastUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class ReceiptViewModel @Inject constructor(
private val repository: RemoteRepository
) : BaseViewModel(repository) {
// 调整状态
private val _adjustState = MutableStateFlow<Boolean>(true)
val adjustState: StateFlow<Boolean> = _adjustState
private val _showReceiptDialog = MutableStateFlow(false)
val showReceiptDialog: StateFlow<Boolean> = _showReceiptDialog
// 当前要收货的供应商
private val _currentPurchaseInfo = MutableStateFlow<PurchaseInfo?>(null)
val currentPurchaseInfo: StateFlow<PurchaseInfo?> = _currentPurchaseInfo
// 自动从 currentPurchaseInfo 派生 orders
// private val _orders: StateFlow<List<GoodsInfo>> = currentPurchaseInfo
// .map { purchaseInfo ->
// purchaseInfo?.receiveGoodsInfoList ?: emptyList()
// }
// .stateIn(
// viewModelScope,
// SharingStarted.WhileSubscribed(5000), // 或者使用 Lazily/Eagerly 根据需求
// emptyList()
// )
private val _orders = MutableStateFlow<List<GoodsInfo>>(emptyList())
val orders: StateFlow<List<GoodsInfo>> = _orders.asStateFlow()
fun initOrders() {
_orders.value = _currentPurchaseInfo.value?.receiveGoodsInfoList ?: emptyList()
}
// 已调整列表
val adjustedOrders: StateFlow<List<GoodsInfo>> = _orders
.map { orders -> orders.filter { it.isAdjusted && it.goodId != null } }
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
// 未调整列表
val unadjustedOrders: StateFlow<List<GoodsInfo>> = _orders
.map { orders -> orders.filter { !it.isAdjusted && it.goodId != null } }
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
val mergedOrders: StateFlow<List<GoodsInfo>> = _orders
//
private val _selectedItem = MutableStateFlow<GoodsInfo?>(null)
val selectedItem: StateFlow<GoodsInfo?> = _selectedItem
// 搜索物品列表
private val _searchListItems = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
val searchListItems: StateFlow<List<SearchGoodsInfo.Record>> = _searchListItems
private val _receiptResult = MutableStateFlow<Boolean>(false)
val receiptResult: StateFlow<Boolean> = _receiptResult
private val _countUserInput = MutableStateFlow<Boolean>(false)
/**
* 更新调整状态
* @param isAdjustState true 已调整 false 未调整
*/
fun updateAdjustState(isAdjustState: Boolean) {
_adjustState.value = isAdjustState
}
/**
* 更新收货提示弹窗
*/
fun updateReceiptDialog(showDialog: Boolean) {
_showReceiptDialog.value = showDialog
}
/**
* 更新选中的item
*/
fun updateSelectedItem(purchaseOrder: GoodsInfo?) {
_selectedItem.value = purchaseOrder
}
fun getReceiveDetail(id: Int) {
launchWithLoading {
val response = repository.getReceiveDetail(id)
if (response.isSuccess()) {
_currentPurchaseInfo.value = response.data
initOrders()
}
}
}
/**
* 更新当前供应商信息
*/
fun updateCurrentPurchaseInfo(purchaseInfo: PurchaseInfo?) {
_currentPurchaseInfo.value = purchaseInfo
}
// 添加订单
fun addPurchaseItem(purchaseOrder: GoodsInfo) {
_orders.update { currentList ->
// 当已经添加过则忽略
if (currentList.any { it.goodId == purchaseOrder.goodId }) {
currentList
} else {
currentList + purchaseOrder
}
}
}
// 更新订单
fun updatePurchaseItem(purchaseOrder: GoodsInfo) {
_orders.update { currentList ->
currentList.map { order ->
if (order.goodId == purchaseOrder.goodId) purchaseOrder else order
}
}
}
/**
* 有异常的订单数量
*/
fun hasWrongCount(): Boolean {
return _orders.value.any {
it.receiveCount != it.receivedNum
}
}
/**
* 更新所有商品仓库
*/
fun updateAllPurchaseStore(store: DictType, predicate: (GoodsInfo) -> Boolean = { true }) {
_orders.update { currentList ->
currentList.map { order ->
if (predicate(order)) order.copy(
warehouseId = store.id,
warehouseName = store.value
) else order
}
}
}
// 删除订单
fun removePurchaseOrder(orderId: Int) {
_orders.update { current ->
current.filterNot { it.goodId == orderId }
}
}
// 清空列表
fun clearPurchaseOrders() {
_orders.value = emptyList()
}
fun updateCountInputState(boolean: Boolean) {
_countUserInput.value = boolean
}
fun startSensorScale() {
Timber.d("开始称重")
SensorScaleUtils.startScale(callback = { weight ->
val currentItem = _selectedItem.value
currentItem?.let { info ->
val consumeValue = currentItem.consumeValue?.toDouble() ?: 1.0
val purchaseValue = currentItem.purchaseValue?.toDouble() ?: 1.0
val count =
weight * 1000 / consumeValue / purchaseValue
val finalCount = count.toSafeFloat(currentItem.unitName)
Timber.d("startSensorScale weight = $weight, count = $count, finalCount = $finalCount, consumeValue = $consumeValue, purchaseValue = $purchaseValue")
_selectedItem.value = info.copy(
goodsWeight = weight.toSafeBigDecimal(),
receivedNum = if (_countUserInput.value) info.receivedNum else finalCount
)
}
})
}
fun stopSensorScale() {
Timber.d("关闭称重")
SensorScaleUtils.stopContinuousRead()
}
fun partialReceipt(uploadInfo: UploadInfo) {
launchWithLoading {
val response = repository.partialReceipt(uploadInfo)
if (!response.isSuccess()) {
parseResponse(response)
return@launchWithLoading
}
if (response.success == true) {
ToastUtils.showToast("部分收货成功")
_receiptResult.value = true
} else {
ToastUtils.showToast("部分收货失败")
}
}
}
fun confirmReceipt(uploadInfo: UploadInfo) {
launchWithLoading {
val response = repository.confirmReceipt(uploadInfo)
if (!response.isSuccess()) {
parseResponse(response)
return@launchWithLoading
}
if (response.success == true) {
ToastUtils.showToast("收货成功")
_receiptResult.value = true
} else {
ToastUtils.showToast("收货失败")
}
}
}
}
@@ -0,0 +1,231 @@
package com.sw.inbound.viewmodel
import androidx.lifecycle.viewModelScope
import com.sw.inbound.GlobalData
import com.sw.inbound.ext.toSafeBigDecimal
import com.sw.inbound.ext.toSafeDouble
import com.sw.inbound.model.request.GoodsAddParam
import com.sw.inbound.model.request.PurchaseWarehouseParam
import com.sw.inbound.model.response.DictType
import com.sw.inbound.model.response.SearchGoodsInfo
import com.sw.inbound.repository.RemoteRepository
import com.sw.inbound.sdk.SensorScaleUtils
import com.sw.inbound.utils.ToastUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class SelfProcurementViewModel @Inject constructor(
private val repository: RemoteRepository
) : BaseViewModel(repository) {
private val _addGoodsResult = MutableStateFlow<Boolean>(false)
val addGoodsResult: StateFlow<Boolean> = _addGoodsResult
private val _addToWarehouseResult = MutableStateFlow<Boolean>(false)
val addToWarehouseResult: StateFlow<Boolean> = _addToWarehouseResult
private val _selectedItem = MutableStateFlow<PurchaseWarehouseParam?>(null)
val selectedItem: StateFlow<PurchaseWarehouseParam?> = _selectedItem
// 全局仓库信息
private val _globalWarehouse = MutableStateFlow<DictType>(DictType(-1, "选择仓库"))
val globalWarehouse: StateFlow<DictType> = _globalWarehouse
private val _purchaseList = MutableStateFlow<List<PurchaseWarehouseParam>>(emptyList())
val purchaseList: StateFlow<List<PurchaseWarehouseParam>> = _purchaseList
// 表单状态
private val _goodsAddParam = MutableStateFlow<GoodsAddParam>(GoodsAddParam())
val goodsAddParam: StateFlow<GoodsAddParam> = _goodsAddParam
// 快速添加弹窗状态
private val _showAddProductDialog = MutableStateFlow<Boolean>(false)
val showAddProductDialog: StateFlow<Boolean> = _showAddProductDialog
// 搜索物品列表
private val _searchListItems = MutableStateFlow<List<SearchGoodsInfo.Record>>(emptyList())
val searchListItems: StateFlow<List<SearchGoodsInfo.Record>> = _searchListItems
// 称重结果
private val _weightInfo = MutableStateFlow<Double?>(0.0)
val weightInfo: StateFlow<Double?> = _weightInfo
/**
* 数量是否是用户输入的值
*/
private val _countUserInput = MutableStateFlow<Boolean>(false)
/**
* 更新全局仓库
*/
fun updateGlobalWarehouse(dictType: DictType) {
_globalWarehouse.value = dictType
}
fun startSensorScale() {
Timber.d("开始称重")
SensorScaleUtils.startScale(callback = { weight ->
val currentItem = _selectedItem.value
currentItem?.let { info ->
val selectUnitType = currentItem.selectUnitType
if (selectUnitType == null) return@startScale
val consumeValue = selectUnitType.consumeValue?.toDouble() ?: 1.0
val purchaseValue = selectUnitType.purchaseValue?.toDouble() ?: 1.0
val count =
weight * 1000 / consumeValue / purchaseValue
val finalCount = count.toSafeDouble(currentItem.unitName)
Timber.d("startSensorScale weight = $weight, count = $count, finalCount = $finalCount, consumeValue = $consumeValue, purchaseValue = $purchaseValue")
_selectedItem.value = info.copy(
goodsWeight = weight.toSafeBigDecimal(),
goodsCount = if (_countUserInput.value) info.goodsCount else finalCount
)
}
})
}
fun stopSensorScale() {
Timber.d("关闭称重")
SensorScaleUtils.stopContinuousRead()
}
fun updateAddProductDialog(show: Boolean) {
_showAddProductDialog.value = show
}
fun updateSelectedItem(purchaseOrder: PurchaseWarehouseParam?) {
_selectedItem.value = purchaseOrder
}
fun updateGoodsAddParam(newState: GoodsAddParam) {
_goodsAddParam.value = newState
}
fun cleanGoodsAddParam() {
_goodsAddParam.value = GoodsAddParam()
}
// 添加订单
fun addPurchaseItem(purchaseOrder: PurchaseWarehouseParam) {
_purchaseList.update { currentList ->
// 当已经添加过则忽略
if (currentList.any { it.goodsId == purchaseOrder.goodsId }) {
currentList
} else {
currentList + purchaseOrder
}
}
updateSelectedItem(null)
}
// 更新订单
fun updatePurchaseItem(purchaseOrder: PurchaseWarehouseParam) {
_purchaseList.update { currentList ->
currentList.map { order ->
if (order.goodsId == purchaseOrder.goodsId) purchaseOrder else order
}
}
}
// 删除订单
fun removePurchaseOrder(orderId: Int) {
_purchaseList.update { current ->
current.filterNot { it.goodsId == orderId }
}
}
// 清空列表
fun clearPurchaseOrders() {
_purchaseList.value = emptyList()
}
fun searchGoodsInfoList(
goodsName: String,
pageNo: Int = 0,
pageSize: Int = 10
) {
launch {
val response = repository.searchGoodsInfoList(goodsName, pageNo, pageSize)
if (response.isSuccess()) {
val data = response.data
if (data != null) {
_searchListItems.value = data.records ?: emptyList<SearchGoodsInfo.Record>()
}
} else {
_searchListItems.value = emptyList<SearchGoodsInfo.Record>()
}
}
}
fun updateSelectedItemWithSearch(searchFirst: SearchGoodsInfo.Record) {
val warehouse = _globalWarehouse.value
viewModelScope.launch {
updateSelectedItem(null)
delay(50)
_selectedItem.value = PurchaseWarehouseParam(
warehouseId = warehouse.id,
goodsId = searchFirst.goodsId!!,
goodsName = searchFirst.goodsName,
kcUnitId = searchFirst.kcUnitId!!,
unitList = searchFirst.unitVoList,
)
}
}
fun updateCountInputState(boolean: Boolean) {
_countUserInput.value = boolean
}
fun addGoodsInfo() {
val imageUri = GlobalData.imageUri
if (imageUri == null) {
ToastUtils.showToast("请先进行图片采集")
return
}
val errInfo = _goodsAddParam.value.hasNullField()
if (errInfo != null) {
ToastUtils.showToast(errInfo)
return
}
launchWithLoading {
val uploadResponse = repository.uploadImage(imageUri)
if (!uploadResponse.isSuccess()) {
parseResponse(uploadResponse)
return@launchWithLoading
}
// ToastUtils.showToast("图片上传成功,${uploadResponse.data}")
GlobalData.imageUri = null
_goodsAddParam.value.relativeUrl = uploadResponse.data
val response = repository.selfPurchaseGoodsAdd(_goodsAddParam.value)
if (!response.isSuccess()) {
parseResponse(response)
return@launchWithLoading
}
_showAddProductDialog.value = false
val searchFirst = response.data!!.records?.get(0)
searchFirst?.let {
updateSelectedItemWithSearch(searchFirst)
}
_addGoodsResult.value = true
}
}
fun addToWarehouse() {
if (_purchaseList.value.isEmpty()) return
launchWithLoading {
val response = repository.selfPurchaseWarehousing(_purchaseList.value)
if (parseResponse(response)) {
_addToWarehouseResult.value = true
}
}
}
}
@@ -0,0 +1,66 @@
package com.sw.inbound.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.sw.inbound.GlobalKey
import com.sw.inbound.model.request.LoginParam
import com.sw.inbound.model.response.User
import com.sw.inbound.repository.RemoteRepository
import com.sw.inbound.utils.GsonUtils
import com.sw.inbound.utils.SPUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class UserViewModel @Inject constructor(
private val repo: RemoteRepository
) : BaseViewModel(repo) {
private var isFirstLaunch by mutableStateOf(true)
private val _user = MutableStateFlow<User?>(null)
val user = _user.asStateFlow()
fun getInitInfo() {
Timber.d("getInitInfo isFirstLaunch = $isFirstLaunch")
if (isFirstLaunch) {
getUserInfo()
getDictType()
isFirstLaunch = false
}
}
fun login(userName: String, password: String) = launchWithLoading {
val loginParam = LoginParam(userName = userName, password = password)
val response = repo.login(loginParam)
if (response.isSuccess()) {
_user.value = response.data
saveToken(response.data)
response.data?.token
}
}
fun getUserInfo() {
val userInfo = SPUtil.getInstance().get(GlobalKey.KEY_USER_INFO, "")
if (userInfo != null) {
_user.value = GsonUtils.fromJson(userInfo, User::class.java)
}
}
private fun saveToken(user: User?) {
user?.let {
SPUtil.getInstance().put(GlobalKey.KEY_USER_INFO, GsonUtils.toJson(it))
SPUtil.getInstance().put(GlobalKey.KEY_TOKEN, it.token)
}
}
fun logout() {
SPUtil.getInstance().remove(GlobalKey.KEY_USER_INFO)
SPUtil.getInstance().remove(GlobalKey.KEY_TOKEN)
}
}