- Add Go modules with required dependencies (Gin, UUID, JWT, etc.) - Implement main web server with landing page endpoint - Add comprehensive API endpoints for health and status - Include proper error handling and request validation - Set up CORS middleware and security headers
263 lines
7.7 KiB
Go
263 lines
7.7 KiB
Go
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
|
|
} |