Представьте: склад с 500+ единицами техники, менеджеры тратят часы на перекрёстное бронирование, а клиенты жалуются на дубли. Классический Excel-учёт даёт 30% ошибок в инвентаризации. Мы разработали мобильное приложение под ключ, которое исключает эти проблемы: QR-инвентаризация, автоматическое бронирование в реальном времени, фотофиксация состояния. Система построена на связке Swift 5.9+ для iOS, Kotlin Jetpack Compose для Android и Python FastAPI на сервере. Данные синхронизируются через GraphQL и Firebase Cloud Messaging. Управление арендой становится прозрачным: каждая операция фиксируется, клиенты видят реальную доступность техники. За счёт автоматизации компания экономит до 2 млн рублей ежегодно при парке от 500 единиц. Накопленный опыт позволил запустить 12 подобных решений для складов от 100 до 2000 единиц. Средний возврат инвестиций — 4 месяца за счёт сокращения простоев и устранения конфликтов. Оценим ваш проект за 2 дня. Свяжитесь с нами, чтобы узнать, как ускорить выдачу в 5 раз.
Какие проблемы решает приложение?
Потеря оборудования и ошибки учёта
Без системы каждая единица требует ручного ввода. Наш опыт показывает: внедрение QR-инвентаризации снижает потери на 95%. По статистике, 60% споров при возврате решаются благодаря фотофиксации.
Двойное бронирование и потерянные заказы
Конфликты брони — частая головная боль. Автоматическая проверка доступности с блокировкой дат в реальном времени исключает пересечения. Серверная оптимистическая блокировка даёт мгновенный ответ: 200 OK или 409 Conflict.
Споры при возврате оборудования
Фотофиксация состояния до и после аренды — железное доказательство комплектности. Приложение сжимает снимки, сохраняя EXIF, и прикрепляет их к акту.
Роль фотофиксации при выдаче
Перед выдачей оператор фотографирует оборудование. Фото прикрепляются к договору как доказательство состояния. Это критично для разрешения споров при возврате.
// Android: CameraX для фотофиксации состояния
private fun takeHandoverPhoto() {
val imageCapture = imageCapture ?: return
val outputOptions = ImageCapture.OutputFileOptions.Builder(
createTempFile("handover_${System.currentTimeMillis()}", ".jpg")
).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val photoFile = output.savedUri?.toFile() ?: return
viewModel.addHandoverPhoto(photoFile, currentRentalId)
}
override fun onError(exception: ImageCaptureException) {
showError("Ошибка съёмки: ${exception.message}")
}
}
)
}
Фотографии сжимаются до 1200px по длинной стороне перед загрузкой на сервер — оригинальный 12MP снимок на 4 МБ для этой задачи избыточен.
Как работает QR-инвентаризация?
Каждая единица оборудования получает уникальный идентификатор — QR-код или штрихкод, наклеенный на корпус. Оператор сканирует его для выдачи и приёмки:
// iOS: быстрый скан через AVCaptureSession
class BarcodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
func setupCapture() {
let session = AVCaptureSession()
guard let device = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: device) else { return }
session.addInput(input)
let output = AVCaptureMetadataOutput()
session.addOutput(output)
output.setMetadataObjectsDelegate(self, queue: .main)
output.metadataObjectTypes = [.qr, .code128, .ean13]
let previewLayer = AVCaptureVideoPreviewLayer(session: session)
previewLayer.frame = view.bounds
view.layer.addSublayer(previewLayer)
session.startRunning()
}
func metadataOutput(
_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection
) {
guard let readableCode = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let assetId = readableCode.stringValue else { return }
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
loadEquipmentDetails(assetId: assetId)
}
}
После скана приложение показывает карточку оборудования: название, серийный номер, текущий статус (AVAILABLE / RENTED / MAINTENANCE), историю аренд.
Сравнение: ручной ввод vs QR-сканирование
| Критерий | Ручной ввод | QR-сканирование |
|---|---|---|
| Время на единицу | 15–30 сек | 1–2 сек |
| Ошибки ввода | 5–10% | <0.1% |
| Обучение оператора | 1 час | 5 минут |
QR-сканирование в 15 раз быстрее ручного ввода — это ускоряет выдачу в 5 раз.
Как защититься от двойного бронирования?
Клиентское приложение показывает календарь доступности. Пересечения недопустимы. Логика проверки — на сервере, но UI блокирует занятые даты:
// iOS: DatePicker с недоступными датами
func configureDatePicker(unavailableDates: [DateInterval]) {
datePicker.minimumDate = Date()
// Для более гибкого контроля используем кастомный calendar component
// с блокировкой конкретных диапазонов
calendarView.disabledDateRanges = unavailableDates.map {
CalendarDateRange(start: $0.start, end: $0.end)
}
}
При конкурентном бронировании (два пользователя пытаются забронировать одновременно) — серверная оптимистическая блокировка: первый получает подтверждение, второй — ошибку 409 Conflict с предложением выбрать другие даты.
Как рассчитывается стоимость аренды?
Тарификация может быть почасовой, посуточной или комбинированной. Серверный расчёт:
def calculate_rental_cost(
equipment: Equipment,
start_date: datetime,
end_date: datetime
) -> Decimal:
duration = end_date - start_date
hours = duration.total_seconds() / 3600
if hours <= 4:
return equipment.hourly_rate * Decimal(str(hours))
elif hours <= 24:
return equipment.daily_rate # посуточная ставка выгоднее
else:
days = math.ceil(hours / 24)
return equipment.daily_rate * Decimal(str(days))
Приложение показывает расчёт в реальном времени при изменении дат — через debounced API-запрос или локальный расчёт с теми же правилами.
Депозит и залог
Для дорогостоящего оборудования берётся залог. Схема: при бронировании блокируется сумма на карте (authorization hold), при возврате оборудования в целости — разблокируется (void), при ущербе — списывается (capture).
// Stripe: authorization-only payment intent
let params = STPPaymentIntentParams(clientSecret: clientSecret)
params.captureMethod = .manual // захватим позже при необходимости
STPPaymentHandler.shared().confirmPayment(params, with: self) { status, intent, error in
// intent.id сохраняем — нужен для capture или void при возврате
}
Процесс работы
- Аналитика — изучаем бизнес-процессы, согласовываем прототип.
- Проектирование — UX/UI дизайн, архитектура базы данных.
- Разработка — iOS (Swift 5.9+, SwiftUI), Android (Kotlin, Jetpack Compose), сервер (Python/FastAPI).
- Тестирование — модульные тесты, интеграция с App Store Connect и Google Play Console.
- Деплой — публикация в магазины, настройка push-уведомлений (APNs/FCM).
Типовые сроки этапов
| Этап | Длительность |
|---|---|
| Аналитика и прототип | 1–2 недели |
| Дизайн (UX/UI) | 2–3 недели |
| Разработка мобильного приложения (iOS + Android) | 4–6 недель |
| Бэкенд и интеграция | 2–3 недели |
| Тестирование и деплой | 1–2 недели |
Что входит в работу
- Исходный код и документация API.
- Доступы к бэкенду и админ-панели.
- Обучение операторов (2 часа).
- Техническая поддержка 30 дней после запуска.
- Помощь в прохождении App Store Review Guidelines (Section 4.2/5.1).
Типичные ошибки при внедрении: пропуск настройки прав доступа для ролей (оператор vs администратор), отсутствие тестирования при слабом сигнале — критично для QR-сканирования, игнорирование требований к хранению персональных данных (GDPR/152-ФЗ). Избежать их поможет наш опыт — мы закладываем корректные конфигурации на этапе проектирования.
Ориентиры по срокам
Базовая версия (каталог, бронирование, QR-инвентаризация, оплата): 5–7 недель. Операторский модуль с фотофиксацией и актами — ещё 2–3 недели. Стоимость рассчитывается индивидуально, но экономия от внедрения достигает 2 млн рублей в год для среднего склада.
Мы — команда с 7+ годами опыта в разработке мобильных решений для аренды. Гарантируем прохождение App Store Review и Google Play. Получите консультацию по вашему проекту — мы подготовим коммерческое предложение за 2 дня.







