e5623d2f84
- Add Infrastructure Health section with CPU/Memory/Disk panels - Add Service Status section with PM2/Docker panels - Create GrafanaPanel component for iframe embedding - Build passes successfully
263 lines
7.7 KiB
Go
263 lines
7.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"boc/legal"
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
// LegalHandler hanterar legal/contract endpoints
|
|
type LegalHandler struct {
|
|
DB *sql.DB
|
|
}
|
|
|
|
func NewLegalHandler(db *sql.DB) *LegalHandler {
|
|
return &LegalHandler{DB: db}
|
|
}
|
|
|
|
// Contract representerar ett avtal i systemet
|
|
type Contract struct {
|
|
ID string `json:"id"`
|
|
TemplateType string `json:"template_type"`
|
|
Name string `json:"name"`
|
|
Counterparty string `json:"counterparty"`
|
|
CounterpartyOrg string `json:"counterparty_org,omitempty"`
|
|
Status string `json:"status"` // draft, pending, active, expired, terminated
|
|
Value float64 `json:"value,omitempty"`
|
|
Currency string `json:"currency,omitempty"`
|
|
StartDate string `json:"start_date,omitempty"`
|
|
EndDate string `json:"end_date,omitempty"`
|
|
RenewalDate string `json:"renewal_date,omitempty"`
|
|
Responsible string `json:"responsible,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
|
|
// ListContracts returnerar alla avtal
|
|
func (h *LegalHandler) ListContracts(w http.ResponseWriter, r *http.Request) {
|
|
rows, err := h.DB.Query(`
|
|
SELECT id, template_type, name, counterparty, counterparty_org, status,
|
|
value, currency, start_date, end_date, renewal_date, responsible, created_at, updated_at
|
|
FROM boc_contracts
|
|
ORDER BY created_at DESC
|
|
`)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
contracts := []Contract{}
|
|
for rows.Next() {
|
|
var c Contract
|
|
var value sql.NullFloat64
|
|
var currency, startDate, endDate, renewalDate, responsible sql.NullString
|
|
rows.Scan(&c.ID, &c.TemplateType, &c.Name, &c.Counterparty, &c.CounterpartyOrg,
|
|
&c.Status, &value, ¤cy, &startDate, &endDate, &renewalDate, &responsible,
|
|
&c.CreatedAt, &c.UpdatedAt)
|
|
if value.Valid {
|
|
c.Value = value.Float64
|
|
}
|
|
if currency.Valid {
|
|
c.Currency = currency.String
|
|
}
|
|
if startDate.Valid {
|
|
c.StartDate = startDate.String
|
|
}
|
|
if endDate.Valid {
|
|
c.EndDate = endDate.String
|
|
}
|
|
if renewalDate.Valid {
|
|
c.RenewalDate = renewalDate.String
|
|
}
|
|
if responsible.Valid {
|
|
c.Responsible = responsible.String
|
|
}
|
|
contracts = append(contracts, c)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"contracts": contracts,
|
|
"total": len(contracts),
|
|
})
|
|
}
|
|
|
|
// GetContract returnerar ett specifikt avtal
|
|
func (h *LegalHandler) GetContract(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
|
|
var c Contract
|
|
var value sql.NullFloat64
|
|
var currency, startDate, endDate, renewalDate, responsible sql.NullString
|
|
|
|
err := h.DB.QueryRow(`
|
|
SELECT id, template_type, name, counterparty, counterparty_org, status,
|
|
value, currency, start_date, end_date, renewal_date, responsible, created_at, updated_at
|
|
FROM boc_contracts WHERE id = $1
|
|
`, id).Scan(&c.ID, &c.TemplateType, &c.Name, &c.Counterparty, &c.CounterpartyOrg,
|
|
&c.Status, &value, ¤cy, &startDate, &endDate, &renewalDate, &responsible,
|
|
&c.CreatedAt, &c.UpdatedAt)
|
|
|
|
if err == sql.ErrNoRows {
|
|
http.Error(w, `{"error":"contract not found"}`, http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil {
|
|
http.Error(w, `{"error":"database error"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if value.Valid {
|
|
c.Value = value.Float64
|
|
}
|
|
if currency.Valid {
|
|
c.Currency = currency.String
|
|
}
|
|
if startDate.Valid {
|
|
c.StartDate = startDate.String
|
|
}
|
|
if endDate.Valid {
|
|
c.EndDate = endDate.String
|
|
}
|
|
if renewalDate.Valid {
|
|
c.RenewalDate = renewalDate.String
|
|
}
|
|
if responsible.Valid {
|
|
c.Responsible = responsible.String
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(c)
|
|
}
|
|
|
|
// GetContractTemplates returnerar alla standardavtal
|
|
func (h *LegalHandler) GetContractTemplates(w http.ResponseWriter, r *http.Request) {
|
|
templates := legal.GetStandardTemplates()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"templates": templates,
|
|
"total": len(templates),
|
|
})
|
|
}
|
|
|
|
// GetContractTemplate returnerar ett specifikt template
|
|
func (h *LegalHandler) GetContractTemplate(w http.ResponseWriter, r *http.Request) {
|
|
templateType := chi.URLParam(r, "type")
|
|
|
|
templates := legal.GetStandardTemplates()
|
|
for _, t := range templates {
|
|
if string(t.Type) == templateType {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(t)
|
|
return
|
|
}
|
|
}
|
|
|
|
http.Error(w, `{"error":"template not found"}`, http.StatusNotFound)
|
|
}
|
|
|
|
// GetProductContractLinks returnerar produkt-avtal kopplingar
|
|
func (h *LegalHandler) GetProductContractLinks(w http.ResponseWriter, r *http.Request) {
|
|
links := legal.GetProductContractLinks()
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"links": links,
|
|
"total": len(links),
|
|
})
|
|
}
|
|
|
|
// CreateContract skapar ett nytt avtal från template
|
|
func (h *LegalHandler) CreateContract(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
TemplateType string `json:"template_type"`
|
|
Counterparty string `json:"counterparty"`
|
|
CounterpartyOrg string `json:"counterparty_org,omitempty"`
|
|
Variables map[string]string `json:"variables,omitempty"`
|
|
Terms legal.ContractTerms `json:"terms,omitempty"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Hitta template
|
|
var template *legal.ContractTemplate
|
|
for _, t := range legal.GetStandardTemplates() {
|
|
if string(t.Type) == req.TemplateType {
|
|
template = &t
|
|
break
|
|
}
|
|
}
|
|
|
|
if template == nil {
|
|
http.Error(w, `{"error":"template not found"}`, http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
// Skapa avtal i databas
|
|
var id string
|
|
err := h.DB.QueryRow(`
|
|
INSERT INTO boc_contracts (template_type, name, counterparty, counterparty_org, status, currency)
|
|
VALUES ($1, $2, $3, $4, 'draft', $5)
|
|
RETURNING id
|
|
`, req.TemplateType, template.Name, req.Counterparty, req.CounterpartyOrg, template.DefaultTerms.Currency).Scan(&id)
|
|
|
|
if err != nil {
|
|
http.Error(w, `{"error":"failed to create contract"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"id": id,
|
|
"message": "Contract created",
|
|
"template": template,
|
|
})
|
|
}
|
|
|
|
// UpdateContract uppdaterar ett avtal
|
|
func (h *LegalHandler) UpdateContract(w http.ResponseWriter, r *http.Request) {
|
|
id := chi.URLParam(r, "id")
|
|
|
|
var req struct {
|
|
Status string `json:"status,omitempty"`
|
|
Value float64 `json:"value,omitempty"`
|
|
StartDate string `json:"start_date,omitempty"`
|
|
EndDate string `json:"end_date,omitempty"`
|
|
Responsible string `json:"responsible,omitempty"`
|
|
}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
_, err := h.DB.Exec(`
|
|
UPDATE boc_contracts
|
|
SET status = COALESCE(NULLIF($1, ''), status),
|
|
value = COALESCE($2, value),
|
|
start_date = COALESCE(NULLIF($3, ''), start_date),
|
|
end_date = COALESCE(NULLIF($4, ''), end_date),
|
|
responsible = COALESCE(NULLIF($5, ''), responsible),
|
|
updated_at = NOW()
|
|
WHERE id = $6
|
|
`, req.Status, req.Value, req.StartDate, req.EndDate, req.Responsible, id)
|
|
|
|
if err != nil {
|
|
http.Error(w, `{"error":"failed to update contract"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"message": "Contract updated",
|
|
})
|
|
}
|