2026-02-06 17:35:29 +01:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"erp_system/internal/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (h *Handler) ChartOfAccounts(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
accounts, err := models.GLAccountGetAll(h.DB)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "Chart of Accounts",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Accounts": accounts,
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/chart_of_accounts.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) JournalEntries(w http.ResponseWriter, r *http.Request) {
|
2026-02-07 07:47:20 +01:00
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
|
|
|
if page < 1 {
|
|
|
|
|
page = 1
|
|
|
|
|
}
|
|
|
|
|
limit := 20
|
|
|
|
|
|
|
|
|
|
entries, total, err := models.JournalEntryGetPaginated(h.DB, page, limit)
|
2026-02-06 17:35:29 +01:00
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 07:47:20 +01:00
|
|
|
totalPages := (total + limit - 1) / limit
|
|
|
|
|
|
2026-02-06 17:35:29 +01:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "Journal Entries",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Entries": entries,
|
2026-02-07 07:47:20 +01:00
|
|
|
"Page": page,
|
|
|
|
|
"TotalPages": totalPages,
|
|
|
|
|
"HasPrev": page > 1,
|
|
|
|
|
"HasNext": page < totalPages,
|
|
|
|
|
"PrevPage": page - 1,
|
|
|
|
|
"NextPage": page + 1,
|
2026-02-06 17:35:29 +01:00
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/journal_entries.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) JournalEntryNew(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
accounts, _ := models.GLAccountGetAll(h.DB)
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "New Journal Entry",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Accounts": accounts,
|
|
|
|
|
"Today": time.Now().Format("2006-01-02"),
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/journal_entry_form.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) JournalEntryCreate(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
r.ParseForm()
|
|
|
|
|
|
|
|
|
|
je := &models.JournalEntry{
|
|
|
|
|
EntryDate: r.FormValue("entry_date"),
|
|
|
|
|
Description: r.FormValue("description"),
|
|
|
|
|
Reference: r.FormValue("reference"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse lines from form
|
|
|
|
|
accountIDs := r.Form["account_id"]
|
|
|
|
|
debits := r.Form["debit"]
|
|
|
|
|
credits := r.Form["credit"]
|
|
|
|
|
|
|
|
|
|
for i := range accountIDs {
|
|
|
|
|
accID, _ := strconv.Atoi(accountIDs[i])
|
|
|
|
|
debit, _ := strconv.ParseFloat(debits[i], 64)
|
|
|
|
|
credit, _ := strconv.ParseFloat(credits[i], 64)
|
|
|
|
|
|
|
|
|
|
if accID == 0 || (debit == 0 && credit == 0) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
je.Lines = append(je.Lines, models.JournalLine{
|
|
|
|
|
AccountID: accID,
|
|
|
|
|
Debit: debit,
|
|
|
|
|
Credit: credit,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(je.Lines) < 2 {
|
|
|
|
|
accounts, _ := models.GLAccountGetAll(h.DB)
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "New Journal Entry",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Accounts": accounts,
|
|
|
|
|
"Today": time.Now().Format("2006-01-02"),
|
|
|
|
|
"Error": "At least two lines are required",
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/journal_entry_form.html"}, data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := models.JournalEntryInsert(h.DB, je); err != nil {
|
|
|
|
|
accounts, _ := models.GLAccountGetAll(h.DB)
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "New Journal Entry",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Accounts": accounts,
|
|
|
|
|
"Today": time.Now().Format("2006-01-02"),
|
|
|
|
|
"Error": err.Error(),
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/journal_entry_form.html"}, data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", "/ledger/journal")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, "/ledger/journal", http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) JournalEntryDetail(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
entry, err := models.JournalEntryGetByID(h.DB, id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Journal entry not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "Journal Entry #" + strconv.Itoa(id),
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Entry": entry,
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/journal_entry_detail.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) TrialBalance(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
rows, err := models.GetTrialBalance(h.DB)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var totalDebit, totalCredit float64
|
|
|
|
|
for _, row := range rows {
|
|
|
|
|
totalDebit += row.Debit
|
|
|
|
|
totalCredit += row.Credit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "Trial Balance",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "ledger",
|
|
|
|
|
"Rows": rows,
|
|
|
|
|
"TotalDebit": totalDebit,
|
|
|
|
|
"TotalCredit": totalCredit,
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "ledger/trial_balance.html"}, data)
|
|
|
|
|
}
|