package services import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "strings" "time" "github.com/ydn/yourdreamnamehere/internal/config" "github.com/ydn/yourdreamnamehere/internal/models" ) type DolibarrService struct { db *gorm.DB config *config.Config client *http.Client } type DolibarrCustomer struct { ID int `json:"id"` Name string `json:"name"` Email string `json:"email"` Phone string `json:"phone"` Address string `json:"address"` Zip string `json:"zip"` Town string `json:"town"` Country string `json:"country"` CustomerCode string `json:"customer_code"` } type DolibarrInvoice struct { ID int `json:"id"` Ref string `json:"ref"` Total float64 `json:"total"` Status string `json:"status"` Date string `json:"date"` CustomerID int `json:"socid"` } type DolibarrProduct struct { ID int `json:"id"` Ref string `json:"ref"` Label string `json:"label"` Description string `json:"description"` Price float64 `json:"price"` } func NewDolibarrService(db *gorm.DB, config *config.Config) *DolibarrService { return &DolibarrService{ db: db, config: config, client: &http.Client{ Timeout: 30 * time.Second, }, } } func (s *DolibarrService) CreateCustomer(customer *models.Customer) (*DolibarrCustomer, error) { // Prepare customer data for Dolibarr doliCustomer := map[string]interface{}{ "name": customer.Email, // Use email as name since we don't have company name "email": customer.Email, "client": 1, "fournisseur": 0, "customer_code": fmt.Sprintf("CU%06d", time.Now().Unix() % 999999), "status": 1, } jsonData, err := json.Marshal(doliCustomer) if err != nil { return nil, fmt.Errorf("failed to marshal customer data: %w", err) } // Make API request to Dolibarr req, err := http.NewRequest("POST", s.config.Dolibarr.URL+"/api/index.php/thirdparties", strings.NewReader(string(jsonData))) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("DOLAPIKEY", s.config.Dolibarr.APIToken) resp, err := s.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("Dolibarr API error: %d - %s", resp.StatusCode, string(body)) } var createdCustomer DolibarrCustomer if err := json.NewDecoder(resp.Body).Decode(&createdCustomer); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } log.Printf("Created customer in Dolibarr: %d", createdCustomer.ID) return &createdCustomer, nil } func (s *DolibarrService) CreateInvoice(customerID int, amount float64, description string) (*DolibarrInvoice, error) { // Prepare invoice data doliInvoice := map[string]interface{}{ "socid": customerID, "type": 0, // Standard invoice "date": time.Now().Format("2006-01-02"), "date_lim_reglement": time.Now().AddDate(0, 1, 0).Format("2006-01-02"), // Due in 1 month "cond_reglement_code": "RECEP", "mode_reglement_code": "CB", "note_public": description, "lines": []map[string]interface{}{ { "desc": description, "subprice": amount, "qty": 1, "tva_tx": 0.0, // No tax for B2B SaaS "product_type": 1, // Service }, }, } jsonData, err := json.Marshal(doliInvoice) if err != nil { return nil, fmt.Errorf("failed to marshal invoice data: %w", err) } // Make API request req, err := http.NewRequest("POST", s.config.Dolibarr.URL+"/api/index.php/invoices", strings.NewReader(string(jsonData))) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("DOLAPIKEY", s.config.Dolibarr.APIToken) resp, err := s.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("Dolibarr API error: %d - %s", resp.StatusCode, string(body)) } var createdInvoice DolibarrInvoice if err := json.NewDecoder(resp.Body).Decode(&createdInvoice); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } // Validate the invoice validateReq, err := http.NewRequest("POST", fmt.Sprintf("%s/api/index.php/invoices/%d/validate", s.config.Dolibarr.URL, createdInvoice.ID), strings.NewReader("{}")) if err != nil { return nil, fmt.Errorf("failed to create validation request: %w", err) } validateReq.Header.Set("Content-Type", "application/json") validateReq.Header.Set("DOLAPIKEY", s.config.Dolibarr.APIToken) validateResp, err := s.client.Do(validateReq) if err != nil { log.Printf("Warning: failed to validate invoice: %v", err) } else { validateResp.Body.Close() } log.Printf("Created invoice in Dolibarr: %d for customer: %d", createdInvoice.ID, customerID) return &createdInvoice, nil } func (s *DolibarrService) GetCustomerInvoices(dolibarrCustomerID int) ([]DolibarrInvoice, error) { req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/index.php/invoices?socid=%d", s.config.Dolibarr.URL, dolibarrCustomerID), nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("DOLAPIKEY", s.config.Dolibarr.APIToken) resp, err := s.client.Do(req) if err != nil { return nil, fmt.Errorf("failed to make request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("Dolibarr API error: %d - %s", resp.StatusCode, string(body)) } var invoices []DolibarrInvoice if err := json.NewDecoder(resp.Body).Decode(&invoices); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return invoices, nil } func (s *DolibarrService) CreateOrUpdateProduct(productCode, label, description string, price float64) error { // First, try to find existing product req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/index.php/products?ref=%s", s.config.Dolibarr.URL, productCode), nil) if err != nil { return fmt.Errorf("failed to create search request: %w", err) } req.Header.Set("DOLAPIKEY", s.config.Dolibarr.APIToken) resp, err := s.client.Do(req) if err != nil { return fmt.Errorf("failed to search for product: %w", err) } resp.Body.Close() if resp.StatusCode == http.StatusOK { // Product exists, update it log.Printf("Product %s already exists in Dolibarr", productCode) return nil } // Create new product product := map[string]interface{}{ "ref": productCode, "label": label, "description": description, "price": price, "type": 1, // Service "status": 1, // On sale "tosell": 1, // Can be sold } jsonData, err := json.Marshal(product) if err != nil { return fmt.Errorf("failed to marshal product data: %w", err) } req, err = http.NewRequest("POST", s.config.Dolibarr.URL+"/api/index.php/products", strings.NewReader(string(jsonData))) if err != nil { return fmt.Errorf("failed to create product request: %w", err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("DOLAPIKEY", s.config.Dolibarr.APIToken) resp, err = s.client.Do(req) if err != nil { return fmt.Errorf("failed to create product: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("Dolibarr API error: %d - %s", resp.StatusCode, string(body)) } log.Printf("Created product in Dolibarr: %s", productCode) return nil }