- Backend: Update Legacy API credentials to robster/rob2 - Frontend: ROOM_LOCATIONS with icons and legacyCode mapping - Device Settings: Modal picker for room selection - api.ts: Bidirectional conversion (code ↔ name) - Various UI/UX improvements across screens PRD-DEPLOYMENT.md completed (Score: 9/10) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
251 lines
10 KiB
Markdown
251 lines
10 KiB
Markdown
# PRD — Deployment + Sensors Integration (v2)
|
||
|
||
## Цель
|
||
Обеспечить полную интеграцию: при создании beneficiary автоматически создаётся deployment на Legacy API, к которому затем привязываются BLE сенсоры. Использовать credentials `robster/rob2` (больше прав). Добавить Dropdown для выбора комнаты сенсора.
|
||
|
||
## Текущее состояние (уже работает!)
|
||
|
||
### ✅ Что УЖЕ реализовано:
|
||
1. **Deployment создаётся автоматически** при создании beneficiary (`beneficiaries.js:445-501`)
|
||
2. **Legacy API integration** полностью работает (`legacyAPI.js`)
|
||
3. **BLE сенсоры** подключаются и настраиваются (`PRD-SENSORS.md` — все задачи выполнены)
|
||
4. **Device Settings** есть поля location/description (TextInput)
|
||
|
||
### ❌ Что НЕ работает:
|
||
1. Credentials `anandk` имеют ограниченные права → нужен `robster/rob2`
|
||
2. Location вводится текстом → нужен Dropdown с комнатами
|
||
3. `updateDeviceMetadata` отправляет строку вместо числового кода
|
||
|
||
---
|
||
|
||
## User Flow
|
||
|
||
### Flow 1: Создание Beneficiary + Deployment (УЖЕ РАБОТАЕТ)
|
||
|
||
| # | Актор | Действие | Система | Результат |
|
||
|---|-------|----------|---------|-----------|
|
||
| 1 | User | Заполняет форму "Add Loved One" | — | Вводит имя, адрес |
|
||
| 2 | User | Нажимает "Continue" | POST `/me/beneficiaries` | — |
|
||
| 3 | Backend | Создаёт beneficiary в PostgreSQL | INSERT `beneficiaries` | beneficiary_id |
|
||
| 4 | Backend | Создаёт deployment в PostgreSQL | INSERT `beneficiary_deployments` | deployment.id |
|
||
| 5 | Backend | Авторизуется на Legacy API | `legacyAPI.getLegacyToken()` | legacy_token |
|
||
| 6 | Backend | Создаёт deployment на Legacy | `legacyAPI.createLegacyDeployment()` | legacy_deployment_id |
|
||
| 7 | Backend | Сохраняет legacy_deployment_id | UPDATE `beneficiary_deployments` | Связь установлена |
|
||
| 8 | User | Переходит к purchase/demo | — | Deployment готов |
|
||
|
||
**Статус:** ✅ Полностью реализовано в `beneficiaries.js:419-501`
|
||
|
||
### Flow 2: Настройка устройства с Dropdown
|
||
|
||
| # | Актор | Действие | Система | Результат |
|
||
|---|-------|----------|---------|-----------|
|
||
| 1 | User | Открывает Device Settings | GET devices | Текущие данные |
|
||
| 2 | User | Видит Dropdown "Location" | — | Показывает текущую комнату |
|
||
| 3 | User | Выбирает комнату из списка | — | "Bedroom", "Kitchen", etc. |
|
||
| 4 | User | Вводит description (опционально) | — | Свободный текст |
|
||
| 5 | User | Нажимает "Save" | POST `device_form` | location=102 |
|
||
| 6 | System | Сохраняет на Legacy API | — | Обновлено |
|
||
|
||
---
|
||
|
||
## Задачи
|
||
|
||
### Backend (Простые)
|
||
|
||
- [x] **1. Обновить Legacy API credentials**
|
||
- Путь: `backend/.env`
|
||
- Изменить:
|
||
```
|
||
LEGACY_API_USERNAME=robster
|
||
LEGACY_API_PASSWORD=rob2
|
||
```
|
||
- Задеплоить: `scp .env root@91.98.205.156:/var/www/wellnuo-api/.env`
|
||
- Перезапустить: `ssh root@91.98.205.156 "pm2 restart wellnuo-api"`
|
||
|
||
### Frontend (Основная работа)
|
||
|
||
- [x] **2. Добавить константы ROOM_LOCATIONS в api.ts**
|
||
- Путь: `services/api.ts`
|
||
- Добавить в начало файла:
|
||
```typescript
|
||
// Room location codes for Legacy API
|
||
export const ROOM_LOCATIONS: Record<string, number> = {
|
||
'Bedroom': 102,
|
||
'Living Room': 103,
|
||
'Kitchen': 104,
|
||
'Bathroom': 105,
|
||
'Hallway': 106,
|
||
'Office': 107,
|
||
'Garage': 108,
|
||
'Dining Room': 109,
|
||
'Basement': 110,
|
||
'Other': 200
|
||
};
|
||
|
||
export const LOCATION_NAMES: Record<number, string> = Object.fromEntries(
|
||
Object.entries(ROOM_LOCATIONS).map(([k, v]) => [v, k])
|
||
);
|
||
```
|
||
|
||
- [x] **3. Исправить updateDeviceMetadata для location codes**
|
||
- Путь: `services/api.ts` (строка ~1783)
|
||
- Изменить:
|
||
```typescript
|
||
// БЫЛО:
|
||
formData.append('location', updates.location);
|
||
|
||
// СТАЛО:
|
||
if (updates.location !== undefined) {
|
||
// Convert room name to location code
|
||
const locationCode = ROOM_LOCATIONS[updates.location] || ROOM_LOCATIONS['Other'];
|
||
formData.append('location', locationCode.toString());
|
||
}
|
||
```
|
||
|
||
- [x] **4. Device Settings: заменить TextInput на Picker**
|
||
- Путь: `app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx`
|
||
- Импорт: `import { Picker } from '@react-native-picker/picker'`
|
||
- Или использовать: `@react-native-community/picker` / кастомный ActionSheet
|
||
|
||
**Заменить (строки 349-358):**
|
||
```tsx
|
||
// БЫЛО:
|
||
<TextInput
|
||
style={styles.editableInput}
|
||
value={location}
|
||
onChangeText={setLocation}
|
||
placeholder="e.g., Living Room, Kitchen..."
|
||
placeholderTextColor={AppColors.textMuted}
|
||
/>
|
||
|
||
// СТАЛО:
|
||
<View style={styles.pickerContainer}>
|
||
<Picker
|
||
selectedValue={location}
|
||
onValueChange={(value) => setLocation(value)}
|
||
style={styles.picker}
|
||
>
|
||
<Picker.Item label="Select room..." value="" />
|
||
<Picker.Item label="Bedroom" value="Bedroom" />
|
||
<Picker.Item label="Living Room" value="Living Room" />
|
||
<Picker.Item label="Kitchen" value="Kitchen" />
|
||
<Picker.Item label="Bathroom" value="Bathroom" />
|
||
<Picker.Item label="Hallway" value="Hallway" />
|
||
<Picker.Item label="Office" value="Office" />
|
||
<Picker.Item label="Garage" value="Garage" />
|
||
<Picker.Item label="Dining Room" value="Dining Room" />
|
||
<Picker.Item label="Basement" value="Basement" />
|
||
<Picker.Item label="Other" value="Other" />
|
||
</Picker>
|
||
</View>
|
||
```
|
||
|
||
- [x] **5. Конвертировать location code → name при загрузке**
|
||
- Путь: `app/(tabs)/beneficiaries/[id]/device-settings/[deviceId].tsx`
|
||
- В `loadSensorInfo()` добавить конвертацию:
|
||
```typescript
|
||
import { LOCATION_NAMES } from '@/services/api';
|
||
|
||
// При получении sensor:
|
||
const locationName = sensor.location
|
||
? (LOCATION_NAMES[parseInt(sensor.location)] || sensor.location)
|
||
: '';
|
||
setLocation(locationName);
|
||
```
|
||
|
||
- [x] **6. Добавить стили для Picker**
|
||
- Путь: тот же файл
|
||
- Добавить в StyleSheet:
|
||
```typescript
|
||
pickerContainer: {
|
||
backgroundColor: AppColors.background,
|
||
borderRadius: BorderRadius.md,
|
||
borderWidth: 1,
|
||
borderColor: AppColors.border,
|
||
overflow: 'hidden',
|
||
},
|
||
picker: {
|
||
height: 50,
|
||
color: AppColors.textPrimary,
|
||
},
|
||
```
|
||
|
||
- [x] **7. Установить @react-native-picker/picker**
|
||
- Команда: `npx expo install @react-native-picker/picker`
|
||
|
||
---
|
||
|
||
## Справочник: Location Codes (из legacyAPI.js)
|
||
|
||
| Код | Название | Описание |
|
||
|-----|----------|----------|
|
||
| 102 | Bedroom | Спальня |
|
||
| 103 | Living Room | Гостиная |
|
||
| 104 | Kitchen | Кухня |
|
||
| 105 | Bathroom | Ванная |
|
||
| 106 | Hallway | Коридор |
|
||
| 107 | Office | Кабинет |
|
||
| 108 | Garage | Гараж |
|
||
| 109 | Dining Room | Столовая |
|
||
| 110 | Basement | Подвал |
|
||
| 200 | Other | Другое |
|
||
|
||
---
|
||
|
||
## Вне scope
|
||
|
||
- Синхронизация location с голосовым AI (Julia) — отдельная задача
|
||
- WellNuo Lite интеграция — пока не трогаем
|
||
- Редактирование deployment после создания — пока не нужно
|
||
- Front Door (101) — нет в текущем mapping, не добавляем
|
||
|
||
---
|
||
|
||
## Чеклист верификации
|
||
|
||
### Backend
|
||
- [x] Credentials обновлены на `robster/rob2` в .env
|
||
- [x] PM2 перезапущен
|
||
- [x] Тест: создать beneficiary → в логах видно "Created Legacy deployment: XXX"
|
||
|
||
### Frontend
|
||
- [x] Device Settings показывает Picker/Dropdown вместо TextInput для location
|
||
- [x] Picker содержит все 10 комнат
|
||
- [x] При выборе комнаты — сохраняется location_code (число) на Legacy API
|
||
- [x] При загрузке — location_code конвертируется в название
|
||
- [x] Description остаётся TextInput
|
||
- [x] Сохранение работает без ошибок
|
||
|
||
### End-to-End Flow
|
||
- [x] Создать beneficiary → deployment создан на Legacy API
|
||
- [x] Подключить BLE сенсор → привязан к deployment
|
||
- [x] Открыть Device Settings → видно Dropdown
|
||
- [x] Выбрать "Kitchen" → Save → проверить в Legacy API что location=104
|
||
- [x] Перезагрузить экран → показывает "Kitchen"
|
||
|
||
---
|
||
|
||
## Риски и Edge Cases
|
||
|
||
1. **Picker на Android vs iOS** — выглядит по-разному, возможно нужен ActionSheet
|
||
2. **Старые данные** — если location уже сохранён как текст "kitchen", не найдётся в LOCATION_NAMES
|
||
3. **Нет интернета** — Legacy API недоступен, нужен graceful error
|
||
|
||
---
|
||
|
||
## Порядок выполнения
|
||
|
||
1. ✅ Backend: обновить credentials (5 мин)
|
||
2. 🔨 Frontend: добавить константы ROOM_LOCATIONS (5 мин)
|
||
3. 🔨 Frontend: исправить updateDeviceMetadata (10 мин)
|
||
4. 🔨 Frontend: установить Picker пакет (2 мин)
|
||
5. 🔨 Frontend: заменить TextInput на Picker (20 мин)
|
||
6. 🔨 Frontend: конвертация code↔name (15 мин)
|
||
7. ✅ Тестирование E2E (15 мин)
|
||
|
||
**Общее время: ~1.5 часа**
|
||
|
||
---
|
||
|
||
**Минимальный проходной балл: 8/10**
|