some testing with python
This commit is contained in:
177
internal/database/refrence-repo.go
Normal file
177
internal/database/refrence-repo.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ── Domain types ──────────────────────────────────────────────────────────────
|
||||
|
||||
type Department struct {
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
CostCenter string `json:"cost_center"`
|
||||
Active bool `json:"active"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
type GLAccount struct {
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"` // revenue | cogs | opex | capex | headcount
|
||||
FavourHigh bool `json:"favour_high"` // true = over-budget is good (revenue accounts)
|
||||
Active bool `json:"active"`
|
||||
}
|
||||
|
||||
// ── ReferenceRepo ─────────────────────────────────────────────────────────────
|
||||
|
||||
type ReferenceRepo struct {
|
||||
db *DB
|
||||
}
|
||||
|
||||
func NewReferenceRepo(db *DB) *ReferenceRepo {
|
||||
return &ReferenceRepo{db: db}
|
||||
}
|
||||
|
||||
// ── Department operations ─────────────────────────────────────────────────────
|
||||
|
||||
func (r *ReferenceRepo) CreateDepartment(ctx context.Context, d Department) (*Department, error) {
|
||||
res, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO departments (code, name, cost_center, active)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(code) DO UPDATE
|
||||
SET name = excluded.name,
|
||||
cost_center = excluded.cost_center,
|
||||
active = excluded.active`,
|
||||
d.Code, d.Name, d.CostCenter, boolToInt(d.Active),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("upsert department: %w", err)
|
||||
}
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
if err != nil || id == 0 {
|
||||
// ON CONFLICT branch — fetch existing row
|
||||
return r.getDepartmentByCode(ctx, d.Code)
|
||||
}
|
||||
d.ID = int(id)
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (r *ReferenceRepo) getDepartmentByCode(ctx context.Context, code string) (*Department, error) {
|
||||
var d Department
|
||||
var active int
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
`SELECT id, code, name, cost_center, active, created_at FROM departments WHERE code = ?`, code,
|
||||
).Scan(&d.ID, &d.Code, &d.Name, &d.CostCenter, &active, &d.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch department by code: %w", err)
|
||||
}
|
||||
d.Active = active == 1
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (r *ReferenceRepo) ListDepartments(ctx context.Context) ([]Department, error) {
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT id, code, name, cost_center, active, created_at
|
||||
FROM departments ORDER BY code`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var depts []Department
|
||||
for rows.Next() {
|
||||
var d Department
|
||||
var active int
|
||||
if err := rows.Scan(&d.ID, &d.Code, &d.Name, &d.CostCenter, &active, &d.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Active = active == 1
|
||||
depts = append(depts, d)
|
||||
}
|
||||
return depts, rows.Err()
|
||||
}
|
||||
|
||||
func (r *ReferenceRepo) DeleteDepartment(ctx context.Context, id int) error {
|
||||
_, err := r.db.ExecContext(ctx, `DELETE FROM departments WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ── GL Account operations ─────────────────────────────────────────────────────
|
||||
|
||||
func (r *ReferenceRepo) CreateGLAccount(ctx context.Context, a GLAccount) (*GLAccount, error) {
|
||||
res, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO gl_accounts (code, description, type, favour_high, active)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(code) DO UPDATE
|
||||
SET description = excluded.description,
|
||||
type = excluded.type,
|
||||
favour_high = excluded.favour_high,
|
||||
active = excluded.active`,
|
||||
a.Code, a.Description, a.Type, boolToInt(a.FavourHigh), boolToInt(a.Active),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("upsert gl_account: %w", err)
|
||||
}
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
if err != nil || id == 0 {
|
||||
return r.getGLAccountByCode(ctx, a.Code)
|
||||
}
|
||||
a.ID = int(id)
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *ReferenceRepo) getGLAccountByCode(ctx context.Context, code string) (*GLAccount, error) {
|
||||
var a GLAccount
|
||||
var favourHigh, active int
|
||||
err := r.db.QueryRowContext(ctx,
|
||||
`SELECT id, code, description, type, favour_high, active FROM gl_accounts WHERE code = ?`, code,
|
||||
).Scan(&a.ID, &a.Code, &a.Description, &a.Type, &favourHigh, &active)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fetch gl_account by code: %w", err)
|
||||
}
|
||||
a.FavourHigh = favourHigh == 1
|
||||
a.Active = active == 1
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
func (r *ReferenceRepo) ListGLAccounts(ctx context.Context) ([]GLAccount, error) {
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
`SELECT id, code, description, type, favour_high, active
|
||||
FROM gl_accounts ORDER BY code`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var accts []GLAccount
|
||||
for rows.Next() {
|
||||
var a GLAccount
|
||||
var favourHigh, active int
|
||||
if err := rows.Scan(&a.ID, &a.Code, &a.Description, &a.Type, &favourHigh, &active); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.FavourHigh = favourHigh == 1
|
||||
a.Active = active == 1
|
||||
accts = append(accts, a)
|
||||
}
|
||||
return accts, rows.Err()
|
||||
}
|
||||
|
||||
func (r *ReferenceRepo) DeleteGLAccount(ctx context.Context, id int) error {
|
||||
_, err := r.db.ExecContext(ctx, `DELETE FROM gl_accounts WHERE id = ?`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// ── helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
func boolToInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
144
internal/handler/refrence.go
Normal file
144
internal/handler/refrence.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"Engine/internal/database"
|
||||
)
|
||||
|
||||
type ReferenceHandler struct {
|
||||
repo *database.ReferenceRepo
|
||||
}
|
||||
|
||||
func NewReferenceHandler(repo *database.ReferenceRepo) *ReferenceHandler {
|
||||
return &ReferenceHandler{repo: repo}
|
||||
}
|
||||
|
||||
// ── Departments ───────────────────────────────────────────────────────────────
|
||||
|
||||
// POST /api/v1/departments
|
||||
// PUT /api/v1/departments/{id} (same body, id from path)
|
||||
func (h *ReferenceHandler) CreateDepartment(w http.ResponseWriter, r *http.Request) {
|
||||
var req database.Department
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err))
|
||||
return
|
||||
}
|
||||
req.Code = strings.TrimSpace(req.Code)
|
||||
req.Name = strings.TrimSpace(req.Name)
|
||||
if req.Code == "" || req.Name == "" {
|
||||
writeError(w, http.StatusBadRequest, "code and name are required")
|
||||
return
|
||||
}
|
||||
// Default active to true when not explicitly set to false
|
||||
if !req.Active {
|
||||
req.Active = true
|
||||
}
|
||||
|
||||
dept, err := h.repo.CreateDepartment(r.Context(), req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("create department: %v", err))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, dept)
|
||||
}
|
||||
|
||||
// GET /api/v1/departments
|
||||
func (h *ReferenceHandler) ListDepartments(w http.ResponseWriter, r *http.Request) {
|
||||
depts, err := h.repo.ListDepartments(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("list departments: %v", err))
|
||||
return
|
||||
}
|
||||
if depts == nil {
|
||||
depts = []database.Department{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, depts)
|
||||
}
|
||||
|
||||
// DELETE /api/v1/departments/{id}
|
||||
func (h *ReferenceHandler) DeleteDepartment(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r, "id")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid id")
|
||||
return
|
||||
}
|
||||
if err := h.repo.DeleteDepartment(r.Context(), id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("delete department: %v", err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ── GL Accounts ───────────────────────────────────────────────────────────────
|
||||
|
||||
var validGLTypes = map[string]bool{
|
||||
"revenue": true, "cogs": true, "opex": true, "capex": true, "headcount": true,
|
||||
}
|
||||
|
||||
// POST /api/v1/gl-accounts
|
||||
func (h *ReferenceHandler) CreateGLAccount(w http.ResponseWriter, r *http.Request) {
|
||||
var req database.GLAccount
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid body: %v", err))
|
||||
return
|
||||
}
|
||||
req.Code = strings.TrimSpace(req.Code)
|
||||
req.Description = strings.TrimSpace(req.Description)
|
||||
if req.Code == "" || req.Description == "" {
|
||||
writeError(w, http.StatusBadRequest, "code and description are required")
|
||||
return
|
||||
}
|
||||
if !validGLTypes[req.Type] {
|
||||
writeError(w, http.StatusBadRequest,
|
||||
"type must be one of: revenue, cogs, opex, capex, headcount")
|
||||
return
|
||||
}
|
||||
if !req.Active {
|
||||
req.Active = true
|
||||
}
|
||||
|
||||
acct, err := h.repo.CreateGLAccount(r.Context(), req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("create gl_account: %v", err))
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, acct)
|
||||
}
|
||||
|
||||
// GET /api/v1/gl-accounts
|
||||
func (h *ReferenceHandler) ListGLAccounts(w http.ResponseWriter, r *http.Request) {
|
||||
accts, err := h.repo.ListGLAccounts(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("list gl_accounts: %v", err))
|
||||
return
|
||||
}
|
||||
if accts == nil {
|
||||
accts = []database.GLAccount{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, accts)
|
||||
}
|
||||
|
||||
// DELETE /api/v1/gl-accounts/{id}
|
||||
func (h *ReferenceHandler) DeleteGLAccount(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := pathID(r, "id")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid id")
|
||||
return
|
||||
}
|
||||
if err := h.repo.DeleteGLAccount(r.Context(), id); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, fmt.Sprintf("delete gl_account: %v", err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// ── Shared helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
func pathID(r *http.Request, key string) (int, error) {
|
||||
return strconv.Atoi(r.PathValue(key))
|
||||
}
|
||||
Reference in New Issue
Block a user