Представьте: склад с 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 дня.







