Issue #52: feat: Contractor backoffice (Work reports, photos, payment validation)
- State:
OPEN
- Milestone:
Jalon 3: Features Différenciantes 🎯
- Labels:
phase:k3s,track:software priority:medium
- Assignees:
Unassigned
- Created:
2025-10-27
- Updated:
2025-11-13
- URL:
Description
## Context
**Prestataires/Entrepreneurs** effectuent travaux dans l'immeuble :
- Plombier
- Électricien
- Peintre
- Société de nettoyage
- Jardinier
- Ascensoriste
- Entrepreneur général
**Problème actuel :**
- Pas de traçabilité des interventions
- Pas de preuve photographique des travaux
- Communication email/téléphone fragmentée
- Validation paiement manuelle et lente
**Besoin :**
Espace dédié pour que les prestataires documentent leurs interventions et déclenchent la validation de paiement.
## Objective
**Backoffice léger pour prestataires** permettant :
1. Poster compte-rendu d'intervention
2. Uploader photos (avant/après, pièces changées, résultat fini)
3. Déclarer intervention terminée
4. Déclencher validation paiement par Syndic
5. Suivre statut facture (en attente, approuvée, payée)
## User Flow
```
Entrepreneur termine travaux
↓
Se connecte au portail KoproGo (login simplifié)
↓
Crée compte-rendu intervention
- Sélectionne chantier/tâche assignée
- Description travaux effectués
- Upload photos (avant/après, pièces changées)
- Temps passé (heures)
- Matériaux utilisés
↓
Marque intervention comme "Terminée"
↓
Système notifie Syndic
↓
Syndic voit compte-rendu + photos
↓
Syndic valide ou demande corrections
↓
Si validé → Déclenche paiement
↓
Entrepreneur voit statut "Approuvé - Paiement en cours"
```
## Domain Model
### 1. Contractor (Prestataire)
```rust
pub struct Contractor {
pub id: Uuid,
pub company_name: String,
pub contact_person: String,
pub email: String,
pub phone: String,
pub vat_number: String, // TVA
pub iban: Option<String>,
pub contractor_type: ContractorType,
pub is_active: bool,
pub created_at: DateTime<Utc>,
}
pub enum ContractorType {
Plumber, // Plombier
Electrician, // Électricien
Painter, // Peintre
Cleaner, // Nettoyage
Gardener, // Jardinier
Elevator, // Ascensoriste
GeneralWork, // Travaux généraux
Other,
}
```
### 2. Work Order (Ordre de Travail)
```rust
pub struct WorkOrder {
pub id: Uuid,
pub building_id: Uuid,
pub contractor_id: Uuid,
pub title: String,
pub description: String,
pub location: String, // "Hall d'entrée", "Appartement 3B"
pub quote_amount: Option<Decimal>, // Montant devis
pub status: WorkOrderStatus,
pub assigned_at: DateTime<Utc>,
pub due_date: Option<DateTime<Utc>>,
pub related_issue_id: Option<Uuid>, // Lien avec signalement
pub related_expense_id: Option<Uuid>,
}
pub enum WorkOrderStatus {
Assigned, // Assigné à entrepreneur
InProgress, // En cours
Completed, // Terminé par entrepreneur
Validated, // Validé par Syndic
PaymentPending, // Paiement en attente
Paid, // Payé
Rejected, // Refusé par Syndic
}
```
### 3. Work Report (Compte-Rendu)
```rust
pub struct WorkReport {
pub id: Uuid,
pub work_order_id: Uuid,
pub contractor_id: Uuid,
pub summary: String,
pub detailed_description: String,
pub work_date: DateTime<Utc>,
pub hours_spent: f32,
pub materials_used: Vec<MaterialUsed>,
pub before_photos: Vec<String>, // Photos avant travaux
pub after_photos: Vec<String>, // Photos après travaux
pub parts_photos: Vec<String>, // Photos pièces changées
pub submitted_at: DateTime<Utc>,
pub validated_by: Option<Uuid>, // Syndic
pub validated_at: Option<DateTime<Utc>>,
pub validation_notes: Option<String>,
}
pub struct MaterialUsed {
pub name: String,
pub quantity: i32,
pub unit: String, // "pièce", "mètre", "litre"
pub cost: Option<Decimal>,
}
```
### 4. Contractor Invoice (Facture)
```rust
pub struct ContractorInvoice {
pub id: Uuid,
pub work_order_id: Uuid,
pub contractor_id: Uuid,
pub invoice_number: String,
pub invoice_date: DateTime<Utc>,
pub amount_excl_vat: Decimal,
pub vat_rate: f32,
pub amount_incl_vat: Decimal,
pub invoice_file_url: Option<String>, // PDF facture
pub status: InvoiceStatus,
pub payment_deadline: DateTime<Utc>,
pub paid_at: Option<DateTime<Utc>>,
}
pub enum InvoiceStatus {
Draft, // Brouillon
Submitted, // Soumise
Approved, // Approuvée par Syndic
Rejected, // Refusée
PaymentScheduled, // Paiement planifié
Paid, // Payée
}
```
## API Endpoints
### Contractor Authentication
**Login simplifié (pas de compte utilisateur complet) :**
- Email + code PIN (envoyé par Syndic)
- ou Email + lien magique (passwordless)
```
POST /api/v1/contractors/auth/login
POST /api/v1/contractors/auth/verify-code
```
### Work Orders (Read-only pour contractor)
```
GET /api/v1/contractors/:id/work-orders
GET /api/v1/work-orders/:id
```
### Work Reports
```
POST /api/v1/work-orders/:id/reports
PUT /api/v1/work-reports/:id
POST /api/v1/work-reports/:id/photos
PUT /api/v1/work-reports/:id/submit
```
### Invoices
```
POST /api/v1/work-orders/:id/invoice
GET /api/v1/contractors/:id/invoices
```
### Syndic validation
```
PUT /api/v1/work-reports/:id/validate
PUT /api/v1/work-reports/:id/reject
PUT /api/v1/invoices/:id/approve
PUT /api/v1/invoices/:id/mark-paid
```
## Frontend - Contractor Portal
### 1. Login Page
```svelte
<!-- /contractor-portal/login.astro -->
<ContractorLoginForm />
```
**Formulaire :**
- Email
- Code PIN (4-6 chiffres)
- Bouton "Renvoyer code"
### 2. Dashboard Contractor
```svelte
<!-- /contractor-portal/dashboard.astro -->
<ContractorDashboard contractorId={session.contractor_id} />
```
**Sections :**
- Chantiers en cours (3)
- Chantiers terminés en attente validation (2)
- Factures en attente paiement (€2,450)
- Derniers paiements reçus
### 3. Work Order Detail + Report Form
```svelte
<!-- /contractor-portal/work-order/[id].astro -->
<WorkOrderDetail workOrderId={id} />
<WorkReportForm workOrderId={id} />
```
**Formulaire compte-rendu :**
```svelte
<script lang="ts">
let summary = '';
let detailedDescription = '';
let hoursSpent = 0;
let materialsUsed = [];
let beforePhotos = [];
let afterPhotos = [];
let partsPhotos = [];
function addMaterial() {
materialsUsed.push({
name: '',
quantity: 1,
unit: 'pièce',
cost: 0
});
}
async function submitReport() {
const formData = new FormData();
formData.append('summary', summary);
formData.append('detailed_description', detailedDescription);
formData.append('hours_spent', hoursSpent);
formData.append('materials_used', JSON.stringify(materialsUsed));
beforePhotos.forEach(photo => formData.append('before_photos', photo));
afterPhotos.forEach(photo => formData.append('after_photos', photo));
partsPhotos.forEach(photo => formData.append('parts_photos', photo));
await fetch(`/api/v1/work-orders/${workOrderId}/reports`, {
method: 'POST',
body: formData
});
showToast('Compte-rendu soumis avec succès');
}
</script>
<form on:submit|preventDefault={submitReport}>
<h2>Compte-rendu d'intervention</h2>
<input
type="text"
bind:value={summary}
placeholder="Résumé (ex: Remplacement robinet fuyant)"
required
/>
<textarea
bind:value={detailedDescription}
placeholder="Description détaillée des travaux effectués"
rows="6"
required
/>
<label>
Heures passées
<input type="number" bind:value={hoursSpent} step="0.5" min="0" />
</label>
<h3>Matériaux utilisés</h3>
{#each materialsUsed as material, i}
<div class="material-row">
<input bind:value={material.name} placeholder="Nom" />
<input bind:value={material.quantity} type="number" placeholder="Qté" />
<input bind:value={material.unit} placeholder="Unité" />
<input bind:value={material.cost} type="number" step="0.01" placeholder="Coût (€)" />
<button on:click={() => materialsUsed.splice(i, 1)}>❌</button>
</div>
{/each}
<button type="button" on:click={addMaterial}>+ Ajouter matériau</button>
<h3>Photos</h3>
<FileUpload
bind:files={beforePhotos}
label="Photos AVANT travaux"
accept="image/*"
multiple
/>
<FileUpload
bind:files={partsPhotos}
label="Photos pièces changées"
accept="image/*"
multiple
/>
<FileUpload
bind:files={afterPhotos}
label="Photos APRÈS travaux / Résultat fini"
accept="image/*"
multiple
/>
<button type="submit">Soumettre compte-rendu</button>
</form>
```
### 4. Invoice Upload
```svelte
<InvoiceUploadForm workOrderId={workOrderId} />
```
**Champs :**
- Numéro facture
- Date facture
- Montant HT
- Taux TVA (21% BE par défaut)
- Upload PDF facture
- Date échéance paiement
## Frontend - Syndic View
### Work Report Validation
```svelte
<!-- /admin/work-reports/[id].astro -->
<WorkReportReview reportId={id} />
```
**Vue Syndic :**
- Infos chantier
- Compte-rendu entrepreneur
- Galerie photos (avant/après/pièces)
- Matériaux utilisés avec coûts
- Heures passées
- Actions :
- ✅ Valider → Déclenche approbation facture
- ❌ Rejeter → Demander corrections
- 💬 Demander clarifications
## Notifications
**Entrepreneur reçoit :**
- Nouveau chantier assigné
- Compte-rendu validé
- Compte-rendu rejeté (avec raisons)
- Facture approuvée
- Paiement effectué
**Syndic reçoit :**
- Compte-rendu soumis (à valider)
- Facture soumise (à approuver)
- Échéance paiement approche
## Security & Access Control
**Contractor access :**
- Voit uniquement SES chantiers
- Voit uniquement SES factures
- Pas d'accès aux autres données immeuble
- Session limitée (24h expiration)
- Code PIN rotatif (nouveau code tous les 3 mois)
**Syndic :**
- Assigne chantiers aux contractors
- Valide comptes-rendus
- Approuve factures
- Marque paiements effectués
## Testing
- [ ] Contractor login with PIN code
- [ ] View assigned work orders
- [ ] Submit work report with photos
- [ ] Upload materials used
- [ ] Submit invoice
- [ ] Syndic validates report
- [ ] Syndic approves invoice
- [ ] Syndic marks as paid
- [ ] Notifications delivery
- [ ] Photo gallery display
## Acceptance Criteria
- [ ] Contractor entities + authentication
- [ ] Work order management
- [ ] Work report submission with photos
- [ ] Material tracking
- [ ] Invoice submission
- [ ] Syndic validation workflow
- [ ] Contractor portal UI (login, dashboard, report form)
- [ ] Syndic review UI
- [ ] Photo upload/gallery
- [ ] Notifications configured
- [ ] Access control enforced
- [ ] Mobile-responsive (contractors use phones on-site)
## Effort Estimate
**Large** (8-10 days)
- Days 1-2: Contractor entities + auth (PIN code)
- Days 3-4: Work report submission + photos
- Days 5-6: Invoice management
- Days 7-8: Syndic validation UI
- Days 9-10: Notifications, testing
## Benefits
**Pour Syndic :**
- Traçabilité complète des interventions
- Preuve photographique des travaux
- Validation paiement facilitée
- Historique consultable
- Réduction litiges
**Pour Entrepreneur :**
- Paiements plus rapides (validation digitale)
- Pas d'emails/papiers perdus
- Historique travaux accessible
- Proof of work pour portfolio
**Pour ASBL :**
- Professionnalisation gestion copropriété
- Transparence pour copropriétaires
- Réduction charges administratives
## Future Enhancements
- Signature électronique PV intervention
- Géolocalisation check-in/check-out (proof of presence)
- Time tracking automatique (chronomètre)
- Rating/reviews entrepreneurs par Syndic
- Marketplace entrepreneurs (annuaire)
- Automated reminders for overdue work
- Integration comptabilité (export factures vers logiciel compta)
## References
- Fieldwire (construction management): https://www.fieldwire.com/
- Procore (contractor portal): https://www.procore.com/
- ServiceTitan (field service software): https://www.servicetitan.com/