225 lines
6.4 KiB
Go
225 lines
6.4 KiB
Go
package models
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type Order struct {
|
|
ID int
|
|
CustomerID int
|
|
CustomerName string // joined field
|
|
Status string
|
|
OrderDate string
|
|
TotalAmount float64
|
|
Notes string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
Lines []OrderLine
|
|
}
|
|
|
|
type OrderLine struct {
|
|
ID int
|
|
OrderID int
|
|
Description string
|
|
Quantity float64
|
|
UnitPrice float64
|
|
LineTotal float64
|
|
}
|
|
|
|
func OrderGetAll(db *sql.DB, status string) ([]Order, error) {
|
|
query := `SELECT o.id, o.customer_id, c.name, o.status, o.order_date, o.total_amount, o.notes, o.created_at, o.updated_at
|
|
FROM orders o JOIN customers c ON o.customer_id = c.id`
|
|
args := []interface{}{}
|
|
if status != "" {
|
|
query += " WHERE o.status = ?"
|
|
args = append(args, status)
|
|
}
|
|
query += " ORDER BY o.created_at DESC"
|
|
|
|
rows, err := db.Query(query, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var orders []Order
|
|
for rows.Next() {
|
|
var o Order
|
|
if err := rows.Scan(&o.ID, &o.CustomerID, &o.CustomerName, &o.Status, &o.OrderDate, &o.TotalAmount, &o.Notes, &o.CreatedAt, &o.UpdatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
orders = append(orders, o)
|
|
}
|
|
return orders, nil
|
|
}
|
|
|
|
func OrderGetByID(db *sql.DB, id int) (*Order, error) {
|
|
o := &Order{}
|
|
err := db.QueryRow(
|
|
`SELECT o.id, o.customer_id, c.name, o.status, o.order_date, o.total_amount, o.notes, o.created_at, o.updated_at
|
|
FROM orders o JOIN customers c ON o.customer_id = c.id WHERE o.id = ?`, id,
|
|
).Scan(&o.ID, &o.CustomerID, &o.CustomerName, &o.Status, &o.OrderDate, &o.TotalAmount, &o.Notes, &o.CreatedAt, &o.UpdatedAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load lines
|
|
lines, err := OrderLinesGetByOrderID(db, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
o.Lines = lines
|
|
return o, nil
|
|
}
|
|
|
|
func OrderInsert(db *sql.DB, o *Order) error {
|
|
result, err := db.Exec(
|
|
"INSERT INTO orders (customer_id, status, order_date, total_amount, notes) VALUES (?, ?, ?, ?, ?)",
|
|
o.CustomerID, o.Status, o.OrderDate, o.TotalAmount, o.Notes,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, _ := result.LastInsertId()
|
|
o.ID = int(id)
|
|
return nil
|
|
}
|
|
|
|
func OrderUpdate(db *sql.DB, o *Order) error {
|
|
_, err := db.Exec(
|
|
"UPDATE orders SET customer_id = ?, order_date = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
|
o.CustomerID, o.OrderDate, o.Notes, o.ID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func OrderUpdateStatus(db *sql.DB, id int, status string) error {
|
|
_, err := db.Exec("UPDATE orders SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", status, id)
|
|
return err
|
|
}
|
|
|
|
func OrderRecalcTotal(db *sql.DB, orderID int) error {
|
|
_, err := db.Exec(
|
|
"UPDATE orders SET total_amount = (SELECT COALESCE(SUM(line_total), 0) FROM order_lines WHERE order_id = ?), updated_at = CURRENT_TIMESTAMP WHERE id = ?",
|
|
orderID, orderID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func OrderDelete(db *sql.DB, id int) error {
|
|
_, err := db.Exec("DELETE FROM orders WHERE id = ?", id)
|
|
return err
|
|
}
|
|
|
|
func OrderCount(db *sql.DB) int {
|
|
var count int
|
|
db.QueryRow("SELECT COUNT(*) FROM orders WHERE status NOT IN ('cancelled')").Scan(&count)
|
|
return count
|
|
}
|
|
|
|
func OrderCountByStatus(db *sql.DB, status string) int {
|
|
var count int
|
|
db.QueryRow("SELECT COUNT(*) FROM orders WHERE status = ?", status).Scan(&count)
|
|
return count
|
|
}
|
|
|
|
// Order Lines
|
|
|
|
func OrderLinesGetByOrderID(db *sql.DB, orderID int) ([]OrderLine, error) {
|
|
rows, err := db.Query("SELECT id, order_id, description, quantity, unit_price, line_total FROM order_lines WHERE order_id = ? ORDER BY id", orderID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var lines []OrderLine
|
|
for rows.Next() {
|
|
var l OrderLine
|
|
if err := rows.Scan(&l.ID, &l.OrderID, &l.Description, &l.Quantity, &l.UnitPrice, &l.LineTotal); err != nil {
|
|
return nil, err
|
|
}
|
|
lines = append(lines, l)
|
|
}
|
|
return lines, nil
|
|
}
|
|
|
|
func OrderLineInsert(db *sql.DB, l *OrderLine) error {
|
|
l.LineTotal = l.Quantity * l.UnitPrice
|
|
result, err := db.Exec(
|
|
"INSERT INTO order_lines (order_id, description, quantity, unit_price, line_total) VALUES (?, ?, ?, ?, ?)",
|
|
l.OrderID, l.Description, l.Quantity, l.UnitPrice, l.LineTotal,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
id, _ := result.LastInsertId()
|
|
l.ID = int(id)
|
|
return OrderRecalcTotal(db, l.OrderID)
|
|
}
|
|
|
|
func OrderLineDelete(db *sql.DB, lineID, orderID int) error {
|
|
_, err := db.Exec("DELETE FROM order_lines WHERE id = ?", lineID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return OrderRecalcTotal(db, orderID)
|
|
}
|
|
|
|
// GenerateInvoiceFromOrder creates an invoice and GL entries when an order is fulfilled
|
|
func GenerateInvoiceFromOrder(db *sql.DB, orderID int) error {
|
|
order, err := OrderGetByID(db, orderID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Generate invoice number
|
|
var maxID int
|
|
db.QueryRow("SELECT COALESCE(MAX(id), 0) FROM invoices").Scan(&maxID)
|
|
invoiceNumber := fmt.Sprintf("INV-%05d", maxID+1)
|
|
|
|
// Create invoice (due in 30 days)
|
|
_, err = db.Exec(
|
|
`INSERT INTO invoices (order_id, customer_id, invoice_number, status, amount, due_date)
|
|
VALUES (?, ?, ?, 'pending', ?, date('now', '+30 days'))`,
|
|
order.ID, order.CustomerID, invoiceNumber, order.TotalAmount,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("creating invoice: %w", err)
|
|
}
|
|
|
|
// Create GL journal entry: Debit AR, Credit Revenue
|
|
result, err := db.Exec(
|
|
"INSERT INTO journal_entries (entry_date, description, reference) VALUES (date('now'), ?, ?)",
|
|
fmt.Sprintf("Invoice %s - Order #%d fulfilled", invoiceNumber, orderID),
|
|
invoiceNumber,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("creating journal entry: %w", err)
|
|
}
|
|
jeID, _ := result.LastInsertId()
|
|
|
|
// Debit Accounts Receivable (1100)
|
|
var arID int
|
|
db.QueryRow("SELECT id FROM gl_accounts WHERE code = '1100'").Scan(&arID)
|
|
_, err = db.Exec("INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit) VALUES (?, ?, ?, 0)", jeID, arID, order.TotalAmount)
|
|
if err != nil {
|
|
return fmt.Errorf("creating AR debit line: %w", err)
|
|
}
|
|
|
|
// Credit Sales Revenue (4000)
|
|
var revID int
|
|
db.QueryRow("SELECT id FROM gl_accounts WHERE code = '4000'").Scan(&revID)
|
|
_, err = db.Exec("INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit) VALUES (?, ?, 0, ?)", jeID, revID, order.TotalAmount)
|
|
if err != nil {
|
|
return fmt.Errorf("creating revenue credit line: %w", err)
|
|
}
|
|
|
|
// Update account balances
|
|
db.Exec("UPDATE gl_accounts SET balance = balance + ? WHERE code = '1100'", order.TotalAmount) // AR increases (debit)
|
|
db.Exec("UPDATE gl_accounts SET balance = balance + ? WHERE code = '4000'", order.TotalAmount) // Revenue increases (credit)
|
|
|
|
return nil
|
|
}
|