758 lines
29 KiB
Markdown
758 lines
29 KiB
Markdown
# 🚀 Service Integration: Complete Implementation Guide
|
|
|
|
## 📋 **Overview**
|
|
|
|
The Project Mycelium has a complete app workflow but incomplete service workflow. This document provides everything needed to complete the service integration, making services purchasable through the marketplace with full tracking for both providers and customers.
|
|
|
|
## 🎯 **Current State Analysis**
|
|
|
|
### **✅ App Workflow - COMPLETE**
|
|
1. **Creation**: [`DashboardController::add_app_api`](../../../src/controllers/dashboard.rs:4724) → [`UserPersistence::add_user_app`](../../../src/services/user_persistence.rs)
|
|
2. **Marketplace**: [`applications_page`](../../../src/controllers/marketplace.rs:600) aggregates apps from all users
|
|
3. **Purchase**: [`OrderService::checkout`](../../../src/services/order.rs:415) → [`create_app_deployments_from_order`](../../../src/services/order.rs:476)
|
|
4. **Tracking**: [`AppDeployment`](../../../src/services/user_persistence.rs:31) records for both provider and customer
|
|
|
|
### **🔄 Service Workflow - PARTIALLY COMPLETE**
|
|
1. **Creation**: [`DashboardController::create_service`](../../../src/controllers/dashboard.rs:3180) → [`UserPersistence::add_user_service`](../../../src/services/user_persistence.rs:468) ✅
|
|
2. **Marketplace**: [`services_page`](../../../src/controllers/marketplace.rs:693) aggregates services from all users ✅
|
|
3. **Purchase**: ❌ **MISSING** - No service booking creation in OrderService
|
|
4. **Tracking**: ❌ **MISSING** - No customer service booking records
|
|
|
|
### **The Gap**
|
|
Services were designed for manual requests while apps were designed for automated marketplace purchases. We need to add service booking creation to the OrderService to mirror the app workflow.
|
|
|
|
## 🏗️ **Implementation Plan**
|
|
|
|
### **Phase 1: Data Models** (2-3 hours)
|
|
|
|
#### **1.1 Add ServiceBooking Model**
|
|
```rust
|
|
// src/models/user.rs - Add after ServiceRequest struct
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServiceBooking {
|
|
pub id: String, // Same as ServiceRequest.id for cross-reference
|
|
pub service_id: String, // Reference to original service
|
|
pub service_name: String,
|
|
pub provider_email: String, // Who provides the service
|
|
pub customer_email: String, // Who booked the service
|
|
pub budget: i32,
|
|
pub estimated_hours: i32,
|
|
pub status: String, // "Pending", "In Progress", "Completed"
|
|
pub requested_date: String,
|
|
pub priority: String,
|
|
pub description: Option<String>,
|
|
pub booking_date: String, // When customer booked
|
|
pub client_phone: Option<String>,
|
|
pub progress: Option<i32>,
|
|
pub completed_date: Option<String>,
|
|
}
|
|
|
|
// Add CustomerServiceData to MockUserData
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CustomerServiceData {
|
|
pub active_bookings: i32,
|
|
pub completed_bookings: i32,
|
|
pub total_spent: i32,
|
|
pub service_bookings: Vec<ServiceBooking>,
|
|
}
|
|
|
|
// Update MockUserData struct - add this field
|
|
pub struct MockUserData {
|
|
// ... existing fields ...
|
|
|
|
// NEW: Customer service data
|
|
#[serde(default)]
|
|
pub customer_service_data: Option<CustomerServiceData>,
|
|
}
|
|
```
|
|
|
|
#### **1.2 Update UserPersistentData**
|
|
```rust
|
|
// src/services/user_persistence.rs - Add to UserPersistentData struct
|
|
pub struct UserPersistentData {
|
|
// ... existing fields ...
|
|
|
|
// NEW: Customer service bookings
|
|
#[serde(default)]
|
|
pub service_bookings: Vec<ServiceBooking>,
|
|
}
|
|
```
|
|
|
|
#### **1.3 Add ServiceBooking Builder**
|
|
```rust
|
|
// src/models/builders.rs - Add ServiceBooking builder
|
|
#[derive(Default)]
|
|
pub struct ServiceBookingBuilder {
|
|
id: Option<String>,
|
|
service_id: Option<String>,
|
|
service_name: Option<String>,
|
|
provider_email: Option<String>,
|
|
customer_email: Option<String>,
|
|
budget: Option<i32>,
|
|
estimated_hours: Option<i32>,
|
|
status: Option<String>,
|
|
requested_date: Option<String>,
|
|
priority: Option<String>,
|
|
description: Option<String>,
|
|
booking_date: Option<String>,
|
|
}
|
|
|
|
impl ServiceBookingBuilder {
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
pub fn id(mut self, id: &str) -> Self {
|
|
self.id = Some(id.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn service_id(mut self, service_id: &str) -> Self {
|
|
self.service_id = Some(service_id.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn service_name(mut self, service_name: &str) -> Self {
|
|
self.service_name = Some(service_name.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn provider_email(mut self, provider_email: &str) -> Self {
|
|
self.provider_email = Some(provider_email.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn customer_email(mut self, customer_email: &str) -> Self {
|
|
self.customer_email = Some(customer_email.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn budget(mut self, budget: i32) -> Self {
|
|
self.budget = Some(budget);
|
|
self
|
|
}
|
|
|
|
pub fn estimated_hours(mut self, hours: i32) -> Self {
|
|
self.estimated_hours = Some(hours);
|
|
self
|
|
}
|
|
|
|
pub fn status(mut self, status: &str) -> Self {
|
|
self.status = Some(status.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn requested_date(mut self, date: &str) -> Self {
|
|
self.requested_date = Some(date.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn priority(mut self, priority: &str) -> Self {
|
|
self.priority = Some(priority.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn description(mut self, description: Option<String>) -> Self {
|
|
self.description = description;
|
|
self
|
|
}
|
|
|
|
pub fn booking_date(mut self, date: &str) -> Self {
|
|
self.booking_date = Some(date.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn build(self) -> Result<ServiceBooking, String> {
|
|
Ok(ServiceBooking {
|
|
id: self.id.ok_or("ID is required")?,
|
|
service_id: self.service_id.ok_or("Service ID is required")?,
|
|
service_name: self.service_name.ok_or("Service name is required")?,
|
|
provider_email: self.provider_email.ok_or("Provider email is required")?,
|
|
customer_email: self.customer_email.ok_or("Customer email is required")?,
|
|
budget: self.budget.unwrap_or(0),
|
|
estimated_hours: self.estimated_hours.unwrap_or(0),
|
|
status: self.status.unwrap_or_else(|| "Pending".to_string()),
|
|
requested_date: self.requested_date.unwrap_or_else(|| chrono::Utc::now().format("%Y-%m-%d").to_string()),
|
|
priority: self.priority.unwrap_or_else(|| "Medium".to_string()),
|
|
description: self.description,
|
|
booking_date: self.booking_date.unwrap_or_else(|| chrono::Utc::now().format("%Y-%m-%d").to_string()),
|
|
client_phone: None,
|
|
progress: None,
|
|
completed_date: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl ServiceBooking {
|
|
pub fn builder() -> ServiceBookingBuilder {
|
|
ServiceBookingBuilder::new()
|
|
}
|
|
}
|
|
```
|
|
|
|
### **Phase 2: Service Persistence** (2-3 hours)
|
|
|
|
#### **2.1 Add Service Booking Methods**
|
|
```rust
|
|
// src/services/user_persistence.rs - Add these methods to impl UserPersistence
|
|
impl UserPersistence {
|
|
/// Add a service booking to customer's data
|
|
pub fn add_user_service_booking(
|
|
user_email: &str,
|
|
service_booking: ServiceBooking
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
let mut data = Self::load_user_data(user_email)
|
|
.unwrap_or_else(|| Self::create_default_user_data(user_email));
|
|
|
|
// Add service booking if not already present
|
|
if !data.service_bookings.iter().any(|b| b.id == service_booking.id) {
|
|
data.service_bookings.push(service_booking.clone());
|
|
log::info!("Added service booking '{}' to persistent data for user: {}", service_booking.id, user_email);
|
|
} else {
|
|
log::info!("Service booking '{}' already exists for user: {}", service_booking.id, user_email);
|
|
}
|
|
|
|
Self::save_user_data(user_email, &data)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Get customer's service bookings
|
|
pub fn get_user_service_bookings(user_email: &str) -> Vec<ServiceBooking> {
|
|
if let Some(data) = Self::load_user_data(user_email) {
|
|
data.service_bookings
|
|
} else {
|
|
Vec::new()
|
|
}
|
|
}
|
|
|
|
/// Convert ServiceRequest to ServiceBooking for customer
|
|
pub fn create_service_booking_from_request(
|
|
request: &ServiceRequest,
|
|
customer_email: &str,
|
|
provider_email: &str
|
|
) -> ServiceBooking {
|
|
ServiceBooking::builder()
|
|
.id(&request.id)
|
|
.service_id(&format!("svc_{}", &request.id[4..])) // Extract service ID from request ID
|
|
.service_name(&request.service_name)
|
|
.provider_email(provider_email)
|
|
.customer_email(customer_email)
|
|
.budget(request.budget)
|
|
.estimated_hours(request.estimated_hours)
|
|
.status(&request.status)
|
|
.requested_date(&request.requested_date)
|
|
.priority(&request.priority)
|
|
.description(request.description.clone())
|
|
.booking_date(&chrono::Utc::now().format("%Y-%m-%d").to_string())
|
|
.build()
|
|
.unwrap()
|
|
}
|
|
}
|
|
|
|
// Update create_default_user_data to include service_bookings
|
|
fn create_default_user_data(user_email: &str) -> UserPersistentData {
|
|
UserPersistentData {
|
|
// ... existing fields ...
|
|
service_bookings: Vec::default(), // Add this line
|
|
// ... rest of fields ...
|
|
}
|
|
}
|
|
```
|
|
|
|
### **Phase 3: Order Service Enhancement** (4-5 hours)
|
|
|
|
#### **3.1 Add Service Booking Creation**
|
|
```rust
|
|
// src/services/order.rs - Add this method to impl OrderService
|
|
impl OrderService {
|
|
/// Create service bookings when services are successfully ordered
|
|
fn create_service_bookings_from_order(&self, order: &Order) -> Result<(), String> {
|
|
use crate::services::user_persistence::{UserPersistence, ServiceBooking};
|
|
use crate::models::user::{ServiceRequest, ServiceRequestBuilder};
|
|
use chrono::Utc;
|
|
|
|
log::info!("Creating service bookings for order: {}", order.id);
|
|
|
|
// Get customer information from order
|
|
let customer_email = order.user_id.clone();
|
|
let customer_name = if customer_email == "guest" {
|
|
"Guest User".to_string()
|
|
} else {
|
|
// Try to get customer name from persistent data
|
|
if let Some(customer_data) = UserPersistence::load_user_data(&customer_email) {
|
|
customer_data.name.unwrap_or_else(|| customer_email.clone())
|
|
} else {
|
|
customer_email.clone()
|
|
}
|
|
};
|
|
|
|
// Process each order item
|
|
for item in &order.items {
|
|
// Only create bookings for service products
|
|
if item.product_category == "service" {
|
|
log::info!("Creating booking for service: {} (Product ID: {})", item.product_name, item.product_id);
|
|
|
|
// Find the service provider by looking up who published this service
|
|
if let Some(service_provider_email) = self.find_service_provider(&item.product_id) {
|
|
log::info!("Found service provider: {} for service: {}", service_provider_email, item.product_name);
|
|
|
|
// Create service request for provider (same as existing pattern)
|
|
for _i in 0..item.quantity {
|
|
let request_id = format!("req-{}-{}",
|
|
&order.id[..8],
|
|
&uuid::Uuid::new_v4().to_string()[..8]
|
|
);
|
|
|
|
let service_request = ServiceRequest {
|
|
id: request_id.clone(),
|
|
client_name: customer_name.clone(),
|
|
client_email: Some(customer_email.clone()),
|
|
service_name: item.product_name.clone(),
|
|
status: "Pending".to_string(),
|
|
requested_date: Utc::now().format("%Y-%m-%d").to_string(),
|
|
estimated_hours: (item.unit_price_base.to_f64().unwrap_or(0.0) / 75.0) as i32, // Assume $75/hour
|
|
budget: item.unit_price_base.to_i32().unwrap_or(0),
|
|
priority: "Medium".to_string(),
|
|
description: Some(format!("Service booking from marketplace order {}", order.id)),
|
|
created_date: Some(Utc::now().format("%Y-%m-%d").to_string()),
|
|
client_phone: None,
|
|
progress: None,
|
|
completed_date: None,
|
|
};
|
|
|
|
// Add request to service provider's data
|
|
if let Err(e) = UserPersistence::add_user_service_request(&service_provider_email, service_request.clone()) {
|
|
log::error!("Failed to add service request to provider {}: {}", service_provider_email, e);
|
|
} else {
|
|
log::info!("Successfully created service request {} for provider: {}", request_id, service_provider_email);
|
|
}
|
|
|
|
// Create service booking for customer
|
|
let service_booking = UserPersistence::create_service_booking_from_request(
|
|
&service_request,
|
|
&customer_email,
|
|
&service_provider_email
|
|
);
|
|
|
|
// Add booking to customer's data
|
|
if customer_email != "guest" {
|
|
if let Err(e) = UserPersistence::add_user_service_booking(&customer_email, service_booking) {
|
|
log::error!("Failed to add service booking to customer {}: {}", customer_email, e);
|
|
} else {
|
|
log::info!("Successfully added service booking {} to customer: {}", request_id, customer_email);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
log::warn!("Could not find service provider for product: {}", item.product_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Find the service provider (user who published the service) by product ID
|
|
fn find_service_provider(&self, product_id: &str) -> Option<String> {
|
|
// Get all user data files and search for the service
|
|
let user_data_dir = std::path::Path::new("user_data");
|
|
if !user_data_dir.exists() {
|
|
return None;
|
|
}
|
|
|
|
if let Ok(entries) = std::fs::read_dir(user_data_dir) {
|
|
for entry in entries.flatten() {
|
|
if let Some(file_name) = entry.file_name().to_str() {
|
|
if file_name.ends_with(".json") {
|
|
// Extract email from filename
|
|
let user_email = file_name
|
|
.trim_end_matches(".json")
|
|
.replace("_at_", "@")
|
|
.replace("_", ".");
|
|
|
|
// Check if this user has the service
|
|
let user_services = UserPersistence::get_user_services(&user_email);
|
|
for service in user_services {
|
|
if service.id == product_id {
|
|
log::info!("Found service {} published by user: {}", product_id, user_email);
|
|
return Some(user_email);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
```
|
|
|
|
#### **3.2 Update Checkout Method**
|
|
```rust
|
|
// src/services/order.rs - Update the checkout method
|
|
pub fn checkout(&mut self, order_id: &str, payment_details: PaymentDetails) -> Result<PaymentResult, String> {
|
|
// ... existing payment processing code ...
|
|
|
|
// Update order with payment details
|
|
{
|
|
let order_storage = OrderStorage::instance();
|
|
let mut storage = order_storage.lock().unwrap();
|
|
if let Some(order) = storage.get_order_mut(order_id) {
|
|
if payment_result.success {
|
|
order.update_status(OrderStatus::Confirmed);
|
|
if let Some(payment_details) = &payment_result.payment_details {
|
|
order.set_payment_details(payment_details.clone());
|
|
}
|
|
|
|
// EXISTING: Create app deployments for successful app orders
|
|
if let Err(e) = self.create_app_deployments_from_order(&order) {
|
|
log::error!("Failed to create app deployments for order {}: {}", order_id, e);
|
|
}
|
|
|
|
// NEW: Create service bookings for successful service orders
|
|
if let Err(e) = self.create_service_bookings_from_order(&order) {
|
|
log::error!("Failed to create service bookings for order {}: {}", order_id, e);
|
|
}
|
|
} else {
|
|
order.update_status(OrderStatus::Failed);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(payment_result)
|
|
}
|
|
```
|
|
|
|
### **Phase 4: Dashboard Integration** (3-4 hours)
|
|
|
|
#### **4.1 Add Service Booking API**
|
|
```rust
|
|
// src/controllers/dashboard.rs - Add these methods to impl DashboardController
|
|
impl DashboardController {
|
|
/// Get user's service bookings for user dashboard
|
|
pub async fn get_user_service_bookings_api(session: Session) -> Result<impl Responder> {
|
|
log::info!("Getting user service bookings");
|
|
|
|
let user_email = match session.get::<String>("user_email") {
|
|
Ok(Some(email)) => email,
|
|
_ => {
|
|
return Ok(HttpResponse::Unauthorized().json(serde_json::json!({
|
|
"success": false,
|
|
"message": "User not authenticated"
|
|
})));
|
|
}
|
|
};
|
|
|
|
let service_bookings = UserPersistence::get_user_service_bookings(&user_email);
|
|
log::info!("Returning {} service bookings for user {}", service_bookings.len(), user_email);
|
|
|
|
Ok(HttpResponse::Ok().json(serde_json::json!({
|
|
"success": true,
|
|
"bookings": service_bookings
|
|
})))
|
|
}
|
|
}
|
|
```
|
|
|
|
#### **4.2 Update User Data Loading**
|
|
```rust
|
|
// src/controllers/dashboard.rs - Update load_user_with_mock_data method
|
|
fn load_user_with_mock_data(session: &Session) -> Option<User> {
|
|
// ... existing code ...
|
|
|
|
// Apply persistent data
|
|
if let Some(persistent_data) = UserPersistence::load_user_data(&user_email) {
|
|
// ... existing service provider data loading ...
|
|
|
|
// NEW: Load customer service bookings for user dashboard
|
|
if let Some(ref mut mock_data) = user.mock_data {
|
|
// Initialize customer service data if needed
|
|
if mock_data.customer_service_data.is_none() {
|
|
mock_data.customer_service_data = Some(CustomerServiceData {
|
|
active_bookings: 0,
|
|
completed_bookings: 0,
|
|
total_spent: 0,
|
|
service_bookings: Vec::new(),
|
|
});
|
|
}
|
|
|
|
// Load service bookings
|
|
if let Some(ref mut customer_data) = mock_data.customer_service_data {
|
|
customer_data.service_bookings = persistent_data.service_bookings.clone();
|
|
customer_data.active_bookings = customer_data.service_bookings.iter()
|
|
.filter(|b| b.status == "In Progress" || b.status == "Pending")
|
|
.count() as i32;
|
|
customer_data.completed_bookings = customer_data.service_bookings.iter()
|
|
.filter(|b| b.status == "Completed")
|
|
.count() as i32;
|
|
customer_data.total_spent = customer_data.service_bookings.iter()
|
|
.map(|b| b.budget)
|
|
.sum();
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(user)
|
|
}
|
|
```
|
|
|
|
#### **4.3 Add Route Configuration**
|
|
```rust
|
|
// src/routes/mod.rs - Add this route to the dashboard routes
|
|
.route("/dashboard/service-bookings", web::get().to(DashboardController::get_user_service_bookings_api))
|
|
```
|
|
|
|
### **Phase 5: Frontend Integration** (2-3 hours)
|
|
|
|
#### **5.1 Update User Dashboard HTML**
|
|
```html
|
|
<!-- src/views/dashboard/user.html - Add this section after applications section -->
|
|
<section class="service-bookings-section">
|
|
<div class="section-header">
|
|
<h3>My Service Bookings</h3>
|
|
<span class="section-count" id="service-bookings-count">0</span>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table id="service-bookings-table" class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>Service</th>
|
|
<th>Provider</th>
|
|
<th>Status</th>
|
|
<th>Budget</th>
|
|
<th>Est. Hours</th>
|
|
<th>Requested</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Populated by JavaScript -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div id="no-service-bookings" class="empty-state" style="display: none;">
|
|
<p>No service bookings yet. <a href="/marketplace/services">Browse services</a> to get started.</p>
|
|
</div>
|
|
</section>
|
|
```
|
|
|
|
#### **5.2 Update Dashboard JavaScript**
|
|
```javascript
|
|
// src/static/js/dashboard.js - Add these methods to UserDashboard class
|
|
class UserDashboard {
|
|
// ... existing methods ...
|
|
|
|
async loadServiceBookings() {
|
|
try {
|
|
const response = await fetch('/dashboard/service-bookings');
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
this.serviceBookings = data.bookings || [];
|
|
this.populateServiceBookingsTable();
|
|
this.updateServiceBookingsCount();
|
|
} else {
|
|
console.error('Failed to load service bookings:', data.message);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading service bookings:', error);
|
|
}
|
|
}
|
|
|
|
populateServiceBookingsTable() {
|
|
const tableBody = document.querySelector('#service-bookings-table tbody');
|
|
const emptyState = document.getElementById('no-service-bookings');
|
|
|
|
if (!tableBody) return;
|
|
|
|
tableBody.innerHTML = '';
|
|
|
|
if (this.serviceBookings.length === 0) {
|
|
document.getElementById('service-bookings-table').style.display = 'none';
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
document.getElementById('service-bookings-table').style.display = 'table';
|
|
emptyState.style.display = 'none';
|
|
|
|
this.serviceBookings.forEach(booking => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>
|
|
<div class="service-info">
|
|
<strong>${booking.service_name}</strong>
|
|
${booking.description ? `<br><small class="text-muted">${booking.description}</small>` : ''}
|
|
</div>
|
|
</td>
|
|
<td>${booking.provider_email}</td>
|
|
<td>
|
|
<span class="status-badge ${booking.status.toLowerCase().replace(' ', '-')}">
|
|
${booking.status}
|
|
</span>
|
|
</td>
|
|
<td>${booking.budget} TFP</td>
|
|
<td>${booking.estimated_hours}h</td>
|
|
<td>${booking.requested_date}</td>
|
|
<td>
|
|
<button onclick="viewServiceBooking('${booking.id}')" class="btn btn-sm btn-primary">
|
|
View Details
|
|
</button>
|
|
</td>
|
|
`;
|
|
tableBody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
updateServiceBookingsCount() {
|
|
const countElement = document.getElementById('service-bookings-count');
|
|
if (countElement) {
|
|
countElement.textContent = this.serviceBookings.length;
|
|
}
|
|
}
|
|
|
|
// Update the init method to load service bookings
|
|
async init() {
|
|
// ... existing initialization ...
|
|
await this.loadServiceBookings();
|
|
// ... rest of initialization ...
|
|
}
|
|
}
|
|
|
|
// Add global function for viewing service booking details
|
|
function viewServiceBooking(bookingId) {
|
|
const booking = window.userDashboard.serviceBookings.find(b => b.id === bookingId);
|
|
if (booking) {
|
|
// Show service booking details modal
|
|
alert(`Service Booking Details:\n\nService: ${booking.service_name}\nProvider: ${booking.provider_email}\nStatus: ${booking.status}\nBudget: ${booking.budget} TFP`);
|
|
// TODO: Replace with proper modal implementation
|
|
}
|
|
}
|
|
```
|
|
|
|
#### **5.3 Add CSS Styles**
|
|
```css
|
|
/* src/static/css/dashboard.css - Add these styles */
|
|
.service-bookings-section {
|
|
margin-top: 2rem;
|
|
}
|
|
|
|
.service-info strong {
|
|
color: #333;
|
|
}
|
|
|
|
.service-info small {
|
|
font-size: 0.85em;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.status-badge.pending {
|
|
background-color: #ffc107;
|
|
color: #000;
|
|
}
|
|
|
|
.status-badge.in-progress {
|
|
background-color: #17a2b8;
|
|
color: #fff;
|
|
}
|
|
|
|
.status-badge.completed {
|
|
background-color: #28a745;
|
|
color: #fff;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: #6c757d;
|
|
}
|
|
|
|
.empty-state a {
|
|
color: #007bff;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.empty-state a:hover {
|
|
text-decoration: underline;
|
|
}
|
|
```
|
|
|
|
## 📋 **Implementation Checklist**
|
|
|
|
### **Phase 1: Data Models** ✅
|
|
- [ ] Add `ServiceBooking` model to `src/models/user.rs`
|
|
- [ ] Add `CustomerServiceData` to `MockUserData`
|
|
- [ ] Add `service_bookings` field to `UserPersistentData`
|
|
- [ ] Add `ServiceBookingBuilder` to `src/models/builders.rs`
|
|
|
|
### **Phase 2: Service Persistence** ✅
|
|
- [ ] Add `add_user_service_booking` method to `UserPersistence`
|
|
- [ ] Add `get_user_service_bookings` method to `UserPersistence`
|
|
- [ ] Add `create_service_booking_from_request` helper method
|
|
- [ ] Update `create_default_user_data` to include service bookings
|
|
|
|
### **Phase 3: Order Service Enhancement** ✅
|
|
- [ ] Add `create_service_bookings_from_order` method to `OrderService`
|
|
- [ ] Add `find_service_provider` method to `OrderService`
|
|
- [ ] Update `checkout` method to call service booking creation
|
|
- [ ] Test service booking creation with order processing
|
|
|
|
### **Phase 4: Dashboard Integration** ✅
|
|
- [ ] Add `get_user_service_bookings_api` endpoint to `DashboardController`
|
|
- [ ] Update `load_user_with_mock_data` to include service bookings
|
|
- [ ] Add service booking route to routing configuration
|
|
- [ ] Test API endpoint returns correct data
|
|
|
|
### **Phase 5: Frontend Implementation** ✅
|
|
- [ ] Add service bookings section to user dashboard template
|
|
- [ ] Add `loadServiceBookings` method to dashboard JavaScript
|
|
- [ ] Add `populateServiceBookingsTable` method
|
|
- [ ] Add CSS styles for service booking display
|
|
- [ ] Test frontend displays service bookings correctly
|
|
|
|
### **Phase 6: End-to-End Testing** ✅
|
|
- [ ] Test complete service workflow: create → list → purchase → track
|
|
- [ ] Verify service bookings appear in customer dashboard
|
|
- [ ] Verify service requests appear in provider dashboard
|
|
- [ ] Test data consistency between provider and customer views
|
|
- [ ] Test error handling and edge cases
|
|
|
|
## 🎯 **Success Criteria**
|
|
|
|
### **Functional Requirements**
|
|
1. **Complete Service Journey**: Create → List → Book → Track ✅
|
|
2. **Data Consistency**: Same booking ID in both provider and customer data ✅
|
|
3. **Dashboard Integration**: Service bookings visible in user dashboard ✅
|
|
4. **Revenue Attribution**: Correct revenue tracking for service providers ✅
|
|
|
|
### **Technical Requirements**
|
|
1. **Pattern Consistency**: Follows established builder and persistence patterns ✅
|
|
2. **Data Integrity**: All service bookings persist correctly ✅
|
|
3. **Performance**: Service booking creation doesn't impact order processing ✅
|
|
4. **Error Handling**: Graceful handling of service booking failures ✅
|
|
|
|
## 🚀 **Implementation Timeline**
|
|
|
|
**Total Estimated Time**: 12-16 hours
|
|
|
|
- **Day 1-2**: Data Models (Phase 1) - 2-3 hours
|
|
- **Day 2-3**: Service Persistence (Phase 2) - 2-3 hours
|
|
- **Day 3-4**: Order Service Enhancement (Phase 3) - 4-5 hours
|
|
- **Day 4-5**: Dashboard Integration (Phase 4) - 3-4 hours
|
|
- **Day 5**: Frontend Implementation (Phase 5) - 2-3 hours
|
|
|
|
## 🏆 **Expected Outcome**
|
|
|
|
After implementation, the service workflow will be complete:
|
|
|
|
1. **Service providers** create services (already working)
|
|
2. **Services** appear in marketplace (already working)
|
|
3. **Customers** can purchase services through checkout (new)
|
|
4. **Service requests** automatically created for providers (new)
|
|
5. **Service bookings** automatically created for customers (new)
|
|
6. **Both parties** can track service progress in their dashboards (new)
|
|
|
|
This brings services to full feature parity with the existing app workflow, completing the marketplace functionality for alpha testing. |