Compare commits
3 Commits
5530cfcdfd
...
c51da2a965
| Author | SHA1 | Date | |
|---|---|---|---|
| c51da2a965 | |||
| 3cfa8d7618 | |||
| 88ddf4c043 |
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# Project specific binaries
|
||||
erp_system
|
||||
|
||||
# SQLite
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# OS Specific
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Editor/IDE specific
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*~
|
||||
97
AGENTS.md
Normal file
97
AGENTS.md
Normal file
@ -0,0 +1,97 @@
|
||||
# ERP System Agent Guidelines
|
||||
|
||||
This document provides instructions for coding agents working on the ERP System repository.
|
||||
|
||||
## 1. Build, Lint, and Test
|
||||
|
||||
### Build
|
||||
To build the application:
|
||||
```bash
|
||||
go build -o erp_system main.go
|
||||
```
|
||||
To run the application locally:
|
||||
```bash
|
||||
go run main.go
|
||||
```
|
||||
The server defaults to port `1337`.
|
||||
|
||||
### Test
|
||||
Run all tests:
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
Run a specific test package:
|
||||
```bash
|
||||
go test ./internal/handlers/...
|
||||
```
|
||||
Run a single specific test function:
|
||||
```bash
|
||||
# Pattern: go test -v <package_path> -run <TestNameRegex>
|
||||
go test -v ./internal/handlers -run TestLoginSubmit
|
||||
```
|
||||
|
||||
### Lint / Verify
|
||||
Run standard Go vetting:
|
||||
```bash
|
||||
go vet ./...
|
||||
```
|
||||
Format code (always run before committing):
|
||||
```bash
|
||||
go fmt ./...
|
||||
```
|
||||
|
||||
## 2. Code Style & Conventions
|
||||
|
||||
### General
|
||||
- **Language:** Go 1.25.6+
|
||||
- **Formatting:** Strictly adhere to `gofmt` standards.
|
||||
- **Naming:**
|
||||
- Use `PascalCase` for exported structs, fields, and functions.
|
||||
- Use `camelCase` for unexported (private) identifiers.
|
||||
- Keep variable names short but descriptive (e.g., `req` vs `r` is fine if context is clear, but `userRequest` is better for complex scopes).
|
||||
|
||||
### Project Structure
|
||||
- `main.go`: Application entry point, router setup, dependency injection.
|
||||
- `internal/`: Private application code.
|
||||
- `database/`: DB initialization and helpers.
|
||||
- `handlers/`: HTTP handlers.
|
||||
- `models/`: Data structs and business logic/DB queries.
|
||||
- `middleware/`: HTTP middleware (Auth, etc.).
|
||||
- `templates/`: HTML templates (server-side rendered).
|
||||
|
||||
### Imports
|
||||
Group imports into three blocks separated by a newline:
|
||||
1. Standard library (e.g., `"net/http"`, `"os"`)
|
||||
2. Third-party packages (e.g., `"github.com/gorilla/sessions"`)
|
||||
3. Internal project packages (e.g., `"erp_system/internal/models"`)
|
||||
|
||||
Example:
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
|
||||
"erp_system/internal/models"
|
||||
)
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
- **Explicit Checks:** Always check errors. `if err != nil { ... }`.
|
||||
- **Handlers:** Use `http.Error(w, err.Error(), http.Status...)` to return errors to the client.
|
||||
- **Logging:** Log critical errors on the server side using `log.Printf` or similar before returning HTTP 500.
|
||||
|
||||
### HTTP & Routing
|
||||
- Use Go's standard `net/http` `ServeMux` with Go 1.22+ routing patterns (e.g., `mux.Handle("GET /path/{id}", ...)`).
|
||||
- **HTMX:** The application uses HTMX.
|
||||
- Check for `HX-Request` header to differentiate full page loads vs partials.
|
||||
- Use `HX-Redirect` header for client-side redirection when handling HTMX requests.
|
||||
- Render partial templates when appropriate.
|
||||
|
||||
### Database
|
||||
- Use `internal/models` for all database interactions.
|
||||
- Pass the database connection (`*sql.DB`) or a wrapping struct to handlers via dependency injection (see `Handler` struct in `main.go`).
|
||||
|
||||
## 3. Cursor / Copilot Rules
|
||||
(No specific Cursor or Copilot rules found in the repository yet. Adhere to the conventions above.)
|
||||
BIN
erp.db-shm
BIN
erp.db-shm
Binary file not shown.
BIN
erp.db-wal
BIN
erp.db-wal
Binary file not shown.
56
internal/handlers/ledger_test.go
Normal file
56
internal/handlers/ledger_test.go
Normal file
@ -0,0 +1,56 @@
|
||||
package handlers_test
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"erp_system/internal/database"
|
||||
"erp_system/internal/handlers"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
func TestJournalEntries_List(t *testing.T) {
|
||||
// Setup temporary DB
|
||||
dbFile := "test_ledger.db"
|
||||
db, err := database.Initialize(dbFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize DB: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
db.Close()
|
||||
os.Remove(dbFile)
|
||||
}()
|
||||
|
||||
// Setup Handler
|
||||
store := sessions.NewCookieStore([]byte("secret"))
|
||||
h := &handlers.Handler{
|
||||
DB: db,
|
||||
Store: store,
|
||||
}
|
||||
|
||||
// Insert a dummy entry so the loop runs
|
||||
db.Exec("INSERT INTO journal_entries (description) VALUES ('Test')")
|
||||
|
||||
// Create a timeout channel to detect freeze
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
req := httptest.NewRequest("GET", "/ledger/journal", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Execute Handler
|
||||
h.JournalEntries(w, req)
|
||||
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
t.Log("Handler completed successfully")
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Handler timed out - DEADLOCK DETECTED")
|
||||
}
|
||||
}
|
||||
@ -73,7 +73,20 @@ func GLAccountGetByID(db *sql.DB, id int) (*GLAccount, error) {
|
||||
}
|
||||
|
||||
func JournalEntryGetAll(db *sql.DB) ([]JournalEntry, error) {
|
||||
rows, err := db.Query("SELECT id, entry_date, description, reference, created_at FROM journal_entries ORDER BY created_at DESC")
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
je.id,
|
||||
je.entry_date,
|
||||
je.description,
|
||||
je.reference,
|
||||
je.created_at,
|
||||
COALESCE(SUM(jl.debit), 0),
|
||||
COALESCE(SUM(jl.credit), 0)
|
||||
FROM journal_entries je
|
||||
LEFT JOIN journal_lines jl ON je.id = jl.journal_entry_id
|
||||
GROUP BY je.id
|
||||
ORDER BY je.created_at DESC
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -82,14 +95,9 @@ func JournalEntryGetAll(db *sql.DB) ([]JournalEntry, error) {
|
||||
var entries []JournalEntry
|
||||
for rows.Next() {
|
||||
var je JournalEntry
|
||||
if err := rows.Scan(&je.ID, &je.EntryDate, &je.Description, &je.Reference, &je.CreatedAt); err != nil {
|
||||
if err := rows.Scan(&je.ID, &je.EntryDate, &je.Description, &je.Reference, &je.CreatedAt, &je.TotalDebit, &je.TotalCredit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get totals
|
||||
db.QueryRow("SELECT COALESCE(SUM(debit), 0), COALESCE(SUM(credit), 0) FROM journal_lines WHERE journal_entry_id = ?", je.ID).
|
||||
Scan(&je.TotalDebit, &je.TotalCredit)
|
||||
|
||||
entries = append(entries, je)
|
||||
}
|
||||
return entries, nil
|
||||
|
||||
2
main.go
2
main.go
@ -92,7 +92,7 @@ func main() {
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
port = "1337"
|
||||
}
|
||||
|
||||
log.Printf("ERP System starting on http://localhost:%s", port)
|
||||
|
||||
@ -14,9 +14,9 @@
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<form class="space-y-6 p-6"
|
||||
{{if .IsNew}}
|
||||
method="POST" action="/customers" hx-post="/customers"
|
||||
method="POST" action="/customers" hx-post="/customers" hx-target="body"
|
||||
{{else}}
|
||||
method="POST" action="/customers/{{.Customer.ID}}" hx-put="/customers/{{.Customer.ID}}"
|
||||
method="POST" action="/customers/{{.Customer.ID}}" hx-put="/customers/{{.Customer.ID}}" hx-target="body"
|
||||
{{end}}>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
{{end}}
|
||||
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<form class="space-y-6 p-6" method="POST" action="/ledger/journal" hx-post="/ledger/journal" id="je-form">
|
||||
<form class="space-y-6 p-6" method="POST" action="/ledger/journal" id="je-form">
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-3">
|
||||
<div>
|
||||
<label for="entry_date" class="block text-sm font-medium text-gray-700">Date *</label>
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<form class="mt-8 space-y-6" method="POST" action="/login" hx-post="/login">
|
||||
<form class="mt-8 space-y-6" method="POST" action="/login" hx-post="/login" hx-target="body">
|
||||
<div class="space-y-4 rounded-md shadow-sm">
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-gray-700">Username</label>
|
||||
|
||||
@ -14,9 +14,9 @@
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<form class="space-y-6 p-6"
|
||||
{{if .IsNew}}
|
||||
method="POST" action="/orders" hx-post="/orders"
|
||||
method="POST" action="/orders" hx-post="/orders" hx-target="body"
|
||||
{{else}}
|
||||
method="POST" action="/orders/{{.Order.ID}}" hx-put="/orders/{{.Order.ID}}"
|
||||
method="POST" action="/orders/{{.Order.ID}}" hx-put="/orders/{{.Order.ID}}" hx-target="body"
|
||||
{{end}}>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user