Розробка мобільного додатку для управління арендою обладнання
Аренда обладнання—інструменти, будівельна техніка, аудіо/відео, медичне обладнання—це історія про облік: хто взяв, на скільки, в якому стані, коли повернув та не зломав ли. Мобільний додаток вирішує два завдання одночасно: клієнтський додаток для бронювання та операторський для управління парком.
Інвентаризація через 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), історію аренд.
Акт видачи з фотофіксацією
Перед видачею обладнання оператор фотографує його стан. Фото прикріплюються до договору аренди як доказ комплектності. Критично для розрішення спорів при поверненні.
// 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 МБ для цього завдання надлишковий.
Бронювання з перевіркою доступності
Клієнтський додаток показує календар доступності обладнання. Пересічення брой недопустимі. Логіка перевірки—на серверу, але 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 при поверненні
}
Ориентири по терміне
Базова версія (каталог обладнання, бронювання, QR-інвентаризація, оплата): 5–7 тижнів. Операторський модуль з фотофіксацією та актами—ще 2–3 тижні. Вартість розраховується індивідуально.







