add holiday test and tweak services
This commit is contained in:
@@ -318,7 +318,15 @@ func GetAvailableHours(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Load bookings
|
// Load bookings
|
||||||
bookingRows, _ := db.DB.Query(r.Context(), `
|
bookingRows, _ := db.DB.Query(r.Context(), `
|
||||||
SELECT b.start_time, COALESCE(SUM(s.duration_minutes),0) as total_duration
|
SELECT
|
||||||
|
b.start_time,
|
||||||
|
COALESCE(SUM(
|
||||||
|
CASE
|
||||||
|
WHEN bs.override_duration_minutes IS NOT NULL AND bs.override_duration_minutes > 0
|
||||||
|
THEN bs.override_duration_minutes
|
||||||
|
ELSE s.duration_minutes
|
||||||
|
END
|
||||||
|
), 0) AS total_duration
|
||||||
FROM bookings b
|
FROM bookings b
|
||||||
LEFT JOIN booking_services bs ON b.id = bs.booking_id
|
LEFT JOIN booking_services bs ON b.id = bs.booking_id
|
||||||
LEFT JOIN services s ON bs.service_id = s.id
|
LEFT JOIN services s ON bs.service_id = s.id
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ type Service struct {
|
|||||||
PatchTestDurationHours int `json:"patch_test_duration_hours"`
|
PatchTestDurationHours int `json:"patch_test_duration_hours"`
|
||||||
MinimumAgeRequired int `json:"minimum_age_required"`
|
MinimumAgeRequired int `json:"minimum_age_required"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
CreatedBy *string `json:"created_by,omitempty"`
|
CreatedBy *string `json:"created_by,omitempty"`
|
||||||
UpdatedBy *string `json:"updated_by,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceResponse struct {
|
type ServiceResponse struct {
|
||||||
@@ -55,8 +53,8 @@ func ToggleService(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := "UPDATE services SET is_active = NOT is_active, updated_at = NOW(), updated_by = $1 WHERE id = $2"
|
query := "UPDATE services SET is_active = NOT is_active WHERE id = $1"
|
||||||
result, err := db.DB.Exec(r.Context(), query, r.Context().Value(mw.UserIDKey), serviceID)
|
result, err := db.DB.Exec(r.Context(), query, serviceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to toggle service: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to toggle service: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -112,17 +110,17 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
query := `
|
query := `
|
||||||
INSERT INTO services (
|
INSERT INTO services (
|
||||||
name, description, price, duration_minutes,
|
name, description, price, duration_minutes,
|
||||||
patch_test_duration_hours, minimum_age_required, created_by, updated_by
|
patch_test_duration_hours, minimum_age_required, created_by
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
RETURNING
|
RETURNING
|
||||||
id, name, description, price, duration_minutes, is_active,
|
id, name, description, price, duration_minutes, is_active,
|
||||||
patch_test_duration_hours, minimum_age_required, created_at,
|
patch_test_duration_hours, minimum_age_required, created_at,
|
||||||
updated_at, created_by, updated_by
|
created_by
|
||||||
`
|
`
|
||||||
|
|
||||||
var service Service
|
var service Service
|
||||||
var createdByDB, updatedByDB sql.NullString
|
var createdByDB sql.NullString
|
||||||
|
|
||||||
err := db.DB.QueryRow(r.Context(),
|
err := db.DB.QueryRow(r.Context(),
|
||||||
query,
|
query,
|
||||||
@@ -133,7 +131,6 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
req.PatchTestDurationHours,
|
req.PatchTestDurationHours,
|
||||||
req.MinimumAgeRequired,
|
req.MinimumAgeRequired,
|
||||||
createdBy,
|
createdBy,
|
||||||
createdBy, // updated_by same as created_by for new records
|
|
||||||
).Scan(
|
).Scan(
|
||||||
&service.ID,
|
&service.ID,
|
||||||
&service.Name,
|
&service.Name,
|
||||||
@@ -144,9 +141,7 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
&service.PatchTestDurationHours,
|
&service.PatchTestDurationHours,
|
||||||
&service.MinimumAgeRequired,
|
&service.MinimumAgeRequired,
|
||||||
&service.CreatedAt,
|
&service.CreatedAt,
|
||||||
&service.UpdatedAt,
|
|
||||||
&createdByDB,
|
&createdByDB,
|
||||||
&updatedByDB,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -163,9 +158,6 @@ func CreateServiceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if createdByDB.Valid {
|
if createdByDB.Valid {
|
||||||
service.CreatedBy = &createdByDB.String
|
service.CreatedBy = &createdByDB.String
|
||||||
}
|
}
|
||||||
if updatedByDB.Valid {
|
|
||||||
service.UpdatedBy = &updatedByDB.String
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return created service
|
// Return created service
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
@@ -270,8 +262,7 @@ func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Query all services including inactive ones
|
// Query all services including inactive ones
|
||||||
query := `
|
query := `
|
||||||
SELECT id, name, description, price, duration_minutes, is_active,
|
SELECT id, name, description, price, duration_minutes, is_active,
|
||||||
patch_test_duration_hours, minimum_age_required, created_at,
|
patch_test_duration_hours, minimum_age_required, created_at, created_by
|
||||||
updated_at, created_by, updated_by
|
|
||||||
FROM services
|
FROM services
|
||||||
ORDER BY is_active DESC, name
|
ORDER BY is_active DESC, name
|
||||||
`
|
`
|
||||||
@@ -287,7 +278,7 @@ func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var service Service
|
var service Service
|
||||||
var createdBy, updatedBy sql.NullString
|
var createdBy sql.NullString
|
||||||
|
|
||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&service.ID,
|
&service.ID,
|
||||||
@@ -299,9 +290,7 @@ func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
&service.PatchTestDurationHours,
|
&service.PatchTestDurationHours,
|
||||||
&service.MinimumAgeRequired,
|
&service.MinimumAgeRequired,
|
||||||
&service.CreatedAt,
|
&service.CreatedAt,
|
||||||
&service.UpdatedAt,
|
|
||||||
&createdBy,
|
&createdBy,
|
||||||
&updatedBy,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to read service data: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to read service data: "+err.Error(), http.StatusInternalServerError)
|
||||||
@@ -312,9 +301,6 @@ func AllServicesHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if createdBy.Valid {
|
if createdBy.Valid {
|
||||||
service.CreatedBy = &createdBy.String
|
service.CreatedBy = &createdBy.String
|
||||||
}
|
}
|
||||||
if updatedBy.Valid {
|
|
||||||
service.UpdatedBy = &updatedBy.String
|
|
||||||
}
|
|
||||||
|
|
||||||
services = append(services, service)
|
services = append(services, service)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ CREATE TYPE account_role AS ENUM ('unverified_email', 'verified_email', 'admin',
|
|||||||
CREATE TYPE account_type AS ENUM ('email', 'google', 'microsoft', 'facebook', 'guest');
|
CREATE TYPE account_type AS ENUM ('email', 'google', 'microsoft', 'facebook', 'guest');
|
||||||
CREATE TYPE payment_type AS ENUM ('deposit', 'full', 'tip', 'balance', 'partial');
|
CREATE TYPE payment_type AS ENUM ('deposit', 'full', 'tip', 'balance', 'partial');
|
||||||
CREATE TYPE payment_method AS ENUM ('online_square', 'in_person_card', 'cash', 'giftcard', 'discount');
|
CREATE TYPE payment_method AS ENUM ('online_square', 'in_person_card', 'cash', 'giftcard', 'discount');
|
||||||
CREATE TYPE booking_status AS ENUM ('pending', 'confirmed', 'in_progress', 'completed', 'cancelled', 'no_show');
|
CREATE TYPE booking_status AS ENUM ('pending', 'confirmed', 'in_progress', 'completed', 'client_cancelled', 'we_cancelled', 're-schedule', 'no_show');
|
||||||
CREATE TYPE payment_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
|
CREATE TYPE payment_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
|
||||||
|
|
||||||
-- =======================================
|
-- =======================================
|
||||||
@@ -105,7 +105,9 @@ CREATE TABLE users (
|
|||||||
data_consent_updated_at TIMESTAMPTZ DEFAULT NOW(),
|
data_consent_updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
-- Audit fields
|
-- Audit fields
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
-- staff fields
|
||||||
|
notes TEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE user_social_logins (
|
CREATE TABLE user_social_logins (
|
||||||
@@ -144,10 +146,10 @@ CREATE TABLE services (
|
|||||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
patch_test_duration_hours INT NOT NULL DEFAULT 0,
|
patch_test_duration_hours INT NOT NULL DEFAULT 0,
|
||||||
minimum_age_required INT NOT NULL DEFAULT 16,
|
minimum_age_required INT NOT NULL DEFAULT 16,
|
||||||
|
requires_manual_pricing BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
requires_manual_duration BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
created_by CHAR(12),
|
created_by CHAR(12),
|
||||||
updated_by CHAR(12)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_services_name ON services(name);
|
CREATE INDEX idx_services_name ON services(name);
|
||||||
@@ -170,10 +172,10 @@ CREATE TABLE bookings (
|
|||||||
user_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
|
user_id CHAR(12) REFERENCES users(id) ON DELETE SET NULL,
|
||||||
start_time TIMESTAMPTZ NOT NULL,
|
start_time TIMESTAMPTZ NOT NULL,
|
||||||
status booking_status NOT NULL DEFAULT 'pending',
|
status booking_status NOT NULL DEFAULT 'pending',
|
||||||
|
notes TEXT,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
created_by CHAR(12),
|
created_by CHAR(12),
|
||||||
updated_by CHAR(12)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_bookings_userid ON bookings(user_id);
|
CREATE INDEX idx_bookings_userid ON bookings(user_id);
|
||||||
@@ -188,6 +190,8 @@ CREATE INDEX idx_bookings_userid_starttime ON bookings(user_id, start_time);
|
|||||||
CREATE TABLE booking_services (
|
CREATE TABLE booking_services (
|
||||||
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
|
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
|
||||||
service_id CHAR(12) NOT NULL REFERENCES services(id) ON DELETE RESTRICT,
|
service_id CHAR(12) NOT NULL REFERENCES services(id) ON DELETE RESTRICT,
|
||||||
|
override_price NUMERIC(10,2),
|
||||||
|
override_duration_minutes INT,
|
||||||
PRIMARY KEY (booking_id, service_id)
|
PRIMARY KEY (booking_id, service_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -257,7 +261,7 @@ CREATE SEQUENCE invoice_number_seq
|
|||||||
|
|
||||||
CREATE TABLE payments (
|
CREATE TABLE payments (
|
||||||
id CHAR(12) PRIMARY KEY DEFAULT generate_payment_id(),
|
id CHAR(12) PRIMARY KEY DEFAULT generate_payment_id(),
|
||||||
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE CASCADE,
|
booking_id CHAR(12) NOT NULL REFERENCES bookings(id) ON DELETE RESTRICT,
|
||||||
payment_type payment_type NOT NULL,
|
payment_type payment_type NOT NULL,
|
||||||
payment_method payment_method NOT NULL,
|
payment_method payment_method NOT NULL,
|
||||||
vendor_code TEXT,
|
vendor_code TEXT,
|
||||||
@@ -272,7 +276,6 @@ CREATE TABLE payments (
|
|||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
created_by CHAR(12),
|
created_by CHAR(12),
|
||||||
updated_by CHAR(12)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_payments_bookingid ON payments(booking_id);
|
CREATE INDEX idx_payments_bookingid ON payments(booking_id);
|
||||||
|
|||||||
@@ -165,6 +165,84 @@ echo ""
|
|||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo "✅ Successfully created: $SUCCESS_COUNT services"
|
echo "✅ Successfully created: $SUCCESS_COUNT services"
|
||||||
echo "❌ Failed: $FAIL_COUNT services"
|
echo "❌ Failed: $FAIL_COUNT services"
|
||||||
|
|
||||||
|
# --- 5️⃣ Create Holiday Exceptional Groups ---
|
||||||
|
echo ""
|
||||||
|
echo "5️⃣ Creating Holiday Exceptional Groups..."
|
||||||
|
|
||||||
|
# November Break (week of Nov 10-16, 2025) - Closed entirely
|
||||||
|
NOVEMBER_BREAK='{
|
||||||
|
"name": "November Break",
|
||||||
|
"description": "Short break period in November",
|
||||||
|
"hours": [
|
||||||
|
{"weekday": 0, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 1, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 2, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 3, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 4, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 5, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 6, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false}
|
||||||
|
],
|
||||||
|
"weekStarts": ["2025-11-10"]
|
||||||
|
}'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "Creating: November Break"
|
||||||
|
NOV_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d "$NOVEMBER_BREAK" \
|
||||||
|
"$BASE_URL/scheduling/exceptional-groups")
|
||||||
|
HTTP_CODE=$(echo "$NOV_RESPONSE" | tail -n1)
|
||||||
|
RESPONSE_BODY=$(echo "$NOV_RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
echo "✅ Created: November Break"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to create November Break (HTTP $HTTP_CODE)"
|
||||||
|
echo "Response: $RESPONSE_BODY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 0.2
|
||||||
|
|
||||||
|
# Christmas Holiday (weeks of Dec 22-28 and Dec 29-Jan 4) - Limited hours
|
||||||
|
CHRISTMAS_BREAK='{
|
||||||
|
"name": "Christmas Holiday Period",
|
||||||
|
"description": "Reduced hours for Christmas and New Year",
|
||||||
|
"hours": [
|
||||||
|
{"weekday": 0, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 1, "startTime": "10:00:00", "endTime": "15:00:00", "isOpen": true},
|
||||||
|
{"weekday": 2, "startTime": "10:00:00", "endTime": "15:00:00", "isOpen": true},
|
||||||
|
{"weekday": 3, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 4, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 5, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false},
|
||||||
|
{"weekday": 6, "startTime": "00:00:00", "endTime": "00:00:00", "isOpen": false}
|
||||||
|
],
|
||||||
|
"weekStarts": ["2025-12-22", "2025-12-29"]
|
||||||
|
}'
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "Creating: Christmas Holiday Period"
|
||||||
|
XMAS_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d "$CHRISTMAS_BREAK" \
|
||||||
|
"$BASE_URL/scheduling/exceptional-groups")
|
||||||
|
HTTP_CODE=$(echo "$XMAS_RESPONSE" | tail -n1)
|
||||||
|
RESPONSE_BODY=$(echo "$XMAS_RESPONSE" | sed '$d')
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
echo "✅ Created: Christmas Holiday Period"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to create Christmas Holiday Period (HTTP $HTTP_CODE)"
|
||||||
|
echo "Response: $RESPONSE_BODY"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo "🎉 Seeding completed!"
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod +x /tmp/seed_data.sh
|
chmod +x /tmp/seed_data.sh
|
||||||
|
|||||||
Reference in New Issue
Block a user