Разработка мобильного приложения для управления складом (WMS)
Мобильный WMS — это не просто сканирование штрихкодов. Это интерфейс между складским работником и системой управления запасами: приём товара, размещение на позиции, отбор для заказа, перемещение между зонами, инвентаризация, отгрузка. Каждая операция требует подтверждения через сканирование, а приложение работает в условиях слабого Wi-Fi, резиновых перчаток и 10-часовых смен.
Аппаратура: ТСД и мобильные компьютеры
Потребительский смартфон на складе — временное решение. Профессиональные ТСД (Zebra MC9300, Honeywell CT40, Point Mobile PM90) — это IP65/IP67 защита, сменные аккумуляторы, встроенный лазерный сканер с дальностью 10 метров и поддержкой всех одномерных и двумерных кодов.
Разработка для Zebra: DataWedge настраивается через Intent API — приложение декларирует профиль в datawedge.db или программно через broadcast. Сканирование приходит как Intent с extra com.symbol.datawedge.data_string:
class WarehouseActivity : AppCompatActivity() {
private val scanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val barcode = intent.getStringExtra("com.symbol.datawedge.data_string") ?: return
val source = intent.getStringExtra("com.symbol.datawedge.label_type") // EAN13, CODE128, QR_CODE
viewModel.processBarcode(barcode, source)
}
}
override fun onResume() {
super.onResume()
registerReceiver(scanReceiver, IntentFilter("com.symbol.datawedge.api.RESULT_ACTION"))
// Включаем DataWedge profile для нашего приложения
sendDataWedgeIntent("com.symbol.datawedge.api.SWITCH_TO_PROFILE", "WarehousePro")
}
}
Для Honeywell — Honeywell Scanning SDK (аналогичный Intent-механизм). Для универсальности: абстракция ScannerProvider с реализациями ZebraScanner, HoneywellScanner, CameraScanner. Определяем тип устройства через Build.MANUFACTURER.
Задания и очереди: серверная модель
Складской работник видит список заданий, отсортированных по приоритету и маршруту (чтобы минимизировать перемещения по складу). Задание содержит: тип операции, список товарных позиций с артикулами и количеством, адреса ячеек (стеллаж-ярус-позиция).
@Entity(tableName = "tasks")
data class WarehouseTask(
@PrimaryKey val taskId: String,
val type: TaskType, // RECEIVING, PUTAWAY, PICKING, REPLENISHMENT, INVENTORY
val status: TaskStatus, // ASSIGNED, IN_PROGRESS, COMPLETED, BLOCKED
val priority: Int,
val assignedUserId: String,
val lines: List<TaskLine>,
val syncedAt: Long,
)
@Entity(tableName = "task_lines")
data class TaskLine(
@PrimaryKey val lineId: String,
val taskId: String,
val sku: String,
val description: String,
val expectedQty: Double,
val actualQty: Double?,
val fromLocation: String?,
val toLocation: String,
val lotNumber: String?,
val expiryDate: String?,
)
Офлайн-режим: при потере Wi-Fi задания доступны из Room. Все операции записываются локально с timestamp, синхронизируются пакетом при восстановлении соединения. Конфликт версий (другой пользователь обработал ту же позицию) — сервер разрешает по timestamp, приложение уведомляет об изменении.
Отбор по маршруту (Picking)
Отбор — самая критичная операция по времени. Сортировка заданий по маршруту обхода: алгоритм S-образного маршрута (Serpentine) или ближайшего соседа по адресам ячеек. Реализуется на бэкенде, мобильное приложение получает уже отсортированный список.
Визуальный индикатор текущей ячейки: выделение на схеме склада или просто крупный адрес (A-12-03) на весь экран. Подтверждение через сканирование адреса ячейки — защита от ошибок. После сканирования ячейки — сканирование штрихкода товара, ввод количества (или автоматически 1 если единичный отбор).
sealed class PickingStep {
object ScanLocation : PickingStep()
data class ScanItem(val expectedSku: String) : PickingStep()
data class EnterQuantity(val sku: String, val required: Double) : PickingStep()
data class Completed(val lineId: String) : PickingStep()
}
class PickingViewModel : ViewModel() {
private val _step = MutableStateFlow<PickingStep>(PickingStep.ScanLocation)
fun processBarcode(barcode: String) {
when (val step = _step.value) {
is PickingStep.ScanLocation -> {
if (barcode == currentLine.toLocation) {
_step.value = PickingStep.ScanItem(currentLine.sku)
} else {
_events.tryEmit(Event.WrongLocation(barcode, currentLine.toLocation))
}
}
is PickingStep.ScanItem -> {
if (barcode == step.expectedSku || isAlias(barcode, step.expectedSku)) {
_step.value = PickingStep.EnterQuantity(step.expectedSku, currentLine.expectedQty)
} else {
_events.tryEmit(Event.WrongItem(barcode))
}
}
else -> { /* обработка других шагов */ }
}
}
}
Интеграция с WMS/ERP
Типичные интеграции: SAP WM/EWM через RFC/BAPI или REST (SAP API Hub), 1С:УПП/ERP через HTTP-сервис, Microsoft Dynamics 365 через Dataverse API, кастомные WMS через REST/gRPC.
Стратегия интеграции: не дёргать ERP напрямую из мобильного — ставим промежуточный сервис (middleware), который буферизует операции, обрабатывает ошибки ERP и не роняет мобильный клиент при 30-секундных ответах SAP.
Разработка мобильного WMS-приложения с поддержкой ТСД Zebra/Honeywell, офлайн-режимом, отбором по маршруту и REST-интеграцией: 3-5 месяцев. Полный цикл (приём, размещение, отбор, инвентаризация, отгрузка) с интеграцией SAP/1С: 5-8 месяцев. Стоимость рассчитывается индивидуально.







