diff --git a/internal/mcp/tools/customers.go b/internal/mcp/tools/customers.go index 6a8bdc4..0e39076 100644 --- a/internal/mcp/tools/customers.go +++ b/internal/mcp/tools/customers.go @@ -3,7 +3,6 @@ package tools import ( "context" "fmt" - "strings" "github.com/beetlebugorg/tekmetric-mcp/pkg/tekmetric" "github.com/mark3labs/mcp-go/mcp" @@ -131,77 +130,3 @@ func (r *Registry) handleCustomers(arguments map[string]interface{}) (*mcp.CallT ) } -// formatCustomerSummary creates a formatted summary of a customer -func (r *Registry) formatCustomerSummary(c *tekmetric.Customer) (*mcp.CallToolResult, error) { - var summary strings.Builder - - // Header - summary.WriteString(fmt.Sprintf("%s %s", c.FirstName, c.LastName)) - if c.CustomerType != nil { - summary.WriteString(fmt.Sprintf(" (%s)", c.CustomerType.Name)) - } - summary.WriteString(fmt.Sprintf("\nCustomer ID: %d\n\n", c.ID)) - - // Contact Information - if c.Email != "" { - summary.WriteString(fmt.Sprintf("Email: %s\n", c.Email)) - } - - if len(c.Phone) > 0 { - for _, phone := range c.Phone { - phoneType := phone.Type - if phoneType == "" { - phoneType = "Phone" - } - primary := "" - if phone.Primary { - primary = " (Primary)" - } - summary.WriteString(fmt.Sprintf("%s: %s%s\n", phoneType, phone.Number, primary)) - } - } - - if c.Address != nil && (c.Address.Address1 != "" || c.Address.City != "") { - summary.WriteString("\nAddress:\n") - if c.Address.Address1 != "" { - summary.WriteString(fmt.Sprintf(" %s\n", c.Address.Address1)) - } - if c.Address.Address2 != "" { - summary.WriteString(fmt.Sprintf(" %s\n", c.Address.Address2)) - } - if c.Address.City != "" { - cityLine := fmt.Sprintf(" %s", c.Address.City) - if c.Address.State != "" { - cityLine += fmt.Sprintf(", %s", c.Address.State) - } - if c.Address.Zip != "" { - cityLine += fmt.Sprintf(" %s", c.Address.Zip) - } - summary.WriteString(cityLine + "\n") - } - } - - // Account Information - if c.EligibleForAccountsReceivable || c.CreditLimit > 0 || c.OkForMarketing { - summary.WriteString("\n") - if c.EligibleForAccountsReceivable { - summary.WriteString("Accounts Receivable: Yes\n") - } - if c.CreditLimit > 0 { - summary.WriteString(fmt.Sprintf("Credit Limit: $%.2f\n", c.CreditLimit)) - } - if c.OkForMarketing { - summary.WriteString("Marketing: Yes\n") - } - } - - // Notes - if c.Notes != "" { - summary.WriteString(fmt.Sprintf("\nNotes: %s\n", c.Notes)) - } - - // Metadata - summary.WriteString(fmt.Sprintf("\nCustomer Since: %s", c.CreatedDate.Format("January 2, 2006"))) - - return formatRichResult(summary.String(), c) -} diff --git a/internal/mcp/tools/employees.go b/internal/mcp/tools/employees.go index cb19b10..39886a9 100644 --- a/internal/mcp/tools/employees.go +++ b/internal/mcp/tools/employees.go @@ -13,7 +13,7 @@ import ( func (r *Registry) RegisterEmployeeTools(s *server.MCPServer) { s.AddTool( mcp.NewTool("employees", - mcp.WithDescription("Search and filter employees/technicians, or get a specific employee by ID. Supports filtering by active status, role, and more."), + mcp.WithDescription("Search employees/technicians by name or email, or get a specific employee by ID. Returns employee details including name, email, phone, role, and active status. Note: The Tekmetric API does not support filtering by role or active status - use search to find employees by name, then filter the results yourself."), mcp.WithNumber("id", mcp.Description("Get specific employee by ID"), ), @@ -23,12 +23,6 @@ func (r *Registry) RegisterEmployeeTools(s *server.MCPServer) { mcp.WithNumber("shop", mcp.Description("Shop ID (defaults to TEKMETRIC_DEFAULT_SHOP_ID)"), ), - mcp.WithBoolean("active", - mcp.Description("Filter by active status (true for active employees only, false for inactive)"), - ), - mcp.WithString("role", - mcp.Description("Filter by employee role (e.g., technician, service advisor, manager)"), - ), mcp.WithString("sort", mcp.Description("Property to sort results by (e.g., firstName, lastName, email)"), ), @@ -36,7 +30,7 @@ func (r *Registry) RegisterEmployeeTools(s *server.MCPServer) { mcp.Description("Sort direction (ASC or DESC)"), ), mcp.WithNumber("limit", - mcp.Description("Maximum number of results to return (max: 100, default: 20)"), + mcp.Description("Maximum number of results to return (max: 100, default: 10)"), ), mcp.WithNumber("page", mcp.Description("Page number for pagination (default: 0)"), diff --git a/internal/mcp/tools/repair_orders.go b/internal/mcp/tools/repair_orders.go index b060b17..8973e41 100644 --- a/internal/mcp/tools/repair_orders.go +++ b/internal/mcp/tools/repair_orders.go @@ -177,79 +177,3 @@ func (r *Registry) handleRepairOrders(arguments map[string]interface{}) (*mcp.Ca return formatJSON(response) } - -// formatRepairOrderSummary creates a rich formatted summary of a repair order -func (r *Registry) formatRepairOrderSummary(ro *tekmetric.RepairOrder) (*mcp.CallToolResult, error) { - var summary strings.Builder - - // Header - summary.WriteString(fmt.Sprintf("# Repair Order #%d\n\n", ro.RepairOrderNumber)) - - // Status - summary.WriteString(fmt.Sprintf("**Status:** %s\n", ro.RepairOrderStatus.Name)) - if ro.CompletedDate != nil { - summary.WriteString(fmt.Sprintf("**Completed:** %s\n", ro.CompletedDate.Format("2006-01-02 15:04"))) - } - if ro.PostedDate != nil { - summary.WriteString(fmt.Sprintf("**Posted:** %s\n", ro.PostedDate.Format("2006-01-02 15:04"))) - } - summary.WriteString("\n") - - // Financial Summary - summary.WriteString("## Financial Summary\n\n") - summary.WriteString(fmt.Sprintf("- **Labor:** %s\n", formatCurrency(ro.LaborSales))) - summary.WriteString(fmt.Sprintf("- **Parts:** %s\n", formatCurrency(ro.PartsSales))) - summary.WriteString(fmt.Sprintf("- **Sublet:** %s\n", formatCurrency(ro.SubletSales))) - summary.WriteString(fmt.Sprintf("- **Fees:** %s\n", formatCurrency(ro.FeeTotal))) - summary.WriteString(fmt.Sprintf("- **Discounts:** -%s\n", formatCurrency(ro.DiscountTotal))) - summary.WriteString(fmt.Sprintf("- **Taxes:** %s\n", formatCurrency(ro.Taxes))) - summary.WriteString(fmt.Sprintf("- **Total:** %s\n", formatCurrency(ro.TotalSales))) - summary.WriteString(fmt.Sprintf("- **Amount Paid:** %s\n", formatCurrency(ro.AmountPaid))) - - balance := ro.TotalSales - ro.AmountPaid - if balance > 0 { - summary.WriteString(fmt.Sprintf("- **Balance Due:** %s\n", formatCurrency(balance))) - } - summary.WriteString("\n") - - // Jobs - if len(ro.Jobs) > 0 { - summary.WriteString(fmt.Sprintf("## Jobs (%d)\n\n", len(ro.Jobs))) - for i, job := range ro.Jobs { - summary.WriteString(fmt.Sprintf("%d. **%s**\n", i+1, job.Name)) - if job.Note != "" { - summary.WriteString(fmt.Sprintf(" - %s\n", job.Note)) - } - if job.JobCategoryName != "" { - summary.WriteString(fmt.Sprintf(" - Category: %s\n", job.JobCategoryName)) - } - summary.WriteString(fmt.Sprintf(" - Labor: %s | Parts: %s | Subtotal: %s\n", - formatCurrency(job.LaborTotal), - formatCurrency(job.PartsTotal), - formatCurrency(job.Subtotal))) - if job.Authorized { - summary.WriteString(" - ✓ Authorized\n") - } - } - summary.WriteString("\n") - } - - // Customer Concerns - if len(ro.CustomerConcerns) > 0 { - summary.WriteString("## Customer Concerns\n\n") - for _, concern := range ro.CustomerConcerns { - summary.WriteString(fmt.Sprintf("- %s\n", concern.Concern)) - } - summary.WriteString("\n") - } - - // Vehicle Info - if ro.MilesIn != nil { - summary.WriteString(fmt.Sprintf("**Mileage In:** %.0f\n", *ro.MilesIn)) - } - if ro.MilesOut != nil { - summary.WriteString(fmt.Sprintf("**Mileage Out:** %.0f\n", *ro.MilesOut)) - } - - return formatRichResult(summary.String(), ro) -} diff --git a/internal/mcp/tools/vehicles.go b/internal/mcp/tools/vehicles.go index dbbbea0..2ec5ea4 100644 --- a/internal/mcp/tools/vehicles.go +++ b/internal/mcp/tools/vehicles.go @@ -3,7 +3,6 @@ package tools import ( "context" "fmt" - "strings" "github.com/beetlebugorg/tekmetric-mcp/pkg/tekmetric" "github.com/mark3labs/mcp-go/mcp" @@ -117,59 +116,3 @@ func (r *Registry) handleVehicles(arguments map[string]interface{}) (*mcp.CallTo "VEHICLES", ) } - -// formatVehicleSummary creates a formatted summary of a vehicle -func (r *Registry) formatVehicleSummary(v *tekmetric.Vehicle) (*mcp.CallToolResult, error) { - var summary strings.Builder - - // Header - vehicleName := fmt.Sprintf("%d %s %s", v.Year, v.Make, v.Model) - if v.SubModel != "" { - vehicleName += fmt.Sprintf(" %s", v.SubModel) - } - if v.Color != "" { - vehicleName += fmt.Sprintf(" (%s)", v.Color) - } - summary.WriteString(vehicleName + "\n") - summary.WriteString(fmt.Sprintf("Vehicle ID: %d\n\n", v.ID)) - - // Identification - if v.VIN != "" { - summary.WriteString(fmt.Sprintf("VIN: %s\n", v.VIN)) - } - if v.LicensePlate != "" { - summary.WriteString(fmt.Sprintf("License Plate: %s\n", v.LicensePlate)) - } - if v.UnitNumber != "" { - summary.WriteString(fmt.Sprintf("Unit Number: %s\n", v.UnitNumber)) - } - - // Mileage - if v.Mileage > 0 { - summary.WriteString(fmt.Sprintf("Current Mileage: %.0f miles\n", v.Mileage)) - } - - // Technical Specifications - if v.Engine != "" || v.Transmission != "" || v.DriveType != "" { - summary.WriteString("\n") - if v.Engine != "" { - summary.WriteString(fmt.Sprintf("Engine: %s\n", v.Engine)) - } - if v.Transmission != "" { - summary.WriteString(fmt.Sprintf("Transmission: %s\n", v.Transmission)) - } - if v.DriveType != "" { - summary.WriteString(fmt.Sprintf("Drive Type: %s\n", v.DriveType)) - } - } - - // Notes - if v.Notes != "" { - summary.WriteString(fmt.Sprintf("\nNotes: %s\n", v.Notes)) - } - - // Metadata - summary.WriteString(fmt.Sprintf("\nAdded: %s", v.CreatedDate.Format("January 2, 2006"))) - - return formatRichResult(summary.String(), v) -} diff --git a/pkg/tekmetric/appointments.go b/pkg/tekmetric/appointments.go index 2c66ab1..207070f 100644 --- a/pkg/tekmetric/appointments.go +++ b/pkg/tekmetric/appointments.go @@ -4,8 +4,42 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// Appointment represents an appointment +type Appointment struct { + ID int `json:"id"` + ShopID int `json:"shopId"` + CustomerID int `json:"customerId"` + VehicleID int `json:"vehicleId"` + ServiceWriterID *int `json:"serviceWriterId"` + TechnicianID *int `json:"technicianId"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Status string `json:"status"` + CustomerConcerns string `json:"customerConcerns,omitempty"` + Notes string `json:"notes,omitempty"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + DeletedDate *time.Time `json:"deletedDate"` +} + +// EnrichedAppointment represents an appointment with customer and vehicle details +type EnrichedAppointment struct { + Appointment + Customer *Customer `json:"customer,omitempty"` + Vehicle *Vehicle `json:"vehicle,omitempty"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // AppointmentQueryParams holds query parameters for appointment searches type AppointmentQueryParams struct { Shop int `url:"shop,omitempty"` diff --git a/pkg/tekmetric/customers.go b/pkg/tekmetric/customers.go index c6561f5..67577f9 100644 --- a/pkg/tekmetric/customers.go +++ b/pkg/tekmetric/customers.go @@ -4,8 +4,45 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// CustomerType represents the type of customer +type CustomerType struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` +} + +// Customer represents a Tekmetric customer +type Customer struct { + ID int `json:"id"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Email string `json:"email"` + Phone []Phone `json:"phone"` + CustomerType *CustomerType `json:"customerType,omitempty"` + ContactFirstName *string `json:"contactFirstName"` + ContactLastName *string `json:"contactLastName"` + Address *Address `json:"address"` + ShopID int `json:"shopId"` + EligibleForAccountsReceivable bool `json:"eligibleForAccountsReceivable"` + CreditLimit float64 `json:"creditLimit"` + OkForMarketing bool `json:"okForMarketing"` + Notes string `json:"notes,omitempty"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + DeletedDate *time.Time `json:"deletedDate"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // CustomerQueryParams holds query parameters for customer searches // Note: By default, deleted records are excluded unless DeletedDateStart/DeletedDateEnd are explicitly set type CustomerQueryParams struct { diff --git a/pkg/tekmetric/employees.go b/pkg/tekmetric/employees.go index de954a2..34da86f 100644 --- a/pkg/tekmetric/employees.go +++ b/pkg/tekmetric/employees.go @@ -4,8 +4,40 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// EmployeeRole represents an employee's role +type EmployeeRole struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` +} + +// Employee represents an employee +type Employee struct { + ID int `json:"id"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Email string `json:"email"` + Phone string `json:"phone,omitempty"` + EmployeeRole *EmployeeRole `json:"employeeRole,omitempty"` + CanPerformWork bool `json:"canPerformWork"` + CertificationNumber string `json:"certificationNumber,omitempty"` + ShopID int `json:"shopId"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + DeletedDate *time.Time `json:"deletedDate"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // EmployeeQueryParams holds query parameters for employee searches type EmployeeQueryParams struct { Shop int `url:"shop,omitempty"` diff --git a/pkg/tekmetric/inventory.go b/pkg/tekmetric/inventory.go index 37ce829..c58af5c 100644 --- a/pkg/tekmetric/inventory.go +++ b/pkg/tekmetric/inventory.go @@ -4,8 +4,46 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// InventoryPart represents an inventory part +type InventoryPart struct { + ID int `json:"id"` + ShopID int `json:"shopId"` + PartNumber string `json:"partNumber"` + Description string `json:"description"` + Brand string `json:"brand,omitempty"` + Cost Currency `json:"cost"` + Retail Currency `json:"retail"` + Quantity float64 `json:"quantity"` + Location string `json:"location,omitempty"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + DeletedDate *time.Time `json:"deletedDate"` +} + +// CannedJob represents a predefined job template +type CannedJob struct { + ID int `json:"id"` + ShopID int `json:"shopId"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + CategoryName string `json:"categoryName,omitempty"` + LaborRate int `json:"laborRate"` + LaborHours float64 `json:"laborHours"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // InventoryQueryParams holds query parameters for inventory searches type InventoryQueryParams struct { Shop int `url:"shop"` // Required: Shop ID diff --git a/pkg/tekmetric/jobs.go b/pkg/tekmetric/jobs.go index 40355fe..62f8a09 100644 --- a/pkg/tekmetric/jobs.go +++ b/pkg/tekmetric/jobs.go @@ -4,8 +4,138 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// PartType represents the type of a part +type PartType struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` +} + +// Part represents a vehicle part +type Part struct { + ID int `json:"id"` + Quantity float64 `json:"quantity"` + Brand string `json:"brand,omitempty"` + Name string `json:"name,omitempty"` + PartNumber string `json:"partNumber,omitempty"` + Description string `json:"description,omitempty"` + Cost Currency `json:"cost"` + Retail Currency `json:"retail"` + Model *string `json:"model,omitempty"` + Width *string `json:"width,omitempty"` + Ratio *float64 `json:"ratio,omitempty"` + Diameter *float64 `json:"diameter,omitempty"` + ConstructionType *string `json:"constructionType,omitempty"` + LoadIndex *string `json:"loadIndex,omitempty"` + SpeedRating *string `json:"speedRating,omitempty"` + PartType *PartType `json:"partType,omitempty"` + DOTNumbers []string `json:"dotNumbers,omitempty"` +} + +// Labor represents labor on a job +type Labor struct { + ID int `json:"id"` + Name string `json:"name"` + Rate Currency `json:"rate"` + Hours float64 `json:"hours"` + Complete bool `json:"complete"` +} + +// Fee represents a fee +type Fee struct { + ID int `json:"id"` + Name string `json:"name"` + Total Currency `json:"total"` +} + +// Discount represents a discount +type Discount struct { + ID int `json:"id"` + Name string `json:"name"` + Total Currency `json:"total"` +} + +// Job represents a job on a repair order +type Job struct { + ID int `json:"id"` + RepairOrderID int `json:"repairOrderId"` + VehicleID int `json:"vehicleId"` + CustomerID int `json:"customerId"` + Name string `json:"name"` + Authorized bool `json:"authorized"` + AuthorizedDate *string `json:"authorizedDate,omitempty"` + Selected bool `json:"selected"` + TechnicianID *int `json:"technicianId"` + Note string `json:"note,omitempty"` + JobCategoryName string `json:"jobCategoryName,omitempty"` + PartsTotal Currency `json:"partsTotal"` + LaborTotal Currency `json:"laborTotal"` + DiscountTotal Currency `json:"discountTotal"` + FeeTotal Currency `json:"feeTotal"` + Subtotal Currency `json:"subtotal"` + Archived bool `json:"archived"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + CompletedDate *time.Time `json:"completedDate,omitempty"` + Labor []Labor `json:"labor,omitempty"` + Parts []Part `json:"parts,omitempty"` + Fees []Fee `json:"fees,omitempty"` + Discounts []Discount `json:"discounts,omitempty"` + LaborHours float64 `json:"laborHours"` + LoggedHours float64 `json:"loggedHours"` + Sort int `json:"sort,omitempty"` +} + +// Vendor represents a vendor/supplier +type Vendor struct { + ID int `json:"id"` + Name string `json:"name"` + Nickname string `json:"nickname,omitempty"` + Website *string `json:"website"` + Phone *string `json:"phone"` +} + +// SubletItem represents an item in a sublet +type SubletItem struct { + ID int `json:"id"` + Name string `json:"name"` + Cost Currency `json:"cost"` + Price Currency `json:"price"` + Complete bool `json:"complete"` +} + +// Sublet represents subcontracted work +type Sublet struct { + ID int `json:"id"` + Name string `json:"name"` + Vendor *Vendor `json:"vendor,omitempty"` + Authorized *bool `json:"authorized"` + AuthorizedDate *string `json:"authorizedDate"` + Selected bool `json:"selected"` + Note *string `json:"note"` + Items []SubletItem `json:"items,omitempty"` + Price Currency `json:"price"` + Cost Currency `json:"cost"` +} + +// CustomerConcern represents a customer's concern +type CustomerConcern struct { + ID int `json:"id"` + Concern string `json:"concern"` + TechComment *string `json:"techComment"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // JobQueryParams holds query parameters for job searches type JobQueryParams struct { Shop int `url:"shop,omitempty"` diff --git a/pkg/tekmetric/models.go b/pkg/tekmetric/models.go index 5651222..7da6b45 100644 --- a/pkg/tekmetric/models.go +++ b/pkg/tekmetric/models.go @@ -2,7 +2,6 @@ package tekmetric import ( "encoding/json" - "time" ) // Currency represents a monetary value in cents that outputs as dollars @@ -32,18 +31,6 @@ type TokenResponse struct { Scope string `json:"scope"` // Space-separated shop IDs } -// Shop represents a Tekmetric shop -type Shop struct { - ID int `json:"id"` - Name string `json:"name"` - Nickname string `json:"nickname"` - Phone string `json:"phone"` - Email string `json:"email"` - Website string `json:"website"` - Address Address `json:"address"` - ROCustomLabelEnabled bool `json:"roCustomLabelEnabled"` -} - // Address represents a physical address type Address struct { ID int `json:"id"` @@ -64,292 +51,6 @@ type Phone struct { Primary bool `json:"primary"` } -// CustomerType represents the type of customer -type CustomerType struct { - ID int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` -} - -// Customer represents a Tekmetric customer -type Customer struct { - ID int `json:"id"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Email string `json:"email"` - Phone []Phone `json:"phone"` - CustomerType *CustomerType `json:"customerType,omitempty"` - ContactFirstName *string `json:"contactFirstName"` - ContactLastName *string `json:"contactLastName"` - Address *Address `json:"address"` - ShopID int `json:"shopId"` - EligibleForAccountsReceivable bool `json:"eligibleForAccountsReceivable"` - CreditLimit float64 `json:"creditLimit"` - OkForMarketing bool `json:"okForMarketing"` - Notes string `json:"notes,omitempty"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - DeletedDate *time.Time `json:"deletedDate"` -} - -// Vehicle represents a vehicle -type Vehicle struct { - ID int `json:"id"` - CustomerID int `json:"customerId"` - ShopID int `json:"shopId"` - Year int `json:"year"` - Make string `json:"make"` - Model string `json:"model"` - SubModel string `json:"subModel,omitempty"` - VIN string `json:"vin"` - LicensePlate string `json:"licensePlate,omitempty"` - Color string `json:"color,omitempty"` - UnitNumber string `json:"unitNumber,omitempty"` - ProductionDate *string `json:"productionDate,omitempty"` - Mileage float64 `json:"mileage"` - Engine string `json:"engine,omitempty"` - Transmission string `json:"transmission,omitempty"` - DriveType string `json:"driveType,omitempty"` - Notes string `json:"notes,omitempty"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - DeletedDate *time.Time `json:"deletedDate"` -} - -// RepairOrderStatus represents the status of a repair order -type RepairOrderStatus struct { - ID int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` -} - -// RepairOrderLabel represents a label for a repair order -type RepairOrderLabel struct { - ID int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` - Status *RepairOrderStatus `json:"status,omitempty"` -} - -// RepairOrderCustomLabel represents a custom label -type RepairOrderCustomLabel struct { - Name string `json:"name"` -} - -// RepairOrder represents a repair order -type RepairOrder struct { - ID int `json:"id"` - RepairOrderNumber int `json:"repairOrderNumber"` - ShopID int `json:"shopId"` - RepairOrderStatus RepairOrderStatus `json:"repairOrderStatus"` - RepairOrderLabel *RepairOrderLabel `json:"repairOrderLabel,omitempty"` - RepairOrderCustomLabel *RepairOrderCustomLabel `json:"repairOrderCustomLabel,omitempty"` - Color string `json:"color,omitempty"` - AppointmentStartTime *time.Time `json:"appointmentStartTime,omitempty"` - CustomerID int `json:"customerId"` - TechnicianID *int `json:"technicianId"` - ServiceWriterID *int `json:"serviceWriterId"` - VehicleID int `json:"vehicleId"` - MilesIn *float64 `json:"milesIn"` - MilesOut *float64 `json:"milesOut"` - Keytag *string `json:"keytag"` - CompletedDate *time.Time `json:"completedDate"` - PostedDate *time.Time `json:"postedDate"` - LaborSales Currency `json:"laborSales"` - PartsSales Currency `json:"partsSales"` - SubletSales Currency `json:"subletSales"` - DiscountTotal Currency `json:"discountTotal"` - FeeTotal Currency `json:"feeTotal"` - Taxes Currency `json:"taxes"` - AmountPaid Currency `json:"amountPaid"` - TotalSales Currency `json:"totalSales"` - Jobs []Job `json:"jobs,omitempty"` - Sublets []Sublet `json:"sublets,omitempty"` - Fees []Fee `json:"fees,omitempty"` - Discounts []Discount `json:"discounts,omitempty"` - CustomerConcerns []CustomerConcern `json:"customerConcerns,omitempty"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - DeletedDate *time.Time `json:"deletedDate"` -} - -// PartType represents the type of a part -type PartType struct { - ID int `json:"id"` - Code string `json:"code"` - Name string `json:"name"` -} - -// Part represents a vehicle part -type Part struct { - ID int `json:"id"` - Quantity float64 `json:"quantity"` - Brand string `json:"brand,omitempty"` - Name string `json:"name,omitempty"` - PartNumber string `json:"partNumber,omitempty"` - Description string `json:"description,omitempty"` - Cost Currency `json:"cost"` - Retail Currency `json:"retail"` - Model *string `json:"model,omitempty"` - Width *string `json:"width,omitempty"` - Ratio *float64 `json:"ratio,omitempty"` - Diameter *float64 `json:"diameter,omitempty"` - ConstructionType *string `json:"constructionType,omitempty"` - LoadIndex *string `json:"loadIndex,omitempty"` - SpeedRating *string `json:"speedRating,omitempty"` - PartType *PartType `json:"partType,omitempty"` - DOTNumbers []string `json:"dotNumbers,omitempty"` -} - -// Labor represents labor on a job -type Labor struct { - ID int `json:"id"` - Name string `json:"name"` - Rate Currency `json:"rate"` - Hours float64 `json:"hours"` - Complete bool `json:"complete"` -} - -// Fee represents a fee -type Fee struct { - ID int `json:"id"` - Name string `json:"name"` - Total Currency `json:"total"` -} - -// Discount represents a discount -type Discount struct { - ID int `json:"id"` - Name string `json:"name"` - Total Currency `json:"total"` -} - -// Job represents a job on a repair order -type Job struct { - ID int `json:"id"` - RepairOrderID int `json:"repairOrderId"` - VehicleID int `json:"vehicleId"` - CustomerID int `json:"customerId"` - Name string `json:"name"` - Authorized bool `json:"authorized"` - AuthorizedDate *string `json:"authorizedDate,omitempty"` - Selected bool `json:"selected"` - TechnicianID *int `json:"technicianId"` - Note string `json:"note,omitempty"` - JobCategoryName string `json:"jobCategoryName,omitempty"` - PartsTotal Currency `json:"partsTotal"` - LaborTotal Currency `json:"laborTotal"` - DiscountTotal Currency `json:"discountTotal"` - FeeTotal Currency `json:"feeTotal"` - Subtotal Currency `json:"subtotal"` - Archived bool `json:"archived"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - CompletedDate *time.Time `json:"completedDate,omitempty"` - Labor []Labor `json:"labor,omitempty"` - Parts []Part `json:"parts,omitempty"` - Fees []Fee `json:"fees,omitempty"` - Discounts []Discount `json:"discounts,omitempty"` - LaborHours float64 `json:"laborHours"` - LoggedHours float64 `json:"loggedHours"` - Sort int `json:"sort,omitempty"` -} - -// Vendor represents a vendor/supplier -type Vendor struct { - ID int `json:"id"` - Name string `json:"name"` - Nickname string `json:"nickname,omitempty"` - Website *string `json:"website"` - Phone *string `json:"phone"` -} - -// SubletItem represents an item in a sublet -type SubletItem struct { - ID int `json:"id"` - Name string `json:"name"` - Cost Currency `json:"cost"` - Price Currency `json:"price"` - Complete bool `json:"complete"` -} - -// Sublet represents subcontracted work -type Sublet struct { - ID int `json:"id"` - Name string `json:"name"` - Vendor *Vendor `json:"vendor,omitempty"` - Authorized *bool `json:"authorized"` - AuthorizedDate *string `json:"authorizedDate"` - Selected bool `json:"selected"` - Note *string `json:"note"` - Items []SubletItem `json:"items,omitempty"` - Price Currency `json:"price"` - Cost Currency `json:"cost"` -} - -// CustomerConcern represents a customer's concern -type CustomerConcern struct { - ID int `json:"id"` - Concern string `json:"concern"` - TechComment *string `json:"techComment"` -} - -// Appointment represents an appointment -type Appointment struct { - ID int `json:"id"` - ShopID int `json:"shopId"` - CustomerID int `json:"customerId"` - VehicleID int `json:"vehicleId"` - ServiceWriterID *int `json:"serviceWriterId"` - TechnicianID *int `json:"technicianId"` - StartTime time.Time `json:"startTime"` - EndTime time.Time `json:"endTime"` - Status string `json:"status"` - CustomerConcerns string `json:"customerConcerns,omitempty"` - Notes string `json:"notes,omitempty"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - DeletedDate *time.Time `json:"deletedDate"` -} - -// EnrichedAppointment represents an appointment with customer and vehicle details -type EnrichedAppointment struct { - Appointment - Customer *Customer `json:"customer,omitempty"` - Vehicle *Vehicle `json:"vehicle,omitempty"` -} - -// Employee represents an employee -type Employee struct { - ID int `json:"id"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Email string `json:"email"` - Phone string `json:"phone,omitempty"` - Role string `json:"role"` - Active bool `json:"active"` - ShopID int `json:"shopId"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - DeletedDate *time.Time `json:"deletedDate"` -} - -// InventoryPart represents an inventory part -type InventoryPart struct { - ID int `json:"id"` - ShopID int `json:"shopId"` - PartNumber string `json:"partNumber"` - Description string `json:"description"` - Brand string `json:"brand,omitempty"` - Cost Currency `json:"cost"` - Retail Currency `json:"retail"` - Quantity float64 `json:"quantity"` - Location string `json:"location,omitempty"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` - DeletedDate *time.Time `json:"deletedDate"` -} - // PaginatedResponse represents a paginated API response type PaginatedResponse[T any] struct { Content []T `json:"content"` @@ -370,16 +71,3 @@ type APIResponse[T any] struct { Data T `json:"data"` Details map[string]interface{} `json:"details"` } - -// CannedJob represents a predefined job template -type CannedJob struct { - ID int `json:"id"` - ShopID int `json:"shopId"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - CategoryName string `json:"categoryName,omitempty"` - LaborRate int `json:"laborRate"` - LaborHours float64 `json:"laborHours"` - CreatedDate time.Time `json:"createdDate"` - UpdatedDate time.Time `json:"updatedDate"` -} diff --git a/pkg/tekmetric/repair_orders.go b/pkg/tekmetric/repair_orders.go index bbc1c0a..dd0321d 100644 --- a/pkg/tekmetric/repair_orders.go +++ b/pkg/tekmetric/repair_orders.go @@ -4,8 +4,74 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// RepairOrderStatus represents the status of a repair order +type RepairOrderStatus struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` +} + +// RepairOrderLabel represents a label for a repair order +type RepairOrderLabel struct { + ID int `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + Status *RepairOrderStatus `json:"status,omitempty"` +} + +// RepairOrderCustomLabel represents a custom label +type RepairOrderCustomLabel struct { + Name string `json:"name"` +} + +// RepairOrder represents a repair order +type RepairOrder struct { + ID int `json:"id"` + RepairOrderNumber int `json:"repairOrderNumber"` + ShopID int `json:"shopId"` + RepairOrderStatus RepairOrderStatus `json:"repairOrderStatus"` + RepairOrderLabel *RepairOrderLabel `json:"repairOrderLabel,omitempty"` + RepairOrderCustomLabel *RepairOrderCustomLabel `json:"repairOrderCustomLabel,omitempty"` + Color string `json:"color,omitempty"` + AppointmentStartTime *time.Time `json:"appointmentStartTime,omitempty"` + CustomerID int `json:"customerId"` + TechnicianID *int `json:"technicianId"` + ServiceWriterID *int `json:"serviceWriterId"` + VehicleID int `json:"vehicleId"` + MilesIn *float64 `json:"milesIn"` + MilesOut *float64 `json:"milesOut"` + Keytag *string `json:"keytag"` + CompletedDate *time.Time `json:"completedDate"` + PostedDate *time.Time `json:"postedDate"` + LaborSales Currency `json:"laborSales"` + PartsSales Currency `json:"partsSales"` + SubletSales Currency `json:"subletSales"` + DiscountTotal Currency `json:"discountTotal"` + FeeTotal Currency `json:"feeTotal"` + Taxes Currency `json:"taxes"` + AmountPaid Currency `json:"amountPaid"` + TotalSales Currency `json:"totalSales"` + Jobs []Job `json:"jobs,omitempty"` + Sublets []Sublet `json:"sublets,omitempty"` + Fees []Fee `json:"fees,omitempty"` + Discounts []Discount `json:"discounts,omitempty"` + CustomerConcerns []CustomerConcern `json:"customerConcerns,omitempty"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + DeletedDate *time.Time `json:"deletedDate"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // RepairOrderQueryParams holds query parameters for repair order searches type RepairOrderQueryParams struct { Shop int `url:"shop,omitempty"` diff --git a/pkg/tekmetric/shops.go b/pkg/tekmetric/shops.go index dc8303e..c3d6ec5 100644 --- a/pkg/tekmetric/shops.go +++ b/pkg/tekmetric/shops.go @@ -5,6 +5,26 @@ import ( "fmt" ) +// ============================================================================ +// Models +// ============================================================================ + +// Shop represents a Tekmetric shop +type Shop struct { + ID int `json:"id"` + Name string `json:"name"` + Nickname string `json:"nickname"` + Phone string `json:"phone"` + Email string `json:"email"` + Website string `json:"website"` + Address Address `json:"address"` + ROCustomLabelEnabled bool `json:"roCustomLabelEnabled"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // GetShops returns all shops accessible by the current token func (c *Client) GetShops(ctx context.Context) ([]Shop, error) { var shops []Shop diff --git a/pkg/tekmetric/vehicles.go b/pkg/tekmetric/vehicles.go index 17f49d0..11603b5 100644 --- a/pkg/tekmetric/vehicles.go +++ b/pkg/tekmetric/vehicles.go @@ -4,8 +4,41 @@ import ( "context" "fmt" "net/url" + "time" ) +// ============================================================================ +// Models +// ============================================================================ + +// Vehicle represents a vehicle +type Vehicle struct { + ID int `json:"id"` + CustomerID int `json:"customerId"` + ShopID int `json:"shopId"` + Year int `json:"year"` + Make string `json:"make"` + Model string `json:"model"` + SubModel string `json:"subModel,omitempty"` + VIN string `json:"vin"` + LicensePlate string `json:"licensePlate,omitempty"` + Color string `json:"color,omitempty"` + UnitNumber string `json:"unitNumber,omitempty"` + ProductionDate *string `json:"productionDate,omitempty"` + Mileage float64 `json:"mileage"` + Engine string `json:"engine,omitempty"` + Transmission string `json:"transmission,omitempty"` + DriveType string `json:"driveType,omitempty"` + Notes string `json:"notes,omitempty"` + CreatedDate time.Time `json:"createdDate"` + UpdatedDate time.Time `json:"updatedDate"` + DeletedDate *time.Time `json:"deletedDate"` +} + +// ============================================================================ +// API Methods +// ============================================================================ + // VehicleQueryParams holds query parameters for vehicle searches type VehicleQueryParams struct { Shop int `url:"shop,omitempty"`