← Назад к вопросам
Декомпозировать требования для экрана мобильного приложения
2.0 Middle🔥 231 комментариев
#Требования и их анализ
Условие
Вам показывают экран мобильного приложения доставки еды — экран оформления заказа.
На экране:
- Список выбранных блюд с количеством и ценой
- Кнопки "+" и "-" для изменения количества
- Поле ввода промокода
- Выбор адреса доставки
- Выбор времени доставки
- Способ оплаты
- Итоговая сумма
- Кнопка "Оформить заказ"
Задача:
- Опишите процесс работы этого экрана своими словами
- Расскажите, какие технические детали нужно учесть
- Декомпозируйте требования в отдельные задачи для разработчиков
- Укажите, какие API-эндпоинты понадобятся для работы экрана
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Декомпозиция экрана оформления заказа в приложении доставки еды
1. Описание процесса работы экрана
Экран оформления заказа — это финальный шаг в воронке покупки, где пользователь подтверждает выбранные блюда, применяет скидки, выбирает условия доставки и способ оплаты.
Основной процесс:
- Пользователь видит список уже выбранных блюд из ресторана (из предыдущего экрана)
- Может изменить количество каждого блюда (+/-) или удалить его
- Вводит промокод для получения скидки
- Выбирает адрес доставки из сохранённых адресов или вводит новый
- Выбирает время доставки (АСАП, конкретный час, конкретное время)
- Выбирает способ оплаты (карта, кошельки, наличные при доставке)
- Видит автоматически рассчитанную итоговую стоимость с учетом:
- Суммы блюд (с учётом изменённого количества)
- Скидки от промокода
- Стоимости доставки
- Налогов (если применимо)
- Нажимает кнопку "Оформить заказ" для создания заказа
Состояния экрана:
- Loading: При загрузке сохранённых адресов
- Ready: Готов к оформлению
- Validating: Проверка промокода, расчёт доставки
- Processing: Создание заказа на сервере
- Success: Заказ создан успешно
- Error: Ошибка при создании или валидации
2. Технические детали
2.1 Управление состоянием
- Выбранные блюда: Хранятся в Redux/Zustand (или контексте React)
- Кэширование адресов: Локально на устройстве + периодическое обновление с сервера
- Вычисляемые значения: Итоговая сумма вычисляется локально при каждом изменении
2.2 Производительность
- Дебоунсинг: При изменении количества не отправляем запрос сразу (ждём 500ms)
- Ленивая загрузка: Список адресов загружается при первом открытии поля выбора
- Кэширование промокодов: Последние 5 использованных кодов кэшируются локально
- Оптимистичное обновление: При изменении количества блюда сразу обновляем UI (не ждём ответ сервера для расчета суммы)
2.3 Валидация
-
Клиентская валидация:
- Минимальная сумма заказа (если есть)
- Выбран ли адрес доставки
- Выбран ли способ оплаты
- Не пуста ли корзина
-
Серверная валидация:
- Промокод актуален и применим к этому ресторану
- Адрес доставки в зоне доставки ресторана
- Способ оплаты доступен в этом регионе
- Статус ресторана: открыт/закрыт
2.4 Обработка ошибок
- Сеть недоступна: Сохраняем состояние локально, попытка отправки при восстановлении
- Сервер отвечает ошибкой: Показываем понятное сообщение на UI
- Промокод неверный: Очищаем поле, показываем сообщение об ошибке
- Адрес вне зоны доставки: Блокируем кнопку "Оформить заказ", показываем сообщение
2.5 Безопасность
- Не отправляем пароли: Для любых операций используем JWT токен
- HTTPS only: Все запросы зашифрованы
- Валидация на сервере: Все входные данные валидируются, несмотря на клиентскую валидацию
- Защита от дублей: При двойном нажатии "Оформить заказ" серверный ключ идемпотентности предотвращает создание дублей
2.6 Доступность
- Поддержка скрин-ридера: Все элементы имеют ARIA-метки
- Клавиатурная навигация: Можно перемещаться стрелками и Tab
- Контраст: Цвета соответствуют WCAG AA
- Размер кнопок: Минимум 48x48 pt для сенсорных экранов
3. Декомпозиция на задачи для разработчиков
Epic: Экран оформления заказа
Story 1: Отображение выбранных блюд
As a User
I want to see all dishes I selected with their quantities and prices
So that I can review my order before checkout
Acceptance Criteria:
- [ ] Показать список выбранных блюд с изображениями (thumbnail)
- [ ] Для каждого блюда показать: название, количество, цена за единицу, сумма
- [ ] Блюда скроллируются если их много (max 3-4 видно без скролла)
- [ ] Нет пустого списка: если корзина пуста, показать сообщение
Tasks:
1. Create DishItem component (UI)
2. Create DishList component (список)
3. Connect to Redux/store (получить блюда)
4. Add scrolling behavior
5. Write unit tests (98%+ coverage)
Story 2: Управление количеством блюд
As a User
I want to change the quantity of each dish using +/- buttons
So that I can adjust my order
Acceptance Criteria:
- [ ] Кнопка "-" уменьшает количество на 1 (минимум 1)
- [ ] Кнопка "+" увеличивает количество (максимум 99)
- [ ] При количестве 1, кнопка "-" показывает иконку удаления (корзина)
- [ ] При нажатии корзины блюдо удаляется из заказа
- [ ] Сумма блюда пересчитывается в реальном времени
Tasks:
1. Create QuantityControl component with +/- buttons
2. Implement delete functionality
3. Add haptic feedback (vibration) при нажатии кнопки
4. Debounce quantity changes (500ms) для запроса доставки
5. Write tests for edge cases (min/max values)
Story 3: Ввод и проверка промокода
As a User
I want to enter a promo code to get a discount
So that I can save money on my order
Acceptance Criteria:
- [ ] Поле ввода промокода
- [ ] Кнопка "Apply" применяет код
- [ ] При применении отправляется запрос на сервер
- [ ] Если код валиден, показать размер скидки зелёным цветом
- [ ] Если код невалиден, показать ошибку красным цветом
- [ ] Кнопка "X" убирает промокод и возвращает полную сумму
- [ ] Ошибки: код истёк, не применим к ресторану, минимальная сумма не достигнута
Tasks:
1. Create PromoCodeInput component
2. Integrate with PromoCode API
3. Implement local caching of last 5 codes
4. Add error handling and messages
5. Update total price calculation
6. Write tests for validation logic
Story 4: Выбор адреса доставки
As a User
I want to select or enter a delivery address
So that the restaurant knows where to deliver my food
Acceptance Criteria:
- [ ] Показать список сохранённых адресов (макс 3)
- [ ] Возможность добавить новый адрес (кнопка "+")
- [ ] При выборе адреса показать метку выбора
- [ ] Для нового адреса: поле для ввода улицы, дома, квартиры
- [ ] Валидация адреса перед отправкой (не пуст, корректный формат)
- [ ] Проверка: адрес в зоне доставки этого ресторана
- [ ] При вводе адреса: автодополнение (Google Places API / Yandex Geocoder)
Tasks:
1. Create AddressList component
2. Create AddressForm component (для нового адреса)
3. Integrate with maps API (geocoding)
4. Validate address on server (zone check)
5. Load saved addresses from API on mount
6. Cache addresses locally (IndexedDB)
7. Tests for address validation
Story 5: Выбор времени доставки
As a User
I want to choose when I want my food delivered
So that I can control when my order arrives
Acceptance Criteria:
- [ ] Опция "АСАП" (если ресторан открыт)
- [ ] Опция выбора конкретного часа (9:00, 10:00, ..., 23:00)
- [ ] Опция выбора конкретного времени с интервалом 15 минут
- [ ] Минимальное время доставки: 20 минут от текущего момента
- [ ] Максимальное время: 8 часов от текущего момента
- [ ] Время автоматически обновляется если ресторан закроется в течение выбранного срока
Tasks:
1. Create DeliveryTimePicker component
2. Implement time validation logic
3. Calculate min/max delivery times
4. Handle timezone (user's timezone vs server)
5. Add time formatting (12h vs 24h based on locale)
6. Tests for edge cases (midnight, restaurant hours)
Story 6: Выбор способа оплаты
As a User
I want to choose how to pay for my order
So that I can use my preferred payment method
Acceptance Criteria:
- [ ] Показать доступные способы: Карта, Электронный кошелёк, Наличные
- [ ] Для карты: список сохранённых карт или форма ввода новой
- [ ] Для кошелька: показать текущий баланс
- [ ] Для наличных: показать, что автоматическая сдача рассчитается
- [ ] Выбранный способ отмечен галочкой
Tasks:
1. Create PaymentMethodSelector component
2. Create SavedCardsList component
3. Create CardForm component (для новой карты)
4. Integrate with Payment Gateway API
5. Show wallet balance (если есть)
6. Validate card data (Luhn algorithm на клиенте)
7. Tests for payment validation
Story 7: Расчёт итоговой суммы
As a System
I want to calculate the total order amount including delivery and taxes
So that the user sees the correct amount to pay
Acceptance Criteria:
- [ ] Показать строкой: Сумма блюд
- [ ] Показать строкой: Скидка (промокод)
- [ ] Показать строкой: Стоимость доставки (зависит от адреса)
- [ ] Показать строкой: Налог (если применимо)
- [ ] Показать ИТОГО жирным шрифтом
- [ ] Обновляется в реальном времени при изменении количества или промокода
Tasks:
1. Create PriceBreakdown component
2. Implement calculation logic:
- itemsTotal = sum(quantity * price)
- discount = applyPromoCode(itemsTotal)
- deliveryFee = calculateDelivery(address, itemsTotal)
- tax = calculateTax(itemsTotal, deliveryFee, region)
- total = itemsTotal - discount + deliveryFee + tax
3. Handle rounding (2 decimal places)
4. Format currency based on locale
5. Tests for calculation accuracy
Story 8: Валидация и оформление заказа
As a User
I want to place my order after validating all required fields
So that my order is successfully created
Acceptance Criteria:
- [ ] Кнопка "Оформить заказ" disabled пока не выбран адрес/время/оплата
- [ ] При нажатии кнопки проверить все поля (клиент-сайд)
- [ ] Отправить заказ на сервер
- [ ] Показать loading spinner во время отправки
- [ ] При успехе: переход на экран подтверждения заказа
- [ ] При ошибке: показать сообщение об ошибке, оставить на экране
Tasks:
1. Implement validation logic (all required fields)
2. Create API call to POST /orders
3. Implement loading state and spinner
4. Handle success/error responses
5. Add idempotency key to prevent duplicate orders
6. Implement retry logic for network failures
7. Tests for validation and API integration
4. API Эндпоинты
4.1 Получение сохранённых адресов
GET /api/v1/user/addresses
Authorization: Bearer {token}
Response:
{
"addresses": [
{
"id": "addr-123",
"street": "ул. Тверская",
"house": "15",
"apartment": "42",
"city": "Москва",
"postal_code": "121000",
"is_default": true,
"label": "Home" // Home, Work, Other
},
...
]
}
4.2 Валидация адреса (проверка в зоне доставки)
POST /api/v1/restaurants/{restaurantId}/validate-delivery-address
Content-Type: application/json
Request:
{
"street": "ул. Тверская",
"house": "15",
"apartment": "42",
"city": "Москва"
}
Response:
{
"is_valid": true,
"is_in_zone": true,
"delivery_fee": 150,
"estimated_time_min": 20,
"estimated_time_max": 45
}
4.3 Проверка и применение промокода
POST /api/v1/restaurants/{restaurantId}/validate-promo
Content-Type: application/json
Request:
{
"promo_code": "SUMMER20",
"subtotal": 2500
}
Response:
{
"valid": true,
"discount_amount": 500,
"discount_percent": 20,
"discount_type": "percent", // percent | fixed
"expires_at": "2025-04-30T23:59:59Z",
"restrictions": {
"min_order": 1000,
"max_discount": 500,
"applicable_items": ["all"] // или specific item ids
}
}
4.4 Получение доступных времён доставки
GET /api/v1/restaurants/{restaurantId}/delivery-times?address_id={addressId}
Response:
{
"delivery_times": [
{
"type": "asap",
"estimated_minutes": 25,
"available": true
},
{
"type": "hourly",
"hour": 13,
"available": true
},
{
"type": "exact_time",
"time": "13:30",
"available": true
}
],
"restaurant_hours": {
"open_at": "09:00",
"close_at": "23:00"
}
}
4.5 Получение доступных способов оплаты
GET /api/v1/user/payment-methods
Response:
{
"payment_methods": [
{
"id": "card-123",
"type": "card",
"card_number": "****5678",
"card_brand": "Visa",
"expiry": "12/26",
"is_default": true
}
],
"wallet": {
"balance": 5000,
"currency": "RUB"
},
"available_methods": ["card", "wallet", "cash"]
}
4.6 Создание заказа
POST /api/v1/orders
Authorization: Bearer {token}
Content-Type: application/json
Idempotency-Key: {uuid} // Для предотвращения дублей
Request:
{
"restaurant_id": "rest-123",
"items": [
{
"dish_id": "dish-456",
"quantity": 2,
"price": 450
}
],
"delivery_address": {
"street": "ул. Тверская",
"house": "15",
"apartment": "42"
},
"delivery_time": {
"type": "asap" | "exact_time",
"time": "13:30" // если exact_time
},
"promo_code": "SUMMER20",
"payment_method": {
"type": "card" | "wallet" | "cash",
"method_id": "card-123" // если card или wallet
},
"special_instructions": "Без лука, пожалуйста"
}
Response:
{
"order_id": "order-789",
"status": "pending",
"estimated_delivery_time": "13:50",
"total_amount": 2150,
"currency": "RUB",
"tracking_url": "https://app.com/orders/order-789"
}
4.7 Получение информации о ресторане (часы работы, зона доставки)
GET /api/v1/restaurants/{restaurantId}/info
Response:
{
"restaurant_id": "rest-123",
"name": "Пиццерия Мамма Миа",
"status": "open" | "closed",
"hours": {
"open_at": "09:00",
"close_at": "23:00"
},
"delivery_zones": [
{
"zone_id": "zone-1",
"name": "Центр",
"min_order": 500,
"delivery_fee": 150,
"estimated_time": { "min": 20, "max": 35 }
}
]
}
Итоговая структура компонентов
CheckoutScreen
├── OrderSummary
│ └── DishList
│ └── DishItem (с количеством)
├── PromoCodeSection
│ └── PromoCodeInput
├── DeliverySection
│ ├── AddressSelector
│ └── TimeSelector
├── PaymentSection
│ └── PaymentMethodSelector
├── PriceBreakdown
│ ├── ItemsTotal
│ ├── Discount
│ ├── DeliveryFee
│ ├── Tax
│ └── Total
└── CheckoutButton (disabled до валидации)
Эта декомпозиция позволяет разработчикам работать независимо над каждой историей, тестировать компоненты отдельно и интегрировать их в общий экран.