133 lines
4.0 KiB
Go
133 lines
4.0 KiB
Go
|
|
package models
|
||
|
|
|
||
|
|
import (
|
||
|
|
"database/sql"
|
||
|
|
"fmt"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Invoice struct {
|
||
|
|
ID int
|
||
|
|
OrderID sql.NullInt64
|
||
|
|
CustomerID int
|
||
|
|
CustomerName string
|
||
|
|
InvoiceNumber string
|
||
|
|
Status string
|
||
|
|
Amount float64
|
||
|
|
DueDate string
|
||
|
|
PaidDate sql.NullString
|
||
|
|
CreatedAt time.Time
|
||
|
|
}
|
||
|
|
|
||
|
|
func InvoiceGetAll(db *sql.DB, status string) ([]Invoice, error) {
|
||
|
|
query := `SELECT i.id, i.order_id, i.customer_id, c.name, i.invoice_number, i.status, i.amount, i.due_date, i.paid_date, i.created_at
|
||
|
|
FROM invoices i JOIN customers c ON i.customer_id = c.id`
|
||
|
|
args := []interface{}{}
|
||
|
|
if status != "" {
|
||
|
|
query += " WHERE i.status = ?"
|
||
|
|
args = append(args, status)
|
||
|
|
}
|
||
|
|
query += " ORDER BY i.created_at DESC"
|
||
|
|
|
||
|
|
rows, err := db.Query(query, args...)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
var invoices []Invoice
|
||
|
|
for rows.Next() {
|
||
|
|
var inv Invoice
|
||
|
|
if err := rows.Scan(&inv.ID, &inv.OrderID, &inv.CustomerID, &inv.CustomerName, &inv.InvoiceNumber, &inv.Status, &inv.Amount, &inv.DueDate, &inv.PaidDate, &inv.CreatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
invoices = append(invoices, inv)
|
||
|
|
}
|
||
|
|
return invoices, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func InvoiceGetByID(db *sql.DB, id int) (*Invoice, error) {
|
||
|
|
inv := &Invoice{}
|
||
|
|
err := db.QueryRow(
|
||
|
|
`SELECT i.id, i.order_id, i.customer_id, c.name, i.invoice_number, i.status, i.amount, i.due_date, i.paid_date, i.created_at
|
||
|
|
FROM invoices i JOIN customers c ON i.customer_id = c.id WHERE i.id = ?`, id,
|
||
|
|
).Scan(&inv.ID, &inv.OrderID, &inv.CustomerID, &inv.CustomerName, &inv.InvoiceNumber, &inv.Status, &inv.Amount, &inv.DueDate, &inv.PaidDate, &inv.CreatedAt)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return inv, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func InvoiceMarkPaid(db *sql.DB, id int) error {
|
||
|
|
inv, err := InvoiceGetByID(db, id)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update invoice status
|
||
|
|
_, err = db.Exec("UPDATE invoices SET status = 'paid', paid_date = date('now') WHERE id = ?", id)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create GL journal entry: Debit Cash, Credit AR
|
||
|
|
result, err := db.Exec(
|
||
|
|
"INSERT INTO journal_entries (entry_date, description, reference) VALUES (date('now'), ?, ?)",
|
||
|
|
fmt.Sprintf("Payment received for %s", inv.InvoiceNumber),
|
||
|
|
inv.InvoiceNumber,
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
jeID, _ := result.LastInsertId()
|
||
|
|
|
||
|
|
// Debit Cash (1000)
|
||
|
|
var cashID int
|
||
|
|
db.QueryRow("SELECT id FROM gl_accounts WHERE code = '1000'").Scan(&cashID)
|
||
|
|
db.Exec("INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit) VALUES (?, ?, ?, 0)", jeID, cashID, inv.Amount)
|
||
|
|
|
||
|
|
// Credit AR (1100)
|
||
|
|
var arID int
|
||
|
|
db.QueryRow("SELECT id FROM gl_accounts WHERE code = '1100'").Scan(&arID)
|
||
|
|
db.Exec("INSERT INTO journal_lines (journal_entry_id, account_id, debit, credit) VALUES (?, ?, 0, ?)", jeID, arID, inv.Amount)
|
||
|
|
|
||
|
|
// Update account balances
|
||
|
|
db.Exec("UPDATE gl_accounts SET balance = balance + ? WHERE code = '1000'", inv.Amount) // Cash increases
|
||
|
|
db.Exec("UPDATE gl_accounts SET balance = balance - ? WHERE code = '1100'", inv.Amount) // AR decreases
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func InvoiceCountByStatus(db *sql.DB, status string) int {
|
||
|
|
var count int
|
||
|
|
db.QueryRow("SELECT COUNT(*) FROM invoices WHERE status = ?", status).Scan(&count)
|
||
|
|
return count
|
||
|
|
}
|
||
|
|
|
||
|
|
func InvoiceTotalOutstanding(db *sql.DB) float64 {
|
||
|
|
var total float64
|
||
|
|
db.QueryRow("SELECT COALESCE(SUM(amount), 0) FROM invoices WHERE status = 'pending'").Scan(&total)
|
||
|
|
return total
|
||
|
|
}
|
||
|
|
|
||
|
|
func InvoiceGetByCustomerID(db *sql.DB, customerID int) ([]Invoice, error) {
|
||
|
|
rows, err := db.Query(
|
||
|
|
`SELECT i.id, i.order_id, i.customer_id, c.name, i.invoice_number, i.status, i.amount, i.due_date, i.paid_date, i.created_at
|
||
|
|
FROM invoices i JOIN customers c ON i.customer_id = c.id WHERE i.customer_id = ? ORDER BY i.created_at DESC`, customerID,
|
||
|
|
)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer rows.Close()
|
||
|
|
|
||
|
|
var invoices []Invoice
|
||
|
|
for rows.Next() {
|
||
|
|
var inv Invoice
|
||
|
|
if err := rows.Scan(&inv.ID, &inv.OrderID, &inv.CustomerID, &inv.CustomerName, &inv.InvoiceNumber, &inv.Status, &inv.Amount, &inv.DueDate, &inv.PaidDate, &inv.CreatedAt); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
invoices = append(invoices, inv)
|
||
|
|
}
|
||
|
|
return invoices, nil
|
||
|
|
}
|