Features: - PDF service for generating task reports with ReportLab-style formatting - Storage service for file uploads (local and S3-compatible) - Admin authentication middleware with JWT support - Admin user model and repository Infrastructure: - Updated Docker configuration for admin panel builds - Email service enhancements for task notifications - Updated router with admin and file upload routes - Environment configuration updates Tests: - Unit tests for handlers (auth, residence, task) - Unit tests for models (user, residence, task) - Unit tests for repositories (user, residence, task) - Unit tests for services (residence, task) - Integration test setup - Test utilities for mocking database and services Database: - Admin user seed data - Updated test data seeds 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
93 lines
2.8 KiB
Go
93 lines
2.8 KiB
Go
package requests
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
// FlexibleDate handles both "2025-11-27" and "2025-11-27T00:00:00Z" formats
|
|
type FlexibleDate struct {
|
|
time.Time
|
|
}
|
|
|
|
func (fd *FlexibleDate) UnmarshalJSON(data []byte) error {
|
|
// Remove quotes
|
|
s := strings.Trim(string(data), "\"")
|
|
if s == "" || s == "null" {
|
|
return nil
|
|
}
|
|
|
|
// Try RFC3339 first (full datetime)
|
|
t, err := time.Parse(time.RFC3339, s)
|
|
if err == nil {
|
|
fd.Time = t
|
|
return nil
|
|
}
|
|
|
|
// Try date-only format
|
|
t, err = time.Parse("2006-01-02", s)
|
|
if err == nil {
|
|
fd.Time = t
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (fd FlexibleDate) MarshalJSON() ([]byte, error) {
|
|
if fd.Time.IsZero() {
|
|
return json.Marshal(nil)
|
|
}
|
|
return json.Marshal(fd.Time.Format(time.RFC3339))
|
|
}
|
|
|
|
// ToTimePtr returns a pointer to the underlying time, or nil if zero
|
|
func (fd *FlexibleDate) ToTimePtr() *time.Time {
|
|
if fd == nil || fd.Time.IsZero() {
|
|
return nil
|
|
}
|
|
return &fd.Time
|
|
}
|
|
|
|
// CreateTaskRequest represents the request to create a task
|
|
type CreateTaskRequest struct {
|
|
ResidenceID uint `json:"residence_id" binding:"required"`
|
|
Title string `json:"title" binding:"required,min=1,max=200"`
|
|
Description string `json:"description"`
|
|
CategoryID *uint `json:"category_id"`
|
|
PriorityID *uint `json:"priority_id"`
|
|
StatusID *uint `json:"status_id"`
|
|
FrequencyID *uint `json:"frequency_id"`
|
|
AssignedToID *uint `json:"assigned_to_id"`
|
|
DueDate *FlexibleDate `json:"due_date"`
|
|
EstimatedCost *decimal.Decimal `json:"estimated_cost"`
|
|
ContractorID *uint `json:"contractor_id"`
|
|
}
|
|
|
|
// UpdateTaskRequest represents the request to update a task
|
|
type UpdateTaskRequest struct {
|
|
Title *string `json:"title" binding:"omitempty,min=1,max=200"`
|
|
Description *string `json:"description"`
|
|
CategoryID *uint `json:"category_id"`
|
|
PriorityID *uint `json:"priority_id"`
|
|
StatusID *uint `json:"status_id"`
|
|
FrequencyID *uint `json:"frequency_id"`
|
|
AssignedToID *uint `json:"assigned_to_id"`
|
|
DueDate *FlexibleDate `json:"due_date"`
|
|
EstimatedCost *decimal.Decimal `json:"estimated_cost"`
|
|
ActualCost *decimal.Decimal `json:"actual_cost"`
|
|
ContractorID *uint `json:"contractor_id"`
|
|
}
|
|
|
|
// CreateTaskCompletionRequest represents the request to create a task completion
|
|
type CreateTaskCompletionRequest struct {
|
|
TaskID uint `json:"task_id" binding:"required"`
|
|
CompletedAt *time.Time `json:"completed_at"` // Defaults to now
|
|
Notes string `json:"notes"`
|
|
ActualCost *decimal.Decimal `json:"actual_cost"`
|
|
PhotoURL string `json:"photo_url"`
|
|
}
|