287 lines
9.2 KiB
Go
287 lines
9.2 KiB
Go
|
|
package handlers
|
||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type SupplierHandler struct {
|
||
|
|
DB *sql.DB
|
||
|
|
}
|
||
|
|
|
||
|
|
func NewSupplierHandler(db *sql.DB) *SupplierHandler {
|
||
|
|
return &SupplierHandler{DB: db}
|
||
|
|
}
|
||
|
|
|
||
|
|
type Supplier struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Email string `json:"email"`
|
||
|
|
Phone string `json:"phone"`
|
||
|
|
OrgNumber string `json:"org_number"`
|
||
|
|
Address map[string]interface{} `json:"address"`
|
||
|
|
PaymentTerms string `json:"payment_terms"`
|
||
|
|
BankAccount string `json:"bank_account"`
|
||
|
|
Bankgiro string `json:"bankgiro"`
|
||
|
|
Postgiro string `json:"postgiro"`
|
||
|
|
Currency string `json:"currency"`
|
||
|
|
Status string `json:"status"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type PurchaseOrder struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
SupplierID string `json:"supplier_id"`
|
||
|
|
PONumber string `json:"po_number"`
|
||
|
|
Status string `json:"status"`
|
||
|
|
Amount float64 `json:"amount"`
|
||
|
|
TaxAmount float64 `json:"tax_amount"`
|
||
|
|
Currency string `json:"currency"`
|
||
|
|
ExpectedDelivery *time.Time `json:"expected_delivery"`
|
||
|
|
ReceivedAt *time.Time `json:"received_at"`
|
||
|
|
Notes string `json:"notes"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type SupplierInvoice struct {
|
||
|
|
ID string `json:"id"`
|
||
|
|
SupplierID string `json:"supplier_id"`
|
||
|
|
POID *string `json:"po_id"`
|
||
|
|
InvoiceNumber string `json:"invoice_number"`
|
||
|
|
Amount float64 `json:"amount"`
|
||
|
|
TaxAmount float64 `json:"tax_amount"`
|
||
|
|
Currency string `json:"currency"`
|
||
|
|
Status string `json:"status"`
|
||
|
|
DueDate *time.Time `json:"due_date"`
|
||
|
|
PaidAt *time.Time `json:"paid_at"`
|
||
|
|
OCRNumber string `json:"ocr_number"`
|
||
|
|
Notes string `json:"notes"`
|
||
|
|
CreatedAt time.Time `json:"created_at"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *SupplierHandler) ListSuppliers(w http.ResponseWriter, r *http.Request) {
|
||
|
|
rows, err := h.DB.Query(`
|
||
|
|
SELECT id, name, email, phone, org_number, address, payment_terms, bank_account, bankgiro, postgiro, currency, status, created_at
|
||
|
|
FROM boc_suppliers WHERE status = 'active' ORDER BY name
|
||
|
|
`)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
suppliers := []Supplier{}
|
||
|
|
for rows.Next() {
|
||
|
|
var s Supplier
|
||
|
|
var addr []byte
|
||
|
|
if err := rows.Scan(&s.ID, &s.Name, &s.Email, &s.Phone, &s.OrgNumber, &addr, &s.PaymentTerms, &s.BankAccount, &s.Bankgiro, &s.Postgiro, &s.Currency, &s.Status, &s.CreatedAt); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
json.Unmarshal(addr, &s.Address)
|
||
|
|
suppliers = append(suppliers, s)
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"suppliers": suppliers,
|
||
|
|
"total": len(suppliers),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *SupplierHandler) CreateSupplier(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req Supplier
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeError(w, http.StatusBadRequest, "invalid request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
addr, _ := json.Marshal(req.Address)
|
||
|
|
|
||
|
|
var id string
|
||
|
|
err := h.DB.QueryRow(`
|
||
|
|
INSERT INTO boc_suppliers (name, email, phone, org_number, address, payment_terms, bank_account, bankgiro, postgiro, currency)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||
|
|
RETURNING id
|
||
|
|
`, req.Name, req.Email, req.Phone, req.OrgNumber, addr, req.PaymentTerms, req.BankAccount, req.Bankgiro, req.Postgiro, req.Currency).Scan(&id)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to create supplier")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusCreated, map[string]interface{}{
|
||
|
|
"id": id,
|
||
|
|
"message": "Supplier created",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *SupplierHandler) ListPurchaseOrders(w http.ResponseWriter, r *http.Request) {
|
||
|
|
status := r.URL.Query().Get("status")
|
||
|
|
if status == "" {
|
||
|
|
status = "all"
|
||
|
|
}
|
||
|
|
|
||
|
|
var query string
|
||
|
|
var args []interface{}
|
||
|
|
if status == "all" {
|
||
|
|
query = `SELECT id, supplier_id, po_number, status, amount, currency, expected_delivery, received_at, created_at FROM boc_purchase_orders ORDER BY created_at DESC LIMIT 100`
|
||
|
|
} else {
|
||
|
|
query = `SELECT id, supplier_id, po_number, status, amount, currency, expected_delivery, received_at, created_at FROM boc_purchase_orders WHERE status = $1 ORDER BY created_at DESC LIMIT 100`
|
||
|
|
args = append(args, status)
|
||
|
|
}
|
||
|
|
|
||
|
|
rows, err := h.DB.Query(query, args...)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
pos := []PurchaseOrder{}
|
||
|
|
for rows.Next() {
|
||
|
|
var p PurchaseOrder
|
||
|
|
if err := rows.Scan(&p.ID, &p.SupplierID, &p.PONumber, &p.Status, &p.Amount, &p.Currency, &p.ExpectedDelivery, &p.ReceivedAt, &p.CreatedAt); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
pos = append(pos, p)
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"purchase_orders": pos,
|
||
|
|
"total": len(pos),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *SupplierHandler) CreatePurchaseOrder(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req struct {
|
||
|
|
SupplierID string `json:"supplier_id"`
|
||
|
|
ExpectedDelivery *time.Time `json:"expected_delivery"`
|
||
|
|
Notes string `json:"notes"`
|
||
|
|
Items []struct {
|
||
|
|
ProductID string `json:"product_id"`
|
||
|
|
Description string `json:"description"`
|
||
|
|
Quantity float64 `json:"quantity"`
|
||
|
|
UnitPrice float64 `json:"unit_price"`
|
||
|
|
TaxRate float64 `json:"tax_rate"`
|
||
|
|
} `json:"items"`
|
||
|
|
}
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeError(w, http.StatusBadRequest, "invalid request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
poNumber := "PO-" + time.Now().Format("20060102-150405")
|
||
|
|
|
||
|
|
var totalAmount, totalTax float64
|
||
|
|
for _, item := range req.Items {
|
||
|
|
itemTotal := item.Quantity * item.UnitPrice
|
||
|
|
itemTax := itemTotal * (item.TaxRate / 100)
|
||
|
|
totalAmount += itemTotal
|
||
|
|
totalTax += itemTax
|
||
|
|
}
|
||
|
|
|
||
|
|
tx, err := h.DB.Begin()
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "transaction error")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer tx.Rollback()
|
||
|
|
|
||
|
|
var id string
|
||
|
|
err = tx.QueryRow(`
|
||
|
|
INSERT INTO boc_purchase_orders (supplier_id, po_number, amount, tax_amount, currency, expected_delivery, notes)
|
||
|
|
VALUES ($1, $2, $3, $4, 'USD', $5, $6)
|
||
|
|
RETURNING id
|
||
|
|
`, req.SupplierID, poNumber, totalAmount, totalTax, req.ExpectedDelivery, req.Notes).Scan(&id)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to create PO")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, item := range req.Items {
|
||
|
|
itemTotal := item.Quantity * item.UnitPrice
|
||
|
|
_, err = tx.Exec(`
|
||
|
|
INSERT INTO boc_purchase_order_items (po_id, product_id, description, quantity, unit_price, tax_rate, total)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||
|
|
`, id, item.ProductID, item.Description, item.Quantity, item.UnitPrice, item.TaxRate, itemTotal)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to create PO items")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := tx.Commit(); err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "commit failed")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusCreated, map[string]interface{}{
|
||
|
|
"id": id,
|
||
|
|
"number": poNumber,
|
||
|
|
"message": "Purchase order created",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *SupplierHandler) ListSupplierInvoices(w http.ResponseWriter, r *http.Request) {
|
||
|
|
status := r.URL.Query().Get("status")
|
||
|
|
if status == "" {
|
||
|
|
status = "all"
|
||
|
|
}
|
||
|
|
|
||
|
|
var query string
|
||
|
|
var args []interface{}
|
||
|
|
if status == "all" {
|
||
|
|
query = `SELECT id, supplier_id, po_id, invoice_number, amount, currency, status, due_date, paid_at, ocr_number, created_at FROM boc_supplier_invoices ORDER BY created_at DESC LIMIT 100`
|
||
|
|
} else {
|
||
|
|
query = `SELECT id, supplier_id, po_id, invoice_number, amount, currency, status, due_date, paid_at, ocr_number, created_at FROM boc_supplier_invoices WHERE status = $1 ORDER BY created_at DESC LIMIT 100`
|
||
|
|
args = append(args, status)
|
||
|
|
}
|
||
|
|
|
||
|
|
rows, err := h.DB.Query(query, args...)
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "database error")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
invoices := []SupplierInvoice{}
|
||
|
|
for rows.Next() {
|
||
|
|
var i SupplierInvoice
|
||
|
|
if err := rows.Scan(&i.ID, &i.SupplierID, &i.POID, &i.InvoiceNumber, &i.Amount, &i.Currency, &i.Status, &i.DueDate, &i.PaidAt, &i.OCRNumber, &i.CreatedAt); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
invoices = append(invoices, i)
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||
|
|
"invoices": invoices,
|
||
|
|
"total": len(invoices),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func (h *SupplierHandler) CreateSupplierInvoice(w http.ResponseWriter, r *http.Request) {
|
||
|
|
var req SupplierInvoice
|
||
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
|
|
writeError(w, http.StatusBadRequest, "invalid request")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
var id string
|
||
|
|
err := h.DB.QueryRow(`
|
||
|
|
INSERT INTO boc_supplier_invoices (supplier_id, po_id, invoice_number, amount, tax_amount, currency, due_date, ocr_number, notes)
|
||
|
|
VALUES ($1, $2, $3, $4, $5, 'USD', $6, $7, $8)
|
||
|
|
RETURNING id
|
||
|
|
`, req.SupplierID, req.POID, req.InvoiceNumber, req.Amount, req.TaxAmount, req.DueDate, req.OCRNumber, req.Notes).Scan(&id)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
writeError(w, http.StatusInternalServerError, "failed to create supplier invoice")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
writeJSON(w, http.StatusCreated, map[string]interface{}{
|
||
|
|
"id": id,
|
||
|
|
"message": "Supplier invoice created",
|
||
|
|
})
|
||
|
|
}
|